На раннем этапе карьеры мне было достаточно сложно понять чем отличается бизнес логика от логики приложения.
Статьи и книги давали сухую теорию и много противоречивых мнений, но сейчас сообщество как будто приходит к консенсусу в некоторых вопросах.
На одном проекте были Rich Model, хорошо структурированный код, но при этом агрегаты сами себя сохраняли в базу данных, получая доступ к репозиториям, через хитро завёрнутый DI в
Ambient Context.
Таким образом репозитории активно использовались внутри агрегатов, практически реализуя паттерн
ActiveRecord. Это работало, но доставляло много проблем, поэтому там вообще не было тестов.
Опыт с таким кодом еще сильнее размывал для меня границу между бизнес логикой и логикой приложения.
Постараюсь систематизировать этот вопрос.
Бизнес-логика — это правила, ограничения, инварианты, знания, состояния и процессы, описывающие предметную область (т.е. как работает сам бизнес),
примеры правил:
- книга не может быть выдана более чем на N дней
- нельзя брать абоненту больше X книг
- если книга просрочена, то абонементу выписывается штраф
- книгу можно взять только если она свободна
Логика приложения — это UseCases (сценарии использования бизнес логики)
- обработка HTTP-запросов
- обработка сообщений
- работа с базой данных
- кэширование
- обработка ошибок
- аутентификация
- авторизация
- логирование
- трейсинг
Чтобы определить является ли бизнес логикой можно использовать несколько популярных техник:
1. Попробуйте объяснить эту логику бизнес-эксперту без использования технических терминов (агрегат, репозиторий, база данных, транзакция, HTTP статус, очередь и т.п.)
Если получается, то скорее всего это бизнес логика.
2. Если убрать всю инфраструктуру: базы данных, интерфейсы взаимодействия, останется ли эта логика?
Если да, то это бизнес логика.
3. Это правило или шаг сценария?
"книга должна быть доступна" - бизнес правило (Domain)
"получить книгу из БД" - шаг сценария (Application)
Так же есть несколько распространенных маркеров и нюансов:
1. Авторизация и прочая проверка прав - логика приложения.
К примеру, пользователю может быть выдано право на взятие книги из библиотеки. И оно будет просто проверять наличие такого права - это логика приложения.
А могут быть и специфичные для предметной области правила и ограничения:
- книга уже взята кем-то другим
- превышен лимит по количеству книг
- есть ещё не возвращенные в срок книги
- не состоит в VIP клубе или нет премиум подписки
это уже домен
2. Использование HTTP client, Repository и прочего - логика приложения.
Бизнес логика при этом обычно pure function, без I/O операций.
3. Валидация входных данных
Есть логика приложения: техническая валидация, когда мы хотим убедиться что нам корректно передали и мы корректно десериализовали:
BookId != Guid.Empty, это логика приложения.
А есть бизнес правила: книгу могут взять только люди старше 18 лет
В идеале вся бизнес логика должна быть сосредоточена в доменной модели (агрегаты, value objects, в сложных случаях в доменных сервисах), а логика приложения в UseCases, которая напоминает оркестрацию:
1. Получение входных данных от инфраструктуры (API controller, Kafka Consumer, etc)
2. Достают агрегаты из базы данных
3. Вызывают методы на агрегатах, передавая необходимые данные
4. Сохраняют изменения в базу данных
5. Возвращают результат наружу в виде DTO, сообщений в кафку и т.п.
для наглядности в BookLibraryПри этом домен не знает ничего о базе данных, очередях или HTTP.
А также не должен обращаться к Ambient Context. Например к DateTime.UtcNow, Guid.NewGuid(), и т.п, вместо этого они должны передаваться в виде параметров.
Если удается выдержать такую чистоту, то будет проще поддерживать код и рефакторить. А чтобы написать тесты на бизнес логику будет достаточно
простых юнит тестов без моков инфраструктуры. При этом они будут работать очень быстро и не будут постоянно флакать.
В тоже время логика приложения почти всегда подразумевает
интеграционные тесты с настоящей инфраструктурой. Они будут намного медленней, но зато будут проверять наши сценарии вместе с бизнес логикой.