February 23, 2023

Слоеный пирог с бизнес логикой, use case и формочкой для выпекания

Луковая, гексагональная, чистая архитектура - все эти архитектуры нацелены на создание модульных и масштабируемых приложений. Все они говорят об одном - приложение необходимо делить на слои и выстраивать правильные зависимости между ними. Существует 3 основных слоя - Domain, Application, Infrastructure.

Правило зависимостей

Существует одно базовое правило, определяющее взаимосвязь между уровнями:

Внутренний слой никогда не должен ссылаться ни на что из внешнего уровня.

Зависимость идет от внешнего слоя к внутреннему

Например, доменный слой не должен зависеть от инфраструктурного слоя, или по другому говоря, бизнес логика не должна зависеть от инструментов: БД не должна определять, как вы доставляете товар клиентам - собственной доставкой или доставкой яндекс.

Другие схемы не смотрите, они не правильные, правильная только моя.

При изучении архитектурных подходов, вы найдете схемы где слоев больше. Выделяют ещё такие слои как Presentation, и Use Cases, но правило продолжает действовать: внутренний слой не должен зависеть от внешнего.

Это важно, потому что благодаря этому правилу, мы придерживаемся принципа ацикличности зависимостей.

Принцип ацикличности зависимостей (ADP)

Принцип гласит - граф зависимостей компонентов не должен иметь циклов, например: если компонент А зависит от компонента Б, а компонент Б зависит от компонента С - то это ациклическая зависимость, но если C будет зависеть от А, то получим замкнутый круг, т.е. циклическая зависимость.

Например, есть кейс “создать пост” - CreatePost зависит от агрегата Post и репозитория PostRepository, при создании поста должно соблюдаться правило уникального слага, есть Value Object - PostSlug, который содержит валидацию и правило по соблюдению уникальности, он в свою очередь ссылается на PostRepository.

В итоге получается цикл зависимостей PostRepository -> Post -> PostSlug -> PostRepository -> Post -> … .

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

Разрыв цикла

Первое, что нужно сделать - это переосмыслить свои слои, вероятно, вы не соблюдаете правила зависимостей. Второе, использовать принцип инверсии зависимостей (буква D из SOLID): в этом случае можно создать интерфейс, определяющие необходимые методы для PostSlug, например, IUniqSlug::isSatisfiedBy, и реализовать этот интерфейс в инфраструктуре.

Слои и правила зависимостей

Domain Layer

Доменный слой - ядро приложения/модуля, содержит бизнес логику, агрегаты, value objects, сущности, доменные события и сервисы, а также допускает хранить интерфейсы репозитория для агрегата.

Любая часть приложения/модуля может зависеть от доменного слоя, от агрегатов, сущностей, value object, сервисов. Но объекты домена, не должны зависеть от объектов из других слоев. Нельзя ссылаться на Application и Infrastructure слои из доменного слоя.

Доменный слой самый стабильный слой, все остальные слои зависят от него, и он не должен иметь зависимостей, чтобы не создавать циклы зависимостей.

Application Layer

Application слой задает поведение модулю, обрабатывает бизнес-процессы, содержит пользовательский сценарии (use cases), сервисы приложения, модели представления (view model), различные обработчики, политики и т.д.

Слой приложения зависит от нижестоящего Domain слоя, но также должен иметь возможность взаимодействовать с инфраструктурным слоем, например, для работы с репозиторием.

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

Исправить это можно с помощью принципа инверсий зависимостей (Dependency inversion principle), вспоминаем SOLID.

Для этого необходимо выделить интерфейс репозитория в слое приложения (или домене), на который будет ссылаться Application слой, а его реализация будет в инфраструктурном слое.

Infrastructure Layer

Инфраструктурный слой реализует методы доступа к внешним сервисам, таким как БД (postgresql, redis, etc.), к системе кеширования (memcache, redis), к внешним API (dadata, strapi, sber etc), http контроллеры, системы обмена сообщениями и т.д.

Может зависеть от любого слоя приложения, таких как Application и Domain.

Данный слой самый нестабильный и может чаще всего меняться.




Ваше приложение в ваших руках 😉




Полезное