В прошлой своей статье я вскользь упомянул Onion архитектуру. Там было в основном про то что не надо лишние библиотеки и папки создавать, что надо делить код на модули и что можно писать код прямо в контроллер и использовать Entity Framework там же напрямую НО Домен все равно должен быть отдельно и все вычисления должны быть в нем. Теперь хочу поговорить о луковой архитектуре. Больше всего подходящей для сложного приложения с большим количеством логики. В этой статье в качестве примера я буду использовать игру "Змейка" потому что в ней достаточно логики для «богатого» домена
Легенда
- Красным цветом указана зависимости.
- Зеленым цветом указаны важные группы. Под — слои
- Синим цветом разделены основные слоим которые желательно держать отдельно
Терминология
- Анемичная модель: Модель данных без логики.
- ValueObject: Неизменяемое хранилище данных без уникального идентификатор
- Entity: Объект c уникальным идентификатором
- Aggregate: Объект содержащий в себе Entity. Обладает уникальным идентификатором.
- Data Layer — Repository: Изолирует нас от того что мы используем. Хранилище наших Aggregate
- UseCase/Interactor: Изолирует нас от того что использует нас. Например от ASP.NET, gRPC, WCF, WPF, WinForms. Может вызывать Repository когда это нужно. Сам почти ничего не делает и не хранит. Тут должна быть минимальная логика. Только использует Entity, Aggregate, ValueObject и Repository. В Анемичной модели наоборот делает все, тут вся логика.
- DomainEvent: Событие которое случилось в Домене и на которое внешний мир должен как-то среагировать. Можно сделать просто булево свойство или коллекцию с объектами у вашего домена в которую добавлять события. Обрабатывать их можно в ApplicationService/UseCase/Interactor.
Суть
Union aka Clean это про то что у вас должен быть отдельно:
- Класс, который делает вычисления или является объектным представление какой-то сущности в приложении. Calculator и ResultValueObject
- Класс, который пишет данные в хранилище данных или читает их оттуда. CalculatorRepository
- Класс, который реагируют на действия пользователя. CalculatorService
- Calculator и ResultValueObject должны быть независимы от CalculatorRepository и CalculatorService
Пример: Игра змейка
github.com/VictoremWinbringer/SnakeGameWithOnionAkaCleanArchitecture
Литература
UPD
В комментариях пожаловались что картинка у меня кривая поэтому добавил еще одну.
Тут замечание: Gateway == Repository
Комментарии (6)
TimurNes
04.05.2019 07:02Теперь хочу поговорить о луковой архитектуре.
15 коротких предложений «на поговорить» про луковую архитектуру — это, конечно, сильно.
DomainServices: Изолирует нас от того что мы используем. Например от ADO.NET, NLogger, Redis, RabbitMQ, AppMetrics, ElasticSearch
Доменные сервисы содержат доменную логику, а не изолирует от того, что мы используем. Домен максимум знает про интерфес IUserRepository. Сам репозиторий, как имплементированный сервис — это инфраструктурный слой, но уж никак не доменный.
Ascar
05.05.2019 15:05Вот предположим то что вы написали про слоеность архитектуры имеет хоть какой то смысл. Смотрим проект и видим что у вас абсолютно все лежит в Program.cs. И первое апреля уже прошло.
yarosroman
Как написать операционную систему. github.com/torvalds/linux