В прошлой своей статье я вскользь упомянул Onion архитектуру. Там было в основном про то что не надо лишние библиотеки и папки создавать, что надо делить код на модули и что можно писать код прямо в контроллер и использовать Entity Framework там же напрямую НО Домен все равно должен быть отдельно и все вычисления должны быть в нем. Теперь хочу поговорить о луковой архитектуре. Больше всего подходящей для сложного приложения с большим количеством логики. В этой статье в качестве примера я буду использовать игру "Змейка" потому что в ней достаточно логики для «богатого» домена

Легенда


  1. Красным цветом указана зависимости.
  2. Зеленым цветом указаны важные группы. Под — слои
  3. Синим цветом разделены основные слоим которые желательно держать отдельно


Терминология


  1. Анемичная модель: Модель данных без логики.
  2. ValueObject: Неизменяемое хранилище данных без уникального идентификатор
  3. Entity: Объект c уникальным идентификатором
  4. Aggregate: Объект содержащий в себе Entity. Обладает уникальным идентификатором.
  5. Data Layer — Repository: Изолирует нас от того что мы используем. Хранилище наших Aggregate
  6. UseCase/Interactor: Изолирует нас от того что использует нас. Например от ASP.NET, gRPC, WCF, WPF, WinForms. Может вызывать Repository когда это нужно. Сам почти ничего не делает и не хранит. Тут должна быть минимальная логика. Только использует Entity, Aggregate, ValueObject и Repository. В Анемичной модели наоборот делает все, тут вся логика.
  7. DomainEvent: Событие которое случилось в Домене и на которое внешний мир должен как-то среагировать. Можно сделать просто булево свойство или коллекцию с объектами у вашего домена в которую добавлять события. Обрабатывать их можно в ApplicationService/UseCase/Interactor.


Суть


Union aka Clean это про то что у вас должен быть отдельно:

  1. Класс, который делает вычисления или является объектным представление какой-то сущности в приложении. Calculator и ResultValueObject
  2. Класс, который пишет данные в хранилище данных или читает их оттуда. CalculatorRepository
  3. Класс, который реагируют на действия пользователя. CalculatorService
  4. Calculator и ResultValueObject должны быть независимы от CalculatorRepository и CalculatorService


Пример: Игра змейка


github.com/VictoremWinbringer/SnakeGameWithOnionAkaCleanArchitecture

Литература


  1. Domain Driven Design – simplifying the complicated
  2. Заблуждения Clean Architecture


UPD


В комментариях пожаловались что картинка у меня кривая поэтому добавил еще одну.
Тут замечание: Gateway == Repository

Комментарии (6)


  1. yarosroman
    04.05.2019 03:23
    +2

    Как написать операционную систему. github.com/torvalds/linux


  1. TimurNes
    04.05.2019 07:02

    Теперь хочу поговорить о луковой архитектуре.
    15 коротких предложений «на поговорить» про луковую архитектуру — это, конечно, сильно.

    DomainServices: Изолирует нас от того что мы используем. Например от ADO.NET, NLogger, Redis, RabbitMQ, AppMetrics, ElasticSearch
    Доменные сервисы содержат доменную логику, а не изолирует от того, что мы используем. Домен максимум знает про интерфес IUserRepository. Сам репозиторий, как имплементированный сервис — это инфраструктурный слой, но уж никак не доменный.


    1. VanquisherWinbringer Автор
      04.05.2019 12:19
      -1

      del


  1. Wyrd
    04.05.2019 09:00

    Корявость картинки намекает на корявость архитектуры? Или нет?


    1. Rikkitik
      04.05.2019 11:14
      +1

      Корявость картинки и самого текста, видимо, должна проиллюстрировать степень серьёзности подхода автора к проблеме. И, надо сказать, иллюстрирует отлично.


  1. Ascar
    05.05.2019 15:05

    Вот предположим то что вы написали про слоеность архитектуры имеет хоть какой то смысл. Смотрим проект и видим что у вас абсолютно все лежит в Program.cs. И первое апреля уже прошло.