чистая архитектура на практике
Чистая архитектура. Часть I — Введение
Вступительное слово
Программная архитектура немного напоминает строительную архитектуру. В зданиях тоже есть фрактальная структура: здания состоят из отсеков, отсеки состоят из комнат, комнаты — из стен, стены — из кирпичей. Программы же состоят из модулей, которые состоят из пакетов, которые состоят из классов, которые состоят из функций и т.д. Однако разнообразие структур программ намного шире, чем зданий, поэтому Дядя Боб считает программную архитектуру «более архитектурной», чем строительную архитектуру.
Кроме того, строительную архитектуру проще визуализировать в голове, чем программную, потому что здания мы видим каждый день, а архитектура программ от нас скрыта внутри исходного кода. Программная архитектура не похожа ни на что.
С другой стороны, программная архитектура тоже должна помнить про физические ограничения, потому что программы запускаются на реальном железе с ограниченной производительностью, памятью, пропускной способностью сети и т.д.
Что такое хорошая архитектура? Это такая архитектура, которая удовлетворяет потребностям пользователей, владельцев бизнеса и программистов не только в текущий момент времени, но и со временем.
«Если вы считаете, что хорошая архитектура – это дорого, то попробуйте плохую».
«Единственный способ идти быстро – это идти хорошо».
Предисловие
Дядя Боб пишет код более 50 лет. Он писал самые разнообразные программы: большие, маленькие, GUI, консольные, веб и т.д. И пришёл к выводу, что правила архитектуры везде одни и те же! И даже за 50 лет они не изменились несмотря на гигантский рост производительности железа.
Языки программирования тоже стали значительно лучше, но базовые конструкции остались теми же самыми: if, присваивание, циклы и т.д. Если посадить программистку из середины прошлого века за современный МакБук, то она сначала офигеет, но потом будет нормально писать Java-код в Идее.
Но за полвека был накоплен большой опыт, которого ещё не было 50 лет назад. Были сформулированы правила, которым и посвящена данная книга.
Введение
Написать рабочую программу нетрудно. Трудно написать программу правильно. Это требует знаний, опыта и умений, для выработки которых нужно большое время и дисциплина.
Когда программа сделана правильно, то её легко поддерживать и вносить в неё изменения. Там очень низкий процент дефектов. Для неё не нужны орды программистов, тонны документов с требованиями и навороченные трекеры.
Это звучит утопично, но Дяде Бобу удавалось работать в таких проектах. Но к сожалению, в большинстве случаев нам приходится работать в условиях ужасного дизайна.
Что такое дизайн и архитектура?
Давайте считать, что дизайн и архитектура – это одно и то же.
Архитектура – это не только верхний уровень, это верхнеуровневые и низкоуровневые детали в целом. Одного без другого не существует.
Цель программной архитектуры – минимизировать человеческие ресурсы, необходимые для построения и поддержки требуемой системы.
Если усилия невелики и остаются таковыми в течение всей жизни, значит дизайн хороший. Если усилия возрастают с каждым релизом, то дизайн плохой.
40 раз. Т.е. продуктивность упала со 100% практически до нуля. Программисты работают усердно во всю силу, но по сути ничего не могут сделать. А теперь самое страшное: если в первом релизе приходилось платить сотрудникам несколько сот тысяч долларов в месяц, то в конце эта цифра стала 20 миллионов долларов в месяц!
Далее Дядя Боб вспоминает про притчу про зайца и черепаху. В ней черепаха выиграла гонку, потому что заяц был слишком самоуверенный, лёг спать и проспал всю гонку.
То же самое и с разработкой: программист тешит себя уверенностью, что он сможет по-быстрому выкатить продукт, а навести порядок позже. Однако проблема в том, что после релиза ему нужно делать следующую фичу, иначе он отстанет от конкурентов на рынке. В конце концов его продуктивность через несколько месяцев просто упадёт до нуля и он проиграет.
Далее он приводит пример эксперимента, проведённого неким Джейсоном Горменом, где он пишет простую программу несколько раз. И у него получается, что c TDD первый раз получается быстрее, чем даже третий раз без TDD. То есть если писать правильно, то в итоге получается быстрее.
Разработчик должен нести ответственность за беспорядок, который он привносит в проект. К качеству архитектуры нужно относиться серьёзно. Но для этого нужно знать, что такое хорошая архитектура.
Рассказ о двух ценностях
Есть две ценности – поведение и архитектура.
Поведение означает, что программа удовлетворяет функциональным требованиям и тем самым приносит деньги владельцам бизнеса. Проблема в том, что многие программисты верят, что только это и есть их работа.
Вторая ценность – это качество, при котором программа остаётся в поддерживаемом состоянии. То есть её легко изменять. Сложность изменений должна быть пропорциональна масштабу этих изменений, но не их форме. Архитектура не должна делать предпочтений одних форм по отношению к другим, иначе запросы владельцев будет реализовать сложнее и сложнее с каждым разом.
Что важнее – поведение или архитектура? Менеджеры считают, что важнее, чтобы система работала. Но программисты должны считать наоборот, что важнее архитектура.
Матрица Эйзенхауэра говорит, что важное и несрочное имеет более высокий приоритет, чем срочное и неважное. А архитектура всегда важна в отличие от поведения, поэтому она более приоритетна.
Программист должен бороться за архитектуру. Программист – это тоже участник бизнеса. Архитектура – это его ответственность.
Чистая Архитектура на Golang
Oct 19, 2019 · 8 min read
UPD: Советую читать слегка оновленную версию статьи на моем сайте.
Мне приходилось сталкиваться с разными подходами построения архитектуры приложений за время работы в сфере разработки. Не так давно я познакомился с принципами Чистой Архитектуры от Дяди Боба, и успешно следую им в работе над моими проектами. В данной статье я хочу поделиться с вами подходом к структуризации проекта на Golang, следуя данной концепции. Надеюсь, этот материал будет полезен не только начинающим разработчикам, но и опытные специалисты смогут для себя подчеркнуть что-то новое.
Если вы еще не зна к омы с данной архитектурой, для начала вам стоит прочитать статью, в которой описаны основные концепции. Обратите внимание на Правило Зависимостей ( Dependency Rule). Это, наверное, ключевое правило при проектировании архитектуры современного приложения. Следование данному принципу поможет вам получить систему, части которой легко тестируются, независимы от фреймворков, БД и протокола коммуникации с внешним миром (HTTP, RPC, etc). А самой системе будет свойственны понятия разделения ответственности и слабая связность компонентов.
Такие слабосвязанные системы позволяют нам без особого труда их масштабировать, модифицировать и дополнять их функционал. Дают возможность менять какие либо инфраструктурные компоненты (например БД) или фреймворки, не задевая при этом других частей приложения.
Также, если вы не знакомы с таким понятиям как Внедрение Зависимостей ( Dependency Injection), рекомендую для начала с ним ознакомиться чтобы лучше понять реализацию данной архитектуры в нашем проекте.
Демо проект
Для изучения данного подхода на практике мы с вами рассмотрим пример создания простого RESTful API. Оно предоставляет пользователю возможность создавать, просматривать и удалять закладки. В приложении используется собственная Sign Up/Sign In система на основе JWT токенов.
Запускать наше приложение будем в докер контейнере, для баз данных я выбрал MongoDB, в качестве HTTP Web фреймворка будем использовать Gin.
Конечно, вы можете использовать другие фреймворки или БД, лично для меня эти инструменты помогают достаточно быстро поднять рабочие приложения.
С самим проектом вы можете ознакомится в данном репозитории.
Структура приложения
На начальном этапе необходимо определить сущности и бизнес правила. В нашем приложении есть пользователи, которые могут создавать, удалять и просматривать уже созданные закладки.
Тут важно понимать, что вне зависимости от платформы, будь то мобайл, веб или консольный интерфейс (cli), у нас всегда есть пользователи, которые умеют создавать, удалять и просматривать закладки. Мы не должны привязываться к конкретному UI или платформе, бизнес правила остаются теми же.
На верхнем уровне директории проекта мы имеем 6 пакетов: auth, bookmark, cmd, config, models, server. Что такое cmd и config думаю понятно сразу, давайте подробнее остановимся на остальных.
В auth у нас реализован Sign Up/Sign In. В пакете bookmark мы имеем логику работы с закладками. В models у нас хранятся сущности, а в server реализован HTTP сервер.
Поскольку наше приложение — монолит, мы имеем несколько независимых компонентов внутри проекта, которые разделены по зонам ответственности.
Давайте рассмотрим auth и bookmark. В данных пакетах инкапсулирована логика регистрации/авторизации и работы с закладками соответственно. При построении монолита, перед тем как вынести какую нибудь часть функционала в отдельный пакет на самом верхнем уровне, я задаю себе вопрос: “ Можно ли реализовать данный функционал как микросервис?”. Если ответ “Да”, тогда это точно отдельный пакет.
Для каждой из этих систем нам необходимо иметь сущность ( Model/Entity), хранилище ( Repository), бизнес логику ( Usecase) и также транспортный слой ( Delivery), в данном случае это эндпоинты нашего API.
Структуры пакетов auth и bookmark идентичны. На верхнем уровне мы имеем файлы repository.go и usecase.go, в которых определены интерфейсы репозитория и объекта, реализующего бизнес логику, соответственно. Также здесь расположены пакеты с идентичными названиями, в которых находятся имплементации этих интерфейсов, и отдельный пакет delivery.
Что ж, меньше текста больше кода!
Система регистрации
Прежде чем начать разбирать подробнее систему регистрации, давайте ознакомимся с сущностью пользователя ( User).
Обратите внимание на поле ID ( string). В MongoDB наши обьекты будут иметь ID типа UUID, а используя, например, Postgres в качестве главного хранилища, мы бы использовали целочисленное значение. Используя string, мы не привязываемся к конкретному хранилищу, и всегда можем конвертировать uuid или int в строку.
Пожалуй начнем разбираться с кодом с системы регистрации. Для начала, давайте определим сценарий работы системы.
При регистрации, мы должны сохранить пользователя у нас в БД, в отдельной таблице (коллекции, выражаясь терминами MongoDB). К паролю нужно добавлять соль и хранить в хешированном виде. При авторизации нам нужно найти пользователя в БД по логину+паролю и сгенерировать токен авторизации. При последующих запросах к приложению, мы должны валидировать наличие и корректность данного токена.
Давайте опишем интерфейс нашего репозитория, основываясь на требованиях выше.
И также опишем интерфейс Usecase, который должен описывать сценарий бизнес-логики данного функционала.
Итак, мы с вами только что определили основные сценарии работы системы регистрации/авторизации и описали абстракции для хранилища и бизнес логики. Давайте приступим к их реализации!
Репозиторий
Как вы можете увидеть, в пакете repository мы имеем еще 3 подпакета с названиями, которые говорят сами за себя.
У нас есть реализация интерфейса репозитория на Mongo, также с помощью in-memory кеша ( localstorage), и мок (для unit-тестирования обьектов, которые депендятся на наш репозиторий). Подробнее тему unit-тестирования и моков мы рассмотрим далее.
Хочу обратить ваше внимание, что в имплементации для MongoDB мы описали структуру пользователя, в которой поле ID имеет нужный формат, а также мы имеем теги bson. Эта структура служит прослойкой для работы с нашей структурой models.User в монге. Для конвертации структур напишем следующие функции:
Ну а далее давайте создадим сам объект репозитория, реализующего методы нашего интерфейса.
Итак, в этом пакете мы имплементировали работу нашего репозитория, работая с MongoDB.
Время рассмотреть бизнес-логику в имплементации Usecase!
UseCase
В пакете usecase находится одноименный файл usecase.go, а также реализация мока и покрытие unit-тестами.
Давайте посмотрим на структуру, которая реализует наш интерфейс.
Как видите, поскольку мы должны работать с хранилищем для сохранения и получения пользователя, наша структура имеет поле репозитория. Однако, обратите внимание, что мы принимаем в качестве репозитория интерфейс auth.UserRepository, а не конкретный обьект, например *mongo.UserRepository, реализующий интерфейс. Здесь мы используем принцип Dependency Injection, который я упоминал в начале. В этом кроется вся сила Правила Зависимостей.
Мы реализовываем бизнес логику Sign Up’a, а именно: хешируем пароль+соль, создаем обьект типа models.User, и сохраняем его в хранилище. При этом нам абсолютно не важно, как под капотом работает userRepo.
Слой Delivery и HTTP сервер
Итак, мы рассмотрели с вами описание моделей, абстракций слоев Repository и Usecase, а также их реализацию. При этом, мы не привязывались к протоколу коммуникации с внешним миром, мы даже не задумывались об этом. Давайте теперь рассмотрим эндпоинты нашего приложения, которые позволят пользователю зарегистрироваться и авторизоваться на нашем сервисе. У нас есть 2 эндпоинта:
В пакете delivery мы имеем подпакет http, который реализует транспортный слой модуля auth средствами http протокола.
Обратите внимание на структуру обработчика ( Handler), который имеет метод-хендлер на каждый эндпоинт.
Единственное поле, которое имеет данная структура, это usecase типа auth.UseCase, т.е. интерфейс который мы описали на самом верхнем уровне пакета auth. Сейчас, вы возможно задались вопросом, зачем нам тут принимать интерфейс, а не сам обьект-реализацию. Ведь имплементация UseCase’а у нас одна, и других точно не будет. А не как в случае с репозиторием, где есть место абсолютно разным типам хранилищ. Ответ на этот вопрос довольно таки прост — unit-тестирование. При тестировании наших методов-хендлеров, мы с легкостью можем передать в конструктор NewHanlder мок, реализующий интерфейс UseCase и сконцентрировать свое внимание лишь на тестировании кода нашего хендлера.
В хендлере SignUp, все что мы делаем, это парсим тело запроса и вызываем метод SignUp нашего Usecase с последующей обработкой ошибок. Опять же, нам не важно как под капотом работает сайн ап, хеширует ли он пароли, добавляет ли соль, куда сохраняет пользователей. Это детали реализации, которые не важны нам на данном этапе. Давайте рассмотрим unit-тест, покрывающий данный хендлер.
Как вы можете увидеть, мы с легкостью тестируем код нашего хендлера в изоляции от остальных частей. А добились мы этого благодаря использованию мока в качестве usecase’a при Внедрении Зависимостей.
Итак, мы детально рассмотрели все слои нашего модуля auth, давайте теперь поднимем HTTP сервер, чтобы обслуживать эндпоинты, про которые мы говорили выше.
Здесь я предпочитаю создавать отдельный пакет server на верхнем уровне, в котором реализовывать структуру нашего приложения. Она должна инкапсулировать необходимые зависимости, такие как репозиторий, usecase, http-сервер и т.д.
Как вы можете увидеть, в конструкторе NewApp, мы создаем новый инстанс обьекта AuthUseCase, и в качестве аргумента передаем Mongo-репозиторий. А что если мы захотим в будущем перейти на PostgresQL? Мы реализуем новый подпакет postgres в auth/repository, по аналогии с mongo. А в конструкторе приложения поменяем инициализацию mongo-репозитория на postgres-репозиторий. И все. Чувствуете всю гибкость и слабую связность данного подхода?
Давайте напишем метод запуска нашего приложения.
Здесь мы создаем наш роутер, регистрируем эндпоинты, инициализируем http-сервер, начинаем слушать на указанном порту. Также мы добавили graceful-shutdown.
Что значит регистрируем наши эндпоинты? Давайте вернемся в слой delivery пакета auth и найдем там файлик register.go.
Здесь мы принимаем в качестве аргументов роутер, и обьект, реализующий интерфейс Usecase. Создаем хендлер, передавая наш полученый обьект и инициализируем эндпоинты.
И давайте перейдем к самому главному, запуск нашего приложения! В пакете cmd/api в файлике main.go напишем следующий код.
Здесь мы инициализируем глобальный конфиг и инициализируем наше приложение. Дальше мы запускаем его с помощью Run() и слушаем ошибки. That’s it!
Подведем итоги
Мы рассмотрели с вами основные методы построения приложений на Golang следуя принципам Чистой Архитектуры. Увидели, как на практике выглядит Dependency Rule ( Правило Зависимостей) и применили Dependency Injection ( Внедрение Зависимостей) для его достижения. Я специально упустил разбор модуля bookmark. Он реализован по аналогичному принципу как и auth, поэтому предлагаю вам ознакомится с ним самостоятельно на этом GitHub-репозитории.
Если вы только начинаете свое знакомство с Go, советую вам эту книгу, который покрывает все основные концепции языка для уверенного старта разработки.
А чтобы не пропускать интересный материал по разработке, подписывайтесь на мой Telegram канал, в котором я делюсь знаниями и опытом.
Реализация “чистой архитектуры” в микросервисах
Сейчас многие проекты используют микросервисную архитектуру. Мы также не стали исключением и вот уже больше 2х лет мы стараемся строить ДБО для юридических лиц в банке с применением микросервисов.
Важность архитектуры микросервиса
Наш проект — это ДБО для юридических лиц. Много разнообразных процессов под капотом и приятный минималистичный интерфейс. Но так было не всегда. Долгое время мы пользовались решением от подрядчика, но в один прекрасный день было принято решение развивать свой продукт.
Начиная проект, было много обсуждений: какой же подход выбрать? как строить нашу новую систему ДБО? Началось все с обсуждений “монолит vs микросервисы”: обсуждали возможные используемые языки программирования, спорили про фреймворки (“использовать ли spring cloud?”, “какой протокол выбрать для общения между микросервисами?”). Данные вопросы, как правило, имеют какое-то ограниченное количество ответов, и мы просто выбираем конкретные подходы и технологии в зависимости от потребностей и возможностей. А ответ на вопрос “Как же писать сами микросервисы?” был не совсем простым.
Многие могут сказать «А зачем разрабатывать общую концепцию архитектуры самого микросервиса? Есть архитектура предприятия и архитектура проекта, и общий вектор развития. Если поставить задачу команде, она ее выполнит, и микросервис будет написан и он будет выполнять свои задачи. Ведь в этом и есть суть микросервисов – независимость». И будут совершенно правы! Но с течением времени команд становятся больше, следовательно — растет количество микросервисов и сотрудников, a старожил меньше. Приходят новые разработчики, которым надо погружаться в проект, некоторые разработчики меняют команды. Также команды с течением времени перестают существовать, но их микросервисы продолжают жить, и в некоторых случаях их надо дорабатывать.
Разрабатывая общую концепцию архитектуры микросервиса, мы оставляем себе большой задел на будущее:
Граница микросервиса
Все, кто работают с микросервисами, прекрасно знают их плюсы и минусы, одним из которых считается возможность быстро заменить старую реализацию на новую. Но насколько мелким должен быть микросервис, чтобы его можно было легко заменить? Где та граница, которая определяет размер микросервиса? Как не сделать мини монолит или наносервис? А еще всегда можно сразу идти в сторону функций, которые выполняют маленькую часть логики и строить бизнес процессы выстраивая очередность вызова таких функций…
Мы решили выделять микросервисы по бизнес доменам (например, микросервис рублевых платежей), а сами микросервисы строить согласно задачам этого домена.
Рассмотрим пример стандартного бизнес процесса для любого банка — “создание платежного поручения”
Можно увидеть, что вроде бы простой запрос клиента является достаточно большим набором операций. Данный сценарий является примерным, некоторые этапы опущены для упрощения, часть этапов происходят на уровне инфраструктурных компонентов и не доходят до основной бизнес-логики в продуктовом сервисе, другая часть операций работает асинхронно. В сухом остатке мы имеем процесс, который в один момент времени может использовать множество соседних сервисов, пользоваться функционалом разных библиотек, реализовывать какую-то логику внутри себя и сохранять данные в разнообразные хранилища.
Взглянув более пристально, можно увидеть, что бизнес-процесс достаточно линеен и в по мере своей работы ему потребуется или получить где-то какие-то данные или как-то обработать те данные, что у него есть, и для этого может потребоваться работа с внешними источниками данных (микросервисы, БД) или логики(библиотеки).
Некоторые микросервисы не подходят под данную концепцию, но количество таких микросервисов в общем процентном соотношении небольшое и составляет около 5%.
Чистая архитектура
Взглянув на разные подходы к организации кода, мы решили попробовать подход “чистой архитектуры”, организовав код в наших микросервисах в виде слоев.
Касательно самой “чистой архитектуры” написана не одна книга, есть много статей и в интернетах и на хабре (статья 1, статья 2), не раз обсуждали ее плюсы и минусы.
Популярная диаграмма которую можно найти по этой теме, была нарисована Бобом Мартиным в его книге “Чистая архитектура”:
Здесь на круговой диаграмме слева в центре видно направление зависимостей между слоями, а скромно в правом углу видно направление потока исполнения.
У данного подхода, как, впрочем, и в любой технологии программирования, имеются плюсы и минусы. Но для нас положительных моментов намного больше, чем отрицательных при использовании данного подхода.
Реализация “чистой архитектуры” в проекте
Мы перерисовали данную диаграмму, опираясь на наш сценарий.
Естественно, на этой схеме отражается один сценарий. Часто бывает так, что микросервис по одной доменной сущности производит больше операций, но, справедливости ради, многие адаптеры могут использоваться повторно.
Для разделения микросервиса на слои можно использовать разные подходы, но мы выбрали деление на модули на уровне сборщика проекта. Реализация на уровне модулей обеспечивает более легкое визуальное восприятие проекта, а также обеспечивает еще один уровень защиты проектов от неправильного использования архитектурного стиля.
По опыту, мы заметили, что при погружении в проект новому разработчику, достаточно ознакомиться с теоретической частью и он уже может легко и быстро ориентироваться практически в любой микросервисе.
Для сборки наших микросервисов на Java мы используем Gradle, поэтому основные слои сформированы в виде набора его модулей:
Сейчас наш проект состоит из модулей, которые или реализуют контракты или используют их. Чтобы эти модули начали работать и решать задачи, нам нужно реализовать внедрение зависимостей и создать точку входа, которая будет запускать все наше приложение. И тут возникает интересный вопрос: в книге дядюшки Боба “Чистая архитектура” есть целые главы, которые рассказывают нам про детали, модели и фреймворки, но мы не строим свою архитектуру вокруг фреймворка или вокруг БД, мы используем их как один из компонентов…
Когда нам нужно сохранить сущность, мы обращаемся к БД, например, для того, чтобы наш сценарий получил в момент исполнения нужные ему реализации контрактов, мы используем фреймворк, который дает нашей архитектуре DI.
Встречаются задачи, когда нужно реализовать микросервис без БД или мы можем отказаться от DI, потому что задача слишком проста и ее быстрее решить в лоб. И если всю работу с БД мы будем осуществлять в модуле “repository”, то где же нам использовать фреймворк, чтобы он приготовил нам весь DI? Вариантов не так и много: либо мы добавляем зависимость в каждый модуль нашего приложения, либо постараемся выделить весь DI в виде отдельного модуля.
Мы выбрали подход с отдельным новым модулем и называем его или “infrastructure” или “application”.
Правда, при введении такого модуля немного нарушается тот принцип, согласно которому все зависимости мы направляем в центр к доменному слою, т.к. у него должен быть доступ до всех классов в приложении.
Добавить слой инфраструктуры в нашу луковицу в виде какого-то слоя не получится, просто нет для него там места, но тут можно взглянуть на все с другого ракурса, и получается, что у нас есть круг “Infrastructure” и на нем находится наша слоеная луковица. Для наглядности попробуем немного раздвинуть слои, чтобы было лучше видно:
Добавим новый модуль и посмотрим на дерево зависимостей от слоя инфраструктуры, чтобы увидеть итоговые зависимости между модулями:
Теперь осталось только добавить сам фреймворк DI. Мы у себя в проекте используем Spring, но это не является обязательным, можно взять любой фреймворк, который реализует DI (например — micronaut).
Как скомпоновать микросервис и где какая часть кода будет — мы уже определились, и стоит взглянуть на бизнес-сценарий еще раз, т.к. там есть еще один интересный момент.
На схеме видно, что проверка права действия может выполняться не в основном сценарии. Это отдельная задача, которая не зависит от того, что будет дальше. Проверку подписи можно было бы вынести в отдельный микросервис, но тут возникает много противоречий при определении границы микросервиса, и мы решили просто добавить еще один слой в нашу архитектуру.
В отдельные слои необходимо выделять этапы, которые могут повторятся в нашем приложении, например — проверка подписи. Данная процедура может происходить при создании, изменении или при подписании документа. Многие основные сценарии сначала запускают более мелкие операции, а затем только основной сценарий. Поэтому нам проще выделить более мелкие операции в небольшие сценарии, разбитые по слоям, чтобы их было удобнее повторно использовать.
Такой подход позволяет упростить для понимания бизнес логику, а также со временем сформируется набор мелких бизнес-кирпичиков, которые можно использовать повторно.
Про код адаптеров, контроллеров и репозиториев особо нечего сказать, т.к. они достаточно простые. В адаптерах для другого микросервиса используется сгенерированный клиент из сваггера, спринговый RestTemplate или Grpc клиент. В репозитариях — одна из вариаций использования Hibernate или других ORM. Контроллеры будут подчиняться библиотеке, которую вы будете использовать.
Заключение
В данной статье, мы хотели показать, зачем мы строим архитектуру микросервиса, какие подходы используем и как развиваемся. Наш проект молодой и находится только в самом начале своего пути, но уже сейчас мы можем выделить основные моменты его развития с точки зрения архитектуры самого микросервиса.
Мы строим многомодульные микросервисы, где к плюсам можно отнести:
Также к минусам можно отнести то, что нужно постоянно контролировать других разработчиков, потому что есть соблазн сделать меньше работы, чем стоит. Например, переместить фреймворк немного дальше, чем один модуль, но это ведет к размыванию ответственности этого фреймворка во всей архитектуре, что в будущем может негативно сказаться на скорости доработок.
Данный подход к реализации микросервисов подходит для проектов с долгим сроком жизни и проектов со сложным поведением. Так как реализации всей инфраструктуры требует время, но в будущем это окупается стабильностью и быстрыми доработками.