Я писал о подходе DDD к микросервисам (здесь и здесь) и ошибках шаблонов микросервисов. Эти статьи предлагают руководство по внедрению архитектуры микросервисов без реализации (иногда) ненужных сложных шаблонов и методов. В этой статье я рассмотрю четыре основные характеристики микрослужбы и представлю простую модель того, как может выглядеть отдельная микрослужба при использовании DDD в качестве общего подхода к проектированию.

Чтобы понять, почему был придуман термин «микросервисы», важно признать, что сервисы (SOA) уже были четко определены и широко использовались. Сервисы SOA должны были быть детализированными (SRP) и хорошо спроектированными, и с 2004 года они также были частью принципов проектирования, управляемого предметной областью. Однако со временем они становились все более тяжелыми и отклонялись от первоначальных принципов, что в конечном итоге привело к Архитектура «большой ком грязи». Некоторые разработчики определили эту проблему и подошли к разработке сервисов, используя исходные принципы (единая ответственность, более легкие протоколы, DDD) с гибким мышлением. Эти службы были разработаны отдельными группами и развернуты как независимые процессы с использованием полностью автоматизированных конвейеров CI/CD. Этот новый подход к созданию и развертыванию сервисов стал известен как микросервисы.

Однако этот метод разработки и развертывания также привел к возникновению различных проблем, которые были решены с использованием определенных шаблонов проектирования, таких как CQRS, Event Sourcing и Compensating Transactions. Хотя эти шаблоны были хорошо известны и применялись задолго до появления микросервисов, сейчас их обычно называют шаблонами микросервисов.

Ознакомьтесь с Ref 1, чтобы узнать мнение Мартина Фаулера о характеристиках микросервисов. По моему мнению, микросервис характеризуется следующими четырьмя особенностями:
1 — Детальная функциональность (принцип единой ответственности) и четкие границы
2 — Независимое развертывание
3 — Разрабатывается одним автономным независимым team
4 — Слабая связь с другими Службами и Клиентами благодаря интеграции на основе контрактов и упрощенным протоколам

Эти функции необходимы для достижения преимуществ архитектуры микросервисов. Однако некоторые другие характеристики, такие как использование событий, асинхронных коммуникаций, отдельной кодовой базы и базы данных для каждой службы, выводятся из вышеупомянутых четырех характеристик и являются решениями проблем, связанных с этими четырьмя, а также для повышения масштабируемости, отказоустойчивости, отказоустойчивости и т. д. NFR. Многие из этих шаблонов и подходов не являются специфическими для микросервисов, а являются общими практиками, используемыми в разных архитектурах.

Например, если независимая группа разрабатывает независимо развертываемую программную систему, которая слабо связана с другими программными системами, то для архитекторов программного обеспечения крайне важно выбрать модель данных и базу данных, которые также являются независимыми, не используются совместно с другими или не ограничиваются ими. программных систем (чтобы сохранить их автономию), и это принцип База данных на службу для архитектуры микрослужб. Сохранение всей отдельной кодовой базы также важно для автономии команды разработчиков. Точно так же асинхронные коммуникации и события (трансляция/активация и забвение) усиливают слабую связь между программными компонентами, в отличие от взаимодействий запрос-ответ, поскольку отправителю события не нужно знать (одну или несколько) Потребители событий. События также поддерживают мелкозернистую/SRP-реализацию микросервисов-эмитентов и потребителей. Асинхронные коммуникации помогают повысить масштабируемость и отказоустойчивость всего решения, а автоматический выключатель и аналогичные шаблоны повышают скорость отклика, стабильность и отказоустойчивость.

Чтобы эффективно внедрить проект, управляемый предметной областью, мы должны сначала определить ключевые компоненты в пределах ограниченного контекста, включая объекты-значения, сущности, агрегаты и операции (в виде доменных служб³). Доменные службы содержат доменную логику, которую нельзя поместить в Сущности, Объекты-значения и Агрегаты. Чтобы представить эти операции как API, мы создаем службы приложений, которые координируют и делегируют доменные службы, инфраструктурные службы и объекты. Кроме того, службы приложений настраивают необходимую среду, включая каналы связи, объекты репозитория/БД, обработку служб ввода-вывода/инфраструктуры и т. д., для выполнения операций, запрошенных потребителями API. Микрослужба инкапсулирует все эти компоненты в единое развертываемое устройство. Ссылка 5 дает хорошее представление о том, как можно реализовать ограниченные контексты DDD.

Ниже приведен пример высокоуровневой структуры кода службы приложений, реализующей парадигму API, описанную в моей предыдущей статье. Обратите внимание, что код, представленный ниже, не имеет синтаксиса какого-либо конкретного языка.

class UserProfileApplicationService {
  UserProfileCreateDomainService crtDmnSvc  = <Create and Initialize Service>;
  UserProfileUpdateDomainService updDmnSvc  = <Create and Initialize Service>;
  
  UserProfileRepository repo = <Create ORM classes>

  EventSubscription subs = <Subscribe to required events>

  create(UserProfile upf) {
    if (! repo.exists(upf)) {
      crtDmnSvc.process(upf);                => Any specific rules or validations to be performed
      UserProfile newPrf = repo.create(upf); => Returns newly create UserProfile
      // Raise any Events and then return new profile to the chassis
      return newPrf;
    }
    // Already exists error
  }
  
  enquiry(UserProfile reqUpf) {             => Does not need a Domain Service
    UserProfile enqUpf = repo.get(reqUpf);  => reqUpf.id or reqUpf.user_name is sent by the client
    enqUpf.validate(reqUpf);                => Validate state to be conducive for the consumer
    return enqUpf;
  }

  update(UserProfile reqUpf, UpdateFieldDetails updFields) {
    UserProfile updPrf = updDmnSvc.process(reqUpf, updFields);

    // Raise any Events and interact with any other service
    repo.update(updPrf, updFields);
    return updPrf;
  }

  block(UserProfile upf) {
    ...
  }

  userDisabledEvent(User user) {
    // This service subscribes to UserDisabled Event so that it can
    //   block the UserProfile as well or send notifications using the email
    //   set in the Profile
  }
}

Для создания микрослужбы профиля пользователя все необходимые службы (приложение и домен), сущности, агрегаты, объекты значений, события, репозиторий, утилиты и другие вспомогательные компоненты (шасси микрослужбы⁴) объединяются. UserProfileApplicationService является основным обработчиком всех запросов к микрослужбе профилей пользователей, предоставляя интерфейс, который позволяет клиентам выполнять различные операции с совокупностью профилей пользователей.

Давайте посмотрим, как этот микросервис может удовлетворить наши четыре характеристики:

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

Независимое развертывание
Этот микросервис построен с использованием шасси микросервиса, состоящего из всех необходимых сквозных функций и программных компонентов, что позволяет развертывать его как автономное независимое приложение вместе с независимой базой данных (репозиторием).

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

Слабая связь
Служба приложений профилей пользователей, входящая в состав этой микрослужбы, взаимодействует с другими микрослужбами с использованием протоколов на основе HTTP и инициирует события в канале/теме публикации-подписки. Клиенты этой службы также используют аналогичные API на основе HTTP. Этот микросервис также подписывается на события, инициированные службой управления пользователями, чтобы управлять жизненным циклом агрегата профилей пользователей.

UserProfileApplicationService также может быть разделен на несколько Application Services и развернут отдельно (в первую очередь для CQRS) одной и той же командой с использованием одних и тех же компонентов. Но эти «отдельные» Сервисы по отдельности не могут квалифицироваться как независимые и автономные Микросервисы, поскольку они не могут быть независимыми от изменений, вносимых друг в друга. Например, изменения в схеме базы данных повлияют на все службы, даже если мы используем CQRS, свойства и код задания репликации данных и структура БД реплики чтения также могут измениться при изменении структуры таблицы на стороне записи. Иногда эти разделенные службы «неточно» называют независимыми микрослужбами, поскольку они демонстрируют некоторые из этих характеристик и реализуют несколько других «шаблонов микрослужб».

В заключение мы оценили, что независимый микросервис должен обладать четырьмя характеристиками, изложенными выше. Различные другие «Шаблоны микросервисов» эффективно выводятся из этих четырех. Микросервисы могут дополнительно реализовывать различные специализированные шаблоны и подходы для удовлетворения конкретных деловых или технических потребностей, таких как CQRS, автоматические выключатели, события, асинхронные коммуникации.

1 — https://martinfowler.com/articles/microservices.html
2 — https://www.ben-morris.com/how-big-is-a-microservice/
3 — http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/
4 — https://microservices.io/patterns/ microservice-chassis.html
5 — https://github.com/VaughnVernon/IDDD_Samples/
6 — Прочтите это, чтобы увидеть Полную систему на основе микросервисов