Монолит часто обсуждается в негативном ключе.
Но не каждый сможет сразу перейти на микросервисы — и это не первая команда и компания, которая делится опытом построения «переходного звена»: модульной архитектуры.
Давайте подробнее рассмотрим, как создаются подобные проекты.
Недавно посмотрел два репортажа на эту тему: от Юлия Николаева из iSpring и Антон Губарев из Скайенга.
Поскольку мы с Антоном работаем в одной компании, я решил поговорить с Юлей для своего подкаста.
Ниже приводится текстовая расшифровка основных идей беседы.
«Мы взвесили причины, по которым мы хотели перейти на микросервисы, и реальные проблемы, которые мешали нам жить в монолитной архитектуре».
Юлия: Когда мы приступаем к разработке приложения, функционала мало и его нужно запустить как можно быстрее.
Но если дело пойдет дальше, на определенном этапе кодовая база начинает бесконтрольно разрастаться — и не принимается никаких новых архитектурных решений, которые бы сдерживали этот хаос.
Так произошло и с нашим проектом: он стартовал в 2009 году, но долгое время не было людей и времени заниматься неконтролируемым ростом кодовой базы.
И к 2018 году стало ясно: пора как-то реструктуризировать монолит. Когда встал вопрос, что делать с нашим монолитом, мы, естественно, подумали и о микросервисах.
Но.
Распределенная архитектура имеет свои накладные расходы.
А мы не были к этому готовы.
Например, у нас вообще не было devops-команды.
Не было нормального CI/CD как такового, не было ни Docker, ни Kubernetes — вообще ничего такого.
Плюс у нас не возникло острой проблемы масштабирования: таких нагрузок нет. Что действительно мешало, так это очень тесная связанность кода.
Изменение одной части часто приводило к ошибкам в куче других мест, плюс мы не могли правильно разработать некоторые фичи.
Вернее, мы могли бы развиваться параллельно, но объединиться.
на этом этапе могут возникнуть неожиданные логические конфликты.
Мы поняли: решив острые проблемы, мы можем какое-то время жить в монолите.
Но мы также подумали о том, как облегчить потом переход на микросервисную архитектуру? Наша тематика – дистанционное обучение.
И здесь можно выделить модули: геймификация, управление курсами, статистика, отчеты — такие поднаправления внутри основной предметной области.
Мы начали представлять наше приложение как набор независимых модулей.
Для нас все усугублялось тем, что кусок наследия был написан на Symfony 1.4 с использованием Propel 2, который не так-то просто использовать в парадигме DDD (Domain Driven Design).
И нам очень хотелось DDD. Для этого хорошо подходит ORM Doctrine, которую нелегко привязать к наследию.
Мы решили скрестить змею с ёжиком и теперь живём с двумя рамками.
Сейчас я думаю, что мы могли бы пойти другим путем: не привязывать новый фреймворк, не привязывать доктрину, а просто попытаться сделать чистую архитектуру на том, что у нас есть.
По сути, наша проблема заключалась в том, что бизнес-логика была в пирах Propel, моделях, контроллерах — везде, где ее не было.
А можно было, как и ребята из еще одна проблема , на старом фреймворке сначала выберите модули, попробуйте реализовать архитектуру, а потом перетащите все это в новый фреймворк.
Возможно, так было бы проще и быстрее.
«Буквально каждый день, на каждом шагу мы сталкивались со множеством вопросов, которые не знали, как решить»
Мы, тимлиды, сначала вместе, а потом втроем разрабатывали функционал в этой новой архитектуре.Было много вопросов.
Начав с того, как организовать папки в проекте: однако, когда они это сделали и поняли, как и зачем, то нашли в Интернете просто массу источников, в которых все это было написано.
Наверное не так искали)
При этом часть команды разработчиков ушла в девопс.
Они начали переходить на кубернетес, докер и все такое.
Чтобы погрузить в тему остальную команду, мы подготовили специальный курс по DDD, который все прошли за пару выходных.
Нашим разработчикам пришлось реализовать два контекста — напрямую закодировать их.
Мы, конечно, поделились всеми идеями, но получили обратную связь, которая была очень сложной и «мозговой».
Да, поначалу тяжело.
Но когда разработчик опробует и примет новую архитектуру, и к нему придет фича, которую нужно сделать в легаси, у него сразу возникнет мысль: «Как мне сделать так, чтобы это было похоже на новую архитектуру».
Это очень сильно меняет ваше мышление.
Уже есть примеры, что, переделывая кусок, его стараются упорядочить и покрыть интерфейсами, чтобы он был более-менее похож на чистую архитектуру, чтобы потом было легче удалить.
На все про все у нас ушло, наверное, месяца три с половиной.
Мы не только перепроектировали архитектуру, но и реализовали новый функционал с удалением частей из наследия.
Плюс все эти три месяца команда devops настраивала нашу инфраструктуру, чтобы мы могли взлететь в Kubernetes. Сейчас в новом коде у нас 14-15 контекстов плюс большое наследие, где можно было выделить несколько контекстов.
Хотя большинство контроллеров остаются в Legacy, было неясно, куда поместить их код, поскольку контроллеры могут получать данные и манипулировать ими из разных контекстов.
И ура! Мы можем писать функции в 3-4 потока командой из 12 бэкендеров.
Сергей: В PHP мы не можем дать программисту пощечину и запретить ему использовать какой-то внутренний класс другого модуля.
Это все держится на устных договоренностях внутри команды.
Как все это организовать? Юлия: Такую архитектуру сложно контролировать.
Все разработчики должны быть погружены в эту идею и поделиться ею.
Через эти грабли мы прошли поначалу, когда привлекали команду.
Многим казалось, что это дикий оверинжиниринг: «Почему это так сложно, давайте не будем делать так».
Но через некоторое время, почувствовав разницу между написанием кода в новой архитектуре и в устаревшей, все вдохновились.
Некоторое время мы жили по договоренностям и старались все отслеживать во время проверки.
Но теперь между модулями есть API, через который они взаимодействуют. И мы не имеем права обойти это API и выдернуть что-то из модуля.
Мы нашли такую утилиту — департамент — статический анализатор кода, который помогает контролировать зависимости.
Утилита встроена в наш CI/CD и запускается автоматически: по сути, с ее помощью контролируется большая часть соглашений.
Логические ошибки мы пытаемся выловить на архитектурном ревью: это собрание, на котором разработчик, который будет нарезать фичу, представляет схемы, где все уже разбито на слои.
Там сразу понятно, что в каком контексте, какие отношения и т.д. Сергей: Как поддерживать такую архитектуру? Юлия: У нас есть статьи о том, как наслаивать классы.
Информацию о выделении контекстов очень сложно формализовать, поскольку для каждого признака это достаточно специфично.
Здесь не может быть никаких правил, это всего лишь архитектурный шаблон.
Сергей: У нас одна база данных, у нас один проект. Что мне делать, если моему модулю нужны данные из нескольких других контекстов? Юлия: Это один из самых сложных вопросов — наверное, как в модульных, так и в микросервисных архитектурах.
В одном из случаев мы пытались реализовать объединение данных из разных API: делали запросы, объединяли в памяти, сортировали, фильтровали.
Работало ужасно медленно.
Сейчас мы лукарим и читаем из чужих таблиц — это предположение, которое мы пока себе позволяем в связи с тем, что у нас нет ни времени, ни ресурсов, чтобы реализовать его нормальным образом.
Когда возникает такой сложный случай, иногда мы отступаем от правил и читаем напрямую из базы данных.
Сергей: И тут мы подходим к проблеме дублирования кода.
Юлия: Дублирование происходит, например, если у нас есть уровень, который разрешает операции во всех контекстах — и мы дублируем его в каждом контексте.
То есть у нас почти один и тот же код, который спрашивает: «Есть ли у пользователей праваЭ» Но мы считаем, что это вполне приемлемое дублирование.
Сергей: Что делать, если одному модулю необходимо выполнить действие в другом модуле? Например, при совершении платежа, изменении баланса, создании уведомления — это все разные контексты.
Как с этим бороться в монолите? Юлия: Вы перечислили случаи, которые хорошо вписываются в событийную модель.
Мы многое сделали с помощью асинхронных событий.
Если контекст не должен ничего знать о том, что произойдет в других контекстах, то возникнет событие, которое будет обработано всеми заинтересованными в нем лицами.
Если действие тесно связано с выполняемой операцией и вам необходимо знать ее контекст, напрямую вызывает метод API.
Всё то же самое, что и в микросервисной архитектуре, грубо говоря.
Модель событий везде одинакова.
Одно событие может прослушиваться разными модулями и обрабатываться по-разному.
И даже в одном модуле может быть несколько разных обработчиков одного события, если это необходимо.
Сергей: Но если приложение постоянно усложняется функциями и контекстами, является ли модульный монолит всего лишь промежуточным шагом на пути к микросервисам? Юлия: Да, конечно.
Если подразумевается, что приложение будет постоянно расти по мере роста команды, это естественно.
Например, у нас была проблема, что автотесты выполняются нереально долго перед релизом, и если что-то отваливается, приходится запускать все заново.
Новые возможности, если они будут независимы, мы, вероятно, сразу реализуем в микросервисах.
Более того, у нас уже есть инфраструктура, есть шаблон, на основе которого мы можем создать новый микросервис.
Но, скажем, если вам нужно использовать какие-то данные или классы из Legacy, у которого нет своего API. У нас есть принцип, что Legacy может подтягивать API, но новые контексты из Legacy фактически не могут ничего получать и не общаются с ними, только через события.
Если мы переносим что-то в микросервис, и этому микросервису нужен доступ к устаревшему коду или какому-то функционалу, то сделать это будет достаточно сложно.
P.S. Больше выпусков подкаста «Между скобками» с интересными людьми из мира PHP можно найти здесь.
Здесь .
Теги: #Управление разработкой #Микросервисы #php #symfony #Perfect Code #утилиты #утилиты #утилиты #утилиты #модульный монолит #архитектура сервисов
-
Необходимость Интегрированного Сообщества
19 Oct, 24 -
«Налог На Бланки» Могут Отменить
19 Oct, 24 -
Github Два Дня Противостоит Ddos-Атаке
19 Oct, 24 -
Так Вот Почему Google Купил Runner
19 Oct, 24