Привет, Хабр. Хочу рассказать о том куда, на рисунке выше, поместить репозиторий. Еще немного о DIP, IoC и структуре проекта. Есть стандартный подход согласно которому интерфейсы IRepository надо помешать в доменный слой к DomainServices а реализацию в Infrastructure. Тут речь именно о стандартных Repository которые просто абстрактная коллекция объектов не знающая про транзакции и UnitOfWork и прочее (например IList и его генерик версия типичный интерфейс абстрактного репозитория). Я лично с ним не согласен потому что обычно репозитории обрастают поведением характерным для конкретного приложения поэтому я помещают их интерфейсы к ApplicationServices. Реализация конечно остается в слое Infrastructure. Если вам интересно, то добро пожаловать под кат.
Часто многих путает старая картинка от Роберта Мартина:
У него же в статьи есть слова
Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.
Поэтому выходит что он просто относит DomainServices к слою Entities. Ну а UseCases == ApplicationServices. Gateways == UserPepository, EmailService, Logger. Подробнее можно почитать тут.
- Serivice это любой класс без состояния. InfrastructureService это анти коррапшн лаер который изолирует вас от файловой системы, библиотеки для работы c SMTP ну и конечно от ORM или библиотеки для работы с Базой Данных и т. п.
- Repository это паттерн. Его теоретически может реализовать любой класс в любом слое. Например типичный List это Repository который использует в качестве хранилища массив T[] и по сути является ValueObject поэтому относиться к слою Entities. Не каждый класс реализующий паттерн Repository является InfrastructureService и не все InfrastructureService реализуют паттерн Repository. Например: Logger, EmailSender.
- Repository работающий с БД это InfrastructureService так же как какой-нибудь EmailSender или EmailService, FileService (обертка над файловой системой который внутри вызывает File.Open(… ) и т. п.) и прочее в этом духе.
Чтобы понять куда помещать абстракции надо вспомнить про Dependency Inversion и Inversion of Control. Согласно им наш ApplicationService может использовать Infrastructure не знаю про том какая у него реализация такими способами как:
- Определять интерфейс, который будет реализовать Infrastructure и взаимодействовать с ним.
- Просто принимать делегаты вроде Action и Func.
Через интерфейсы:
interface IRepository
{
int Get();
}
class ApplicationService
{
private readonly IRepository _repository;
public ApplicationService(IRepository repository)
{
_repository = repository;
}
public int GetInt() => _repository.Get();
}
Через делегаты:
class ApplicationService
{
private readonly Func<int> _getIntFromDb;
public ApplicationService(Func<int> getIntFromDb)
{
_getIntFromDb = getIntFromDb;
}
public int GetInt() => _getIntFromDb();
}
Теперь о том как структурировать код. Тут надо рассмотреть два случая:
- Все разбито по папкам.
- Все разбито по библиотекам.
Подробно о терминологии можно почитать тут.
По папкам в пределах одного приложения
Пример кода SnakeGame
По библиотекам
Помещаем интерфейсы InfrastructureServices (IRepository) в библиотеку ApplicationServices, а реализацию (Repository) в библиотеку Infrastructure.
Конечная картинка из статьи человека с намного большим опытом разработки чем у меня и так же считающим что IRepository нужно помещать в Application Layer к ApplicationServices (сами реализации Repository у него тоже в Infrastructure).
Выводы
Интерфейс инфраструктурных сервисов (IRepository, IEmailSender) можно поместить в библиотеку ApplicationServices.dll, а их конкретную реализацию (Repository, EMailSender) в библиотеку Infrastructure.dll.
LabEG
stackoverflow.com/questions/2268699/domain-driven-design-domain-service-application-service
www.bennadel.com/blog/2385-application-services-vs-infrastructure-services-vs-domain-services.htm
Судя по двум этим ссылкам:
Application Services — как раз сервис для логики и общения с репозиторием в рамках одного домена.
Domain Services — сервис в котором пересекается логика из разных доменов.
Infrastructure Services — сервис для второстепенной логики, не являющейся основным бизнесом, отправить почту, аналитику, логи.
Все три могут работать с репозиториями, инфраструктурный не работает с доменными репозиториями.
VanquisherWinbringer Автор
Тут надо вот одну важную вешь сказать — Repository это паттерн для работы с коллекциями обьектов. Например List реализует паттерн Repository и использует в качестве своего хранилища массив T[]. Такой Repository конечно не является InfrastructureService. Вот тот репозиторий что пишет в БД(Redis, MongoDb,MS SQL) или в Файл или по сети в апи данные отправляет этот как раз типичны InfrastructureService. Да даже какой нибудь Entity в слое Entities может реализовать паттерн репозиторий как делает это List. Конечно же зависит от того что реализующий этот паттерн класс делает. Работает ли он с инфраструктурой или нет.
Да, логгер это типичный InfrastructureService. Пишет в консоль или в NoSQL БД типо ElasticSerch как и Repository может писать в Mongo или MySQL или просто в файл. Любой класс без состояния это Service, а любой класс который работает с инфраструктурой вроде, апи внешней системы, база данных, файл, шина, это InfrastructureService. Repository который работает с БД это тоже Infrastructure Service. Только ApplicationService может работать с Repository. Да блин, посмотрите проект от майкрософт. У них тоже все репозитории в Infrastructure лежат. Вот github.com/dotnet-architecture/eShopOnContainers/tree/dev/src/Services/Ordering те кто считает Repository не частью инфраструктуру и считают себя умнее инженеров майкрософт может идти и с ними поспорить. В статье я описал то что можно не реализации а интерфейсы для рипозиториев помещать в ту библиотеку в которой у вас лежат ApplicationSerivices чтобы создавать каноничные зависимости между слоями.
DomainService — это чистая логика. Не делающая CRUD и поэтому не использующая Repository и прочее. Если совсем в лоб то DomainService == Entity без состояния и данных. И вообще их должно быть минимум в проекте потому что он способствует развитию анемичной модели а это антипаттерн. Его стоит создавать только тогда когда вы не можете привязать логику к одной конкретной сущности.
ApplicationService это фасад который скрывает в себе вызов репозитория, вызов метода сущности. Вычисление каких-то данных в доменном сервисе и т. д.
enterprisecraftsmanship.com/posts/domain-vs-application-services
И да, домен это предметная область. Делят все по BoundedContext и для Каждого BoundedContext свои собственные сущности и доменные сервис могут существовать. Вообще BoundedContext это границы существования сущности поэтому DomainService может существовать только в пределах своего BoundedContext.
Да и по ссылке на стековерфлоу что вы скинули первый ответ говорит тоже самое
Короче — Repository это паттерн который может реализовать класс в любом слое. Я имел в виду те Repository которые обычно делают. Те что работают с БД.
VanquisherWinbringer Автор
Один DomainService не может существовать одновременно в разных доменах поэтому в нем не может пересекаться логика разных доменов. Вообще домен это предметная область как то — интернет торговля, кредитование и т.д.
VanquisherWinbringer Автор
Бизнес логика в Entity и DomainService должна быть.
А так да, только ApplicationService может вызывать класс который работает с БД или с Сетью или с библиотекой для SMTP для отправки почты. Тут безразлично реализует ли этот класс паттерн Repository или нет. Тут важно работает ли этот класс с инфраструктурой или нет. Важно является ли этот класс InfrastructureService.
VanquisherWinbringer Автор
Вообще чтобы поместить класс в нужный слой надо думать не о том какой паттерн класс реализует (Repository или еще какой), а о том какую ответственность это класс несет. Изолировать нас от использующего нас приложения с его фреймворками (WPF, ASP.NET) чтобы наш код оставался чистым от них — значит ApplicationService. Изолировать наше приложение от используемых для получения и отправки данных библиотек и фреймворков нашей Инфраструктуры (SmtpClient, EntityFramework, Redis, HttpClient, Random) чтобы наш код оставался чистым от них и можно было источники данных замокать в тестах значит InfrastructureService. Просто Entity без состояния — DomainService.