— Как поступают VIP SWE, когда нужно задизайнить большую систему?
— VIP SWE рисуют квадратики и стрелочки.

Эта статья про то, как делать гибкую и расширяемую архитектуру с помощью простейших инструментов. Я буду называть такой подход «Методом коспонентов», чтобы это звучало достаточно универсально и применимо ко всем инженерным областям, где присутствует вложенность и зависимости между компоентами. Метод компонентов даёт интероперабельность, платформы, области ответственности, управление жизненным циклом, свободу в выборе технологий, бесконечный источник дофамина и избавляет от боли в суставах. Короче, компонентный подход реально CRAZY. А самое главное то, что он очень простой.

✍️ Квадратики – папочки, стрелочки – зависимости

Компоненты – это просто директории
Компоненты – это просто директории

Квадратики отражают компоненты. Компоненты могут вкладываться в другие компоненты – точно так же, как директории в файловой системе. Инструменты сборки для любого языка программирования позволяют собрать содержимое директории в дистрибуцию, например, в библиотеку, которую можно подключить как зависимость в другой компонент. Такие зависимости мы будем показывать стрелочками.

Зависимости, определяемые сборщиком показаны стрелочками
Зависимости, определяемые сборщиком показаны стрелочками

На диаграмме crm/core зависит от crm/api, потому что ядро CRMки реализует своё API, а так же от payments/api, потому что CRM использует API платёжного шлюза.

На этом введение в метод компонентов закончено. Теперь мы готовы допроектировать нашу систему до настоящей Premium Архитектуры, которая обеспечит 100% реюз кода, интероперабельность, и всё то, что я обещал в самом начале.

Service Provider Interface

Первым делом добавим источники данных. Нам нужно сделать так, чтобы наш прикладной core ничего не знал о системных деталях интеграции с внешним миром. Так что мы обязаны сделать API — Core — SPI бутерброд.

Добавление Serivce Provider Interface
Добавление Serivce Provider Interface

Между API и SPI много общего, но это не одно и то же:

  • Application Programming Interface (API) – это контракт, который мы отдаём, и мы же обязаны его исполнить.

  • Service Provider Interface (SPI) – это контракт, который мы запрашиваем, а кто-то другой его исполняет.

В нашем случае исполнителем контракта хранилища данных для CRM может быть Postgres, а для Платёжного шлюза – TigerBeetle.

Брат, минутку, я запустил тэгэшечку @ViciousDesign и там ещё много такого, подписывайся!

Bootstrap зависимости

Теперь соберём всё в приложение. Для этого сделаем новый bootstrap компонент. Его задача – стать Plug'n'Play зависимостью, которую достаточно просто подключить в конечное приложение, чтобы заработала CRM или Платёжный шлюз.

Bootstrap библиотека, которую достаточно просто подключить
Bootstrap библиотека, которую достаточно просто подключить

Благодаря тому, что обе bootstrap библиотеки находятся в одном и том же котексте, в качестве реализации API платёжного шлюза будет выбран payments/core. Так умеют делать все современные фреймворки. А если ваш язык свободен от фреймворков, то не беда – такое и там можно делать, проконсультировавшись со слоп-машиной.

Интероперабельность

А что, если нагрузка на компоненты будет неравномерной? Было бы здорово масштабировать CRM и платёжный шлюз независимо друг от друга.

Отлично, нам даже не потребуется дорабатывать CRMку, ведь мы грамотно воспользовались интерфейсами!

Разделение монолита на микросервисы
Разделение монолита на микросервисы
  1. Разделим приложения. Теперь CRM и платёжный шлюз могут масштабироваться независимо.

  2. Добавим rest-client для Платёжного шлюза, который реализует API Payments, но под капотом делает REST вызов

  3. Добавим rest-server для Платёжного шлюза, который просто дёргает оригинальную реализацию платёжного шлюза. Оригинальность гарантируется bootstrap компонентом, который в качестве реализации API подключает core платёжного шлюза

  4. Общие компоненты сервера и клиента положим в rest-common. Например, туда можно положить все наши DTO.

  5. Обернём клиент в собственную bootstrap библиотеку, чтобы CRM'ке достаточно было просто подключить новую зависимость.

Voila! За один спринт наш монолит превратился в элегантный кавайный кластер с ограниченными контекстами и интероперабельностью!

Платформы

Вот мы и дошли до инженерной 🪄 имбулечки 🪄, которая вот уже 325475812 лет применяется в автомобилестроении.

Что будет завтра? Вероятнее всего у нас будет много компонентов, которым требуется авторизация, синхронная и асинхронная коммуникация, объектные, горячие, холодные хранилища и прочее. Было бы настоящим безумием каждый раз настраивать это всё с нуля для каждого приложения. Premium инженеры так не работают. Так что мы, как инженеры, которые не тратят деньги клиентов попусту, вынесем общие решения в платформу.

В нашем случае я решил вынести в платформу:

  1. Базовые настройки для подключения к Postgres

  2. Базовые настройки для подключения к TigerBeetle

  3. Общие зависимости и настройки для коммуникации по REST

Это не предел: чем больше общих решений, тем больший показатель переиспользуемости кода. При тщательном проектировании можно исключить повторы вовсе. Это DRY в чистом виде.

Статья написана чтобы набрать здоровых, умных, смелых подписчиков в мой telegram канал @ViciousDesign, где я рассказываю про дизайн ПО.

Что мы получили?

Очевидные области ответственности

Квадратики показывают границы областей ответственности в коде. Эти области можно назначать на команды. Стрелочки будут показывать, кто с кем взаимодействует – прямо по закону Конвея. Почитать про это можно в книге «Team Topologies».

Управление жизненным циклом

Чем мельче компоненты, тем больше возможностей выкинуть старое решение и разработать новое. Такой подход позволяет планировать срок жизни решения. Если кто-то утаит сложность отдельного компонента, его будет проще выкинуть и сделать новое.

Фокус на требованиях

Компонентный подход позволяет выбирать технологии, исходя из требуемых потребительских качеств, благодаря способности абстрагироваться от инструментов. Так что вместо того чтобы выбирать питон «потому что только его и знаем» или Spring «потому что на нём все пишут», мы можем выбирать технологии в соответствии с тем, что ожидает потребитель, стейкхолдеры или 🪄 действительность 🪄 в виде бюджета на разработку.

Когда это использовать?

Всегда. Компонентный подход – генштаб проектирования. Без него проект превратится в комок грязи. Он прост, универсален, поддерживается любым сборщиком. Систему компонентов можно развивать от простого к сложному, разбивать на множество репозиториев, команд и приложений. Я не вижу причин не пользоваться таким подходом в каждом проекте.

🏆 Хоп, награда читателю в виде конца статьи. 🏆