Написание чистого, поддерживаемого и тестируемого Api на Go

Мотивация.

Недавно я закончил читать эту замечательную книгу о чистой архитектуре под названием Запачкни руки о чистой архитектуре Тома Хомбергса. Эта книга была для меня недостающим звеном между блогом дяди Боба о чистой архитектуре и реальным воплощением этих идей в коде. Эта книга краткая и полна примеров кода для каждой концепции. Я постоянно практиковал методы, описанные в книге, и до сих пор учусь на них. Книга и связанные с ней примеры относятся к Java / Springboot, но основные принципы не зависят от языка. Это побудило меня попробовать методы, упомянутые в книге, и применить их для создания простого API с использованием GoLang. Вот GitRepo для справки.

Чистая архитектура.

Как упоминалось в блоге Чистая архитектура дядюшки Боба, разные чистые архитектуры имеют один и тот же основной принцип: Разделение проблем. То есть изолировать один уровень кода от другого таким образом, чтобы изменения на одном уровне не влияли на другой уровень. Упрощенная диаграмма чистой архитектуры, приведенная выше, показывает поток управления. Управление кодом перемещается с внешних уровней внешней структуры (например, БД, HTTP и т. Д.) На внутренние уровни контроллеров. Контроллеры, в свою очередь, вызывают базовые варианты использования, в которых кодируются бизнес-правила для конкретных приложений. Наконец, поток достигает объектов домена, в которых закодированы корпоративные бизнес-правила. Здесь важно отметить центральное положение, которое занимают объекты домена. Поскольку объекты домена и варианты использования являются самыми внутренними уровнями, содержащими фактические бизнес-правила, на них не должны влиять изменения самых внешних уровней. Например, переход на новую базу данных или изменения пользовательского интерфейса. Проще говоря, внутренние слои не могут иметь ссылки или зависеть от внешних слоев. Но как получить доступ к слоям БД (внешний слой) из прецедентов (внутренний уровень), когда нам нужно заполнить данные в Entities? Мы вернемся к этому чуть позже. Читать дальше.

Шестиугольная архитектура.

Одной из таких реализаций чистой архитектуры является гексагональная архитектура.

Как видно из диаграммы выше, изоляция домена (вариантов использования и сущностей) достигается за счет инверсии зависимостей. Инверсия зависимости происходит на портах ввода и вывода, как показано выше. Порты представляют собой интерфейсы с определенными функциями. Случаи использования (уровень обслуживания) реализуют входные порты. Контроллер фактически общается с портом ввода и не имеет представления об уровне обслуживания и сценарии использования, который его реализует. Точно так же проблема доступа к уровню БД из вариантов использования также решается здесь путем разговора с портами вывода. Порты вывода реализованы внешними адаптерами. Давайте теперь поместим эти знания в код.

Постановка задачи.

Зная об архитектуре кода, давайте попробуем реализовать простой вариант использования. Давайте создадим api, который возвращает данные о сотрудниках, а также вычисляет общую оплату из базовых данных и данных о бонусах, возвращаемых из БД. Мы начнем с In-Memory DB (больше похожей на карту в памяти) и пакета Go net / http для создания api. Как только мы закончим, и если мы правильно следовали теории, мы попытаемся поменять БД на Postgres и использовать gorilla / mux для api, не влияя на базовый код домена. Давай начнем.

TDD и домен.

Поскольку код домена изолирован от внешних фреймворков и уровней, мы можем и должны покрывать код этого уровня модульными тестами и добиваться высокого покрытия кода. Например, для объекта "сотрудник" мы начинаем с модульного теста для расчета общей заработной платы.

При написании неудачного теста мы напишем код, чтобы он прошел.

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

TDD и приложение.

Как и на уровне домена, мы можем и должны добавлять модульные тесты для кода на уровне сервиса (варианты использования). Поскольку в сценариях использования могут использоваться выходные порты, это хорошее место, чтобы смоделировать их в тестах и ​​сосредоточиться на тестировании логики приложения. Давайте напишем образец теста для GetEmployeeDetailsUseCase.

Здесь следует отметить несколько важных моментов: на данный момент мы фактически не взаимодействуем с БД. Как видно из теста, мы можем легко смоделировать LoadEmployeeDataPort (выходной порт) и протестировать только логику приложения, содержащуюся в службе. Давайте посмотрим на сервис и посмотрим, как он реализует входной порт (вариант использования) и использует выходные порты для получения данных.

Две функции GetEmployeeDetails и GetAllEmployees объявлены в интерфейсе GetEmployeeDetailsQuery. Поскольку служба определяет эти две функции, она неявно реализует порт ввода, который указан ниже.

Интеграционные тесты и адаптеры.

Теперь, когда мы написали наши внутренние уровни домена и приложения и покрыли их модульными тестами, пришло время переместить уровень выше. Мы достигаем уровня, на котором находятся адаптеры. Адаптеры бывают двух типов: адаптеры драйверов (запускают управление потоком на уровне внутреннего домена) и управляемые адаптеры (запускаются, как правило, внутренними уровнями). В нашем случае простой контроллер - это управляющий адаптер, а постоянный адаптер - это управляемый адаптер. Но самое главное, какое тестирование мы должны здесь проводить. Поскольку мы рассмотрели базовую логику предметной области с помощью модульных тестов, имеет смысл написать несколько интеграционных тестов на этом уровне, чтобы увидеть, работают ли взаимодействия должным образом. Например, в случае веб-контроллеров мы можем захотеть проверить, действительно ли базовая служба запускается и отправляется ли правильный ответ. Точно так же в случае с БД мы действительно хотим попасть в БД и посмотреть, работают ли взаимодействия. Я использовал docker compose, чтобы запустить контейнер postgres для тестирования интеграции PersistenceAdapter. Для тестирования веб-слоя использовался пакет Go net/http/httptest.

Подключите все это.

Теперь, когда мы определили все фрагменты кода, нам нужно их соединить. Обычно лучший способ сделать это - использовать заводской узор. Для простоты и абсолютной лени я использовал main.go файл, чтобы связать все части вместе.

Обратите внимание на БД. Называть это БД InMem - это излишне :). Это простая карта данных о сотрудниках.

Давайте запустим api и посмотрим, получаем ли мы ожидаемые результаты:

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

Момент истины.

Когда проводка работает правильно и поток реализован, самое время применить на практике то, что мы пытались достичь в первую очередь. Поменяйте БД на Postgres, а маршрутизатор на gorilla / mux и посмотрите, как это повлияет на наш код.

Сначала мы меняем БД

Во-вторых меняем роутер

Теперь давайте перезапустим api с изменениями и снова обратимся к конечным точкам. Я вставил другой набор пользователей в Postgres, чтобы различать результаты, полученные из карты памяти.

И результаты из api. ТАДА !!! :

Вызов API работает нормально. Все, что нам нужно было сделать, это перепрограммировать файл main.go и добавить код репозитория postgres, реализующий интерфейс репозитория. Никаких изменений кода на уровне адаптера, службы или домена.

Заключение.

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

Использованная литература.





Гексагональная архитектура с Java и Spring
Термин« гексагональная архитектура
существует уже давно. Достаточно долго, чтобы первоисточник по этой теме… correctoring.io »