Продолжение. К предыдущим постам и карте цикла.
В жизни каждого проекта наступает тот самый волнительный и незабываемый момент, когда от архитектора требуется только архитектура. Волнительный он по вполне понятным причинам. К этому моменту вы уже столько раз повторяли и уточняли дизайн, что и сами стали верить в эту сказку. А теперь вот раз и надо архитектуру. Не концепции и технологии, а вот прям весь хребет слона, которого будут растить пару лет в инкубаторе, а потом лет 10 на пастбищах по всему миру, пока из резервации он не попадёт в консервацию. Ну а памятным это событие делает тот факт, что все последующие годы вам будут припоминать всё мелкие недочёты, которые почему-то вы не смогли предвидеть. Ведь вам дали неделю, а может даже и целых две на то, чтоб определить направление и основные вехи разработки всех команд на годы вперёд.
Итак, контракт подписан и вашей компании выслали официальное извещение о победе на тендере. Теперь пора переходить от теории к практике. Вы работаете в современной компании и все отделы и департаменты давно работают по Agile. То есть их горизонт знаний о проекте ограничен несколькими итерациями. Ну вот эта вот любимая всеми картинка:
Если бы архитектору дома сказали, что клиенту хватит палатки или может в крайнем случае бытовки, а через год попросили добавить 3 этажа, то он бы, наверное, удивился. Еще сильней он бы удивился, когда еще через год ему добавили требование лифта, еще 5-ти этажей и бассейна на крыше. А чтобы он думал и делал, когда за пару месяцев до сдачи проекта ему сказали, что там еще подземная парковка на 100 машин должна быть – я уж и не знаю. А вот в программной разработке – это всё обычное дело. У нас ведь все такие гибкие и топят за адаптивность, а не выстрелы на опережение (особенно если они уже дорогие, но еще не обоснованные). И если вы просто будете апеллировать к методологии, которая подразумевает не только инкрементальность разработки, но и постоянное корректирование уже созданного, то ваша карьера будет яркой, но не долгой.
Да, сюрприза не получилось. Если не планировать вещи заранее, то всё выходит криво и надо постоянно перестраивать. С одной стороны, никто не хочет тратить год на документацию и детальный план разработки продукта, ведь все мы живём в реальном мире и достаточно хорошо понимаем, что изменения неизбежны и нельзя предусмотреть всё. С другой, очевидно, что если просто бежать, не зная дороги и цели, то шанс в конечном счете оказаться там, где нужно и когда ожидают – ничтожно мал. Так что для начала нужно точно определить, где мы (точку А), и куда должны попасть (точку Б). Слава богу на этапе начала разработки вы уже прошли несколько шагов в нужном направлении и примерно представляем цель и длину маршрута.
Возвращаясь к первой статье цикла, ещё раз озвучу своё мнение о назначении архитектуры. Это оптимальный путь решения задач бизнеса. При выборе паттернов проектирования, методологии работы, технологий и фреймворков, мы всегда получаем набор функций и решений, которые работают из коробки и набор ограничений из табакерки. Ещё до финальной подписи в контракте на души разработчиков, вы должны были перепроверить всё, что заявлено. Значит основная парадигма у нас есть. Что-то типа: микросервисы в облаке с разбивкой по DDD с локальным клиентом на Java. Полезность правила измеряется количеством исключений. Так что чем меньше перекосов в сторону монолита на клиенте или костылей вокруг облака – тем лучше. Тут никакого откровения нет. Но, всё же не хочется быть автором ещё одной пустой статьи из серии «мы в компании Х решили всё сделать по У и у нас всё получилось». Ведь я-то как раз и хотел в статьях показать путь, которым приходят к выбору У и как даётся тот самый success story.
Как вы, возможно, читали ранее – выбор часто делают за вас. Технологии - из того, что знает команда, методология тоже какая уж есть в вашей корпорации, провайдеры и стандарты обычно определяются заказчиком как одно из требований тендера. Вам же достаётся игра в Counter Strike. Пытаетесь обезвредить бомбы, которые уже заложены. Особенности работы с большими предприятиями я уже описывал. Так что сам по себе пазл не сложится. Облако и микросервисы – стандарт де-факто и, почти наверняка, вас не миновала чаша сия. Такие системы всегда обладают атрибутами AP. Значит стоит определить какие узлы требуют CA.
Энтерпрайз системы в основном рассчитаны на использование сотрудниками компании для автоматизации процессов. Зачастую процессы критические и производственные. Значит нужно чётко понимать, что должно работать вне облака. Почтовое отделение, заводик или магазин должны продолжать обслуживать даже, когда облако не доступно (это не облако упало, а ваш клиент потерял соединение). И данные на месте нужны актуальные (тот самый СА вариант). Если мы возьмём за пример систему управления логистикой, где у нас есть куча грузовиков развозящих товары по дорогам страны, то понятно, что никакие изменения данных в облачном диспетчере не изменят содержимое кузова. Ну и совсем не хочется, чтоб этот грузовик простаивал, если у него нет связи с сетью. Значит его локальная система должна иметь возможность учёта товара, просмотра маршрута и управления исключениями, без которых в реальной жизни никак. Глаз в облаке будет довольствоваться данными последней синхронизации и предполагаемым прогрессом (эвристикой текущего состояния). Заявленные микросервисы на планшет водителя устанавливать видимо тоже не стоит. Они уместны в центре управления, высоко в облаках, там, где вершина корпоративной пирамиды. Но процессы и данные у центра и аппликации водителя будут перекликаться процентов на 70. Это как раз и создаёт предпосылки к путаницам с распределёнными и модульными монолитами. Нежелание повторяться приводит к сильно связанным громоздким сервисам и полурабочему зоопарку клиентов.
Иногда всё же стоит дублировать логику и учесть, что она может отличаться (центральная система должна уметь обрабатывать данные с оффлайновых приложений, даже если они не соответствуют её ожиданиям). Центр знает, что в машине 3 ящика молока и в каждом пункте доставки должны выгрузить один – в конечной синхронизации может оказаться, что 3 места посетили, а доставили только 2 ящика. Водитель забыл. Или разбил. Или на одной из точек выгрузил 2. Если вы всё время в онлайне, то постоянный поток данных позволяет следить и корректировать поведение. Допустим через нотификации и подтверждениях о выгрузки товара со стороны получателя. Без этого – физический мир просто сообщает вам фактическое состояние и живите с этим как хотите.
Если вам повезло и логику можно инкапсулировать в модуль, который легко впишется и в сервис, и в аппликацию на планшете (reuse over duplication), то данные всё равно будут жить своей жизнью. Но чем меньше кода, тем лучше. Так что если у вас предполагается работа без доступности центра, то стоит свести внесетевые функции к минимуму. Стандартная отказоустойчивость как часть дизайна. Водитель должен отчитаться о разбитом ящике молока – не стоит сразу дублировать форму на случай, если связи нет. Может достаточно создать напоминание об этом событии с принудительным заполнением в конце маршрута и оставить сам рапорт только сервисом на бэке? Не работает встроенный принтер – сохраним файл. А иногда и просто добавим бумажный формуляр к планшету, а в аппликации потребуем сделать его снимок.
Ошибки молодости
Разработчик сфокусирован на задании и не знает всей предыстории и, главное, последующих приключениях того, что он пишет. Особенно в большом проекте, где он один из легиона. Чтобы попасть в production и выстрелить, ошибка имплементации должна пережить всякие ревью, рефакториниги, автоматическое и ручное тестирование. Да и после этого, обычно, починить можно максимум за недельку. Редко, когда на ошибочном исполнении висят несколько процессов (хотя и такое бывает). А вот недочёт проектирования может вылиться в месяцы работы. Одним из таких подводных камней была моя уверенность, что новая система сменит старую и автоматизация заменит ручной труд. Система датчиков должна была свести к минимуму интеракцию с пользователем, искоренить человеческий фактор и поднять достоверность данных. На деле же заказчик боялся довериться системе и не стал менять процедуры ручных проверок. Ни в каких требованиях и ожиданиях это не было обговорено. Ручной ввод был на случай отказа датчиков, а не в дополнение к ним. Система не могла справиться с прыжками и конфликтами в показаниях между данными от ручных измерений и сенсорами. Пришлось добавить еще один сервис для коррекции входных данных, перенаправить потоки, поменять процессы и переустановить весь прод. С тех пор я всегда рассчитываю на то, что новый продукт работает side-by-side. Этот подход так же помогает подготовиться к миграции и поддержке множества версий.
Ну и как найти и учесть все подобные случаи заранее? А никак. Архитектура большого проекта похожа на школьные опыты по биологии. Там, где рассматривают кожицу лука под микроскопом. Сначала вы смотрите на всё со стороны и добавляете контрастную жидкость. Потом вставляете в микроскоп и настраиваете свет, пытаясь остаться в границах на минимальном приближении. И лишь найдя цель, меняете увеличение на максимум фокусируетесь на деталях. То есть сначала вы делали блок схему, а потом перед непосредственной разработкой будете делать архитектуру каждого блока, заполняя его деталями. По необходимости будете добавлять всякие flow и sequence – они легче всего воспринимаются всеми участниками процесса разработки. Так что в отличии от строительства, вы начинаете разработку с наброска, а детальную архитектуру получите лишь в конце. Поэтому так полезны, хоть и не популярны, большие UML пакеты, позволяющие ссылки на диаграммы и вложенные чарты. Разработка – это drill down от blue-print до detailed architecture.
Что же нужно делать в начале – это инфраструктуру. То, что заказчик явным образом не просит, а значит и добавить её позже будет сложно. Если и не технически, то политически точно. Основа любого проекта – команда. Именно на людях стоит сосредоточится и задать единый язык и шаблон. По законам Мёрфи – если в коде будет плохой кусок, то его обязательно выберут как пример и размножат бесчисленное количество раз. Обговорите с разработчиками структуру проекта, названия и, если нужно, стилистику кода. Многие считают нейминг не важным, но в долгой разработке у всего есть дополнительный вес. Единый язык продуктового отдела и разработчиков, увековеченный в коде, сильно упрощает поддержку и модификации. Представьте, что вам на рабочий стол попал проект в пару сотен тысяч строк, десятки сервисов и баг, который говорит, что расчёт потенциала не верен. Если поиск в базе кода и системе планирования/отслеживания задач с ключевым словом «потенциал» ничего не даст, то вас ждут долгие часы выяснения правильного сценария и пошаговой отладки бизнес-процесса, разбросанного по куче модулей. Вам кажется, что пример высосан из пальца, но мне не раз доводилось самому копаться в подобном, пахнущем кислой радугой, проекте. Обилие синонимов, всяких акронимов и аббревиатур сделает вашу жизнь еще сложнее.
Всё, что нужно делать вручную, будут игнорировать так или иначе. Начало проекта – начало автоматизации. Тут и юнит тесты (для правильного дизайна и возможности рефакторинга), и интеграционные тесты (для стабильности проекта и уменьшения time-to-market), и метрики кода (всевозможные KPI) – всё часть CI-CD. Игры в микросервисы с ручной настройкой и развёрткой оставим мазохистам. Нам необходима команда DevOps. Формально или нет, но кто-то должен быть ответственным за правильную конфигурацию и установку среды. Среды разработки, среды проверок, среды интеграции и т.д. Иначе в будущем вместо среды вас будут ждать суббота и воскресенье разработки и проверки. В производственных системах редко можно найти изолированные бизнес-кейсы. Все микросервисы должны будут взаимодействовать друг с другом и между ними будет явная или не явная зависимость. Устранить этот недостаток можно еще одним уровнем абстракции! Cервис – оркестратор сервисов. Тот, что вызывает цепочку других сервисов, чтоб обеспечить необходимый результат.
Вернёмся к нашему грузовику. Водитель хочет начать маршрут. Для этого необходимо: получить квоту на топливо, заправиться, погрузить товар, подтвердить погрузку, составить маршрут, сообщить на точки отгрузки предположительное время прибытия и так далее. Если каждый этап станет отдельным сервисом, то клиент будет отвечать за бизнес. А хотелось бы чтоб он был как можно тоньше. Удобно группировать несколько таких шагов в один процесс и вызов. Сервис заправки – он проверит маршрут, квоту и авторизирует заправку. То есть последовательно запустит 3–4 микросервиса (с десяток API вызовов и сохранить состояние между ними). Конечный автомат. Плодить такие сервисы бесплатно тоже нельзя. Стоит создать жирненькие мета-сервисы объединяющие кросс-домейн процессы, вместо того, чтоб рулить всем из фронта. Не легковесные они, потому как знают многое о многих и строят сложные процессы взаимодействия. Однако связь односторонняя. Собственно – это простой медиатор. Основная причина не раздувать клиентскую часть лежит в разнообразии таких приложений с однообразием и стандартизацией процесса. Абсолютно тот же функционал должен быть доступен в каком-нибудь легаси винформ монолите, нативном андройд клиенте, который кто-то запилил вашему заказчику пару лет назад, и новой веб-аппликации из вашего пакета. Очень удобно все три приложения заставить обращаться к одному сервису, сделав миграцию из legacy в nextgen быстрой и легко управляемой.
Помимо планирования есть еще и проверки того, что план соблюдают. Настройка правил статистического анализатора кода и внедрение проектных тестов (solution unit-test), на мой взгляд, задача архитектора. Что такое проектные тесты? Это жесткие правила ломающие билд при нарушении, написанные в виде юнит теста или плагина к какому-нибудь сонару. Примеры подобных тестов:
Минимальное покрытие тестами в определённых модулях
Ограничение по количеству заигнориных тестов
Тест на то, что тест с ограничением тестов в игноре не заигнорин (личный опыт)
Список запрещённых референсов (например уровень бизнес-логики не должен знать о типах в базе данных)
Список разрешённых референсов (допустим open source и legacy библиотеки)
Разрешённые типы файлов (к примеру файлов .sql не должно существовать)
Naming и структура проекта (если должно быть 3 слоя и каждый отдельной библиотекой, а тесты заканчиваться на _Test, то будьте любезны)
Парсинг и проверка скриптов на соответствие (например скрипт конфигурации должен создавать одноименную запись и в параметрах окружения и в файлах)
Минимальное количество строк лога в каждом классе (если актуально)
Типы исключений и их иерархия (допустим модуль безопасности не должен отдавать детальные exception, так что там может быть только один тип и без текста)
Такие тесты всегда делаются под конкретный проект и архитектуру решения. Всё это заставляет разработчика задуматься еще раз, и не даёт его менеджерам возможность легко увеличить тех.долг. Только не надо делать из тестов фетиш. Типа: «Каждый модуль должен быть на 100% покрыт тестами.». 100% покрытие тестом необходимое условие для пельменей, а не для проекта.
Теперь пора строить план. Что вы должны выдать в качестве необходимого функционала – вам сообщат все заинтересованные участники. Надо понять как много (или мало) останется на инфраструктуру. Все Agile, но только, когда им удобно. Выделить месяц на создание основы проекта без деловой логики/функционала, почему-то не хотят. Не достаточно прозрачно. Или сложно тестировать. Все уверены, что основа проекта закладывается с самой сложной формы в UI. Вот gateway ваш, безопасность и мониторинг можно и позже – их же всё равно никто не видит. А заказчику нужно что-то показать. Так что опять нужно будет продавать необходимость и своевременность. Как обычно апеллируя к тому, что увеличение числа процессов и компонентов прямо пропорционально стоимости внедрения 3-х «С» - сквозных системных служб. И, как всегда, создаётся впечатление, что это понимаете вы и пара инженеров, но не управленцы.
Если всё получилось, то «земля, прощай» и в добрый путь.