Вот как я бы структурировал вашу проблему. Я считаю, что это также рекомендуемый способ DDD сделать это.
public class ProductService : IProductService // Application Service class, used by outside components like UI, WCF, HTTP Services, other Bounded Contexts, etc.
{
private readonly IProductRepository _prodRepository;
private readonly IStoreRepository _storeRepository;
public ProductService(IProductRepository prodRepository, IStoreRepository storeRepository) // Injected dependencies DI
{
if(prodRepository == null) throw new NullArgumentException("Prod Repo is required."); // guard
if(storeRepository == null) throw new NullArgumentException("Store Repo is required."); // guard
_prodRepository = prodRepository;
_storeRepository = storeRepository;
}
public void AddProductToStore(string name, Address address, StoreId storeId) //An exposed API method related to Product that is a part of your Application Service. Address and StoreId are value objects.
{
Store store = _storeRepository.GetBy(storeId);
IProductIdGenerator productIdGenerator = new ProductIdGenerator(_prodRepository);
Product product = Product.MakeNew(name, address, productIdGenerator);
}
... // Rest of API
}
public class Product : Entity
{
public static MakeNew(string name, Address address, IProductIdGenerator productIdGenerator) // Factory to make construction behaviour more explicit
{
return new Product(name, address, productIdGenerator);
}
protected Product(string name, Address address, IProductIdGenerator productIdGenerator)
: base(productIdGenerator.GetNextProductId())
{
Name = name;
Address = address;
}
... // Rest of Product methods, properties and fields
}
public class ProductIdGenerator : IProductIdGenerator
{
private IProductRepository _repository;
public ProductIdGenerator(IProductRepository repository)
{
_repository = repository;
}
public long GetNextProductId()
{
return _repository.GetNextProductId();
}
}
public interface IProductIdGenerator
{
long GetNextProductId();
}
По сути, ProductService является частью вашей службы приложений, то есть точкой входа и выхода всего, что необходимо для использования вашего домена или пересечения его границ. Он отвечает за делегирование каждого варианта использования соответствующим компонентам, которые могут с ним работать, и координацию между всеми этими компонентами, если для выполнения сценария использования требуется много.
Продукт - это ваш AggregateRoot и сущность в вашем домене. Он отвечает за диктование договора UbiquitousLanguage, который захватывает домен вашего предприятия. Таким образом, само по себе это означает, что в вашем домене есть концепция Продукта, которая содержит Данные и Поведение, и какие бы данные и поведение вы ни демонстрировали публично, они должны быть концепцией UbiquitousLanguage. Это поле не должно иметь внешних зависимостей вне доменной модели, поэтому нет сервисов. Но его методы могут принимать доменные службы в качестве параметров, помогающих выполнять логику поведения.
ProductIdGenerator является примером такой доменной службы. Доменные службы инкапсулируют логику поведения, которая выходит за пределы собственной границы сущности. Поэтому, если у вас есть логика, которая требует других совокупных корней или внешних сервисов, таких как репозиторий, файловая система, криптография и т. Д. По сути, любая логика, которую вы не можете тренировать изнутри вашей сущности, не нуждаясь ни в чем другом, вам может понадобиться доменная служба. Если логика заключается в том, что она занята и кажется, что концептуально она может не относиться к методу в вашей сущности, это признак того, что вам может понадобиться совершенно новый вариант использования службы приложений, либо вы пропустили сущность в своем дизайне. Также возможно использовать Службу домена непосредственно из Службы приложений, не используя двойной способ отправки. Это немного похоже на методы расширения C # по сравнению с обычным статическим методом.
=========== Ответить на ваши вопросы о редактировании ===============
Я также сомневался, следует ли вызывать службу из класса продукта.
Доменные службы могут вызываться из класса продукта, если они передаются в качестве временной ссылки через параметр метода. Службы приложений никогда не должны вызываться из класса Product.
Я не использовал заводской шаблон (пока) в качестве конструкции
Объект все еще прост. Я не чувствую, что это оправдывает фабричный метод?
Зависит от того, что вы ожидаете, что вам потребуется больше времени на создание фабрики сейчас, даже если у вас нет логики множественного построения, или рефакторинг позже, когда вы это сделаете. Я думаю, что это не стоит того, чтобы сущности не нуждались в создании более чем одним способом. Как объясняет wikipedia , фабрика используется, чтобы сделать то, что каждый конструктор делает более явным и дифференцируемым. В моем примере фабрика MakeNew объясняет, для чего служит эта конкретная конструкция сущности: создать новый продукт. У вас может быть больше фабрики, такой как MakeExisting, MakeSample, MakeDeprecated и т. Д. Каждая из этих фабрик создаст Продукт, но для разных целей и немного по-разному. Без фабрики все эти конструкторы были бы названы Product (), и было бы трудно понять, какой из них предназначен для чего и для чего. Недостатком является то, что с Factory сложнее работать, когда вы расширяете вашу сущность, дочерняя сущность не может использовать родительскую Factory для создания дочерней, поэтому в любом случае я стараюсь выполнять всю конструкторскую логику внутри конструкторов и использовать только Factory иметь красивое имя для них.
Я в замешательстве ... Откладываю ProductId, если мой класс Product
нужны некоторые другие данные из службы, например, GetSystemDateTime () (я знаю,
плохой пример, но пытается продемонстрировать не вызов БД) где бы это
метод обслуживания будет вызван?
Скажем, вы думали, что реализация Date - это деталь инфраструктуры. Вы должны создать вокруг него абстракцию для использования в своем приложении. Это будет начинаться с интерфейса, может быть, что-то вроде IDateTimeProvider. Этот интерфейс будет иметь метод GetSystemDateTime ().
Ваши прикладные службы могут свободно создавать экземпляр IDateTimeProvider и вызывать его методы в любое время, после чего он может передать результат агрегатам, сущностям, доменным службам или любому другому, кому это потребуется.
Ваши доменные службы могут свободно хранить ссылку на IDateTimeProvider в качестве поля класса, но не должны создавать сам экземпляр. Либо он получает его через внедрение зависимостей, либо запрашивает его через Service Locator.
Наконец, ваши Entites и Aggregate Roots и Value Object могут свободно вызывать GetSystemDateTime () и другие методы IDateTimeProvider, но не напрямую. Он должен был бы пройти двойную диспетчеризацию, где вы бы указали ему доменную службу в качестве параметра одного из его методов, и она бы использовала эту доменную службу для запроса необходимой информации или для выполнения требуемого поведения. Он также может передавать себя обратно в службу домена, где служба домена будет выполнять запросы и настройку.
Если вы считаете, что ваш IDateTimeProvider на самом деле является доменной службой, как часть вездесущего языка, тогда ваши сущности и совокупные корни могут просто напрямую вызывать методы для него, он просто не может содержать ссылку на него как поле класса , но локальные переменные параметров метода в порядке.
Службы в DDD - это логические дампы, в которых логика не является естественной для
доменный объект, верно? Так как же это склеить?
Я думаю, что весь мой ответ уже ясно показал это. По сути, у вас есть 3 варианта склеивания всего этого (о чем я могу думать, по крайней мере, сейчас).
1) Прикладная служба создает экземпляр доменной службы, вызывает для нее метод и передает полученные возвращаемые значения чему-то еще, в чем она нуждалась (репо, сущность, совокупный корень, объект значения, другая служба домена, фабрики и т. Д.). ).
2) Доменная служба создается Доменом приложений и передается в качестве параметра методу того, что будет его использовать. Что бы оно ни использовало, оно не хранит постоянную ссылку на него, это только локальная переменная.
3) Доменная служба создается в Прикладном домене и передается в качестве параметра методу того, что будет его использовать. Что бы оно ни использовало, использует двойную диспетчеризацию для использования доменной службы независимым образом. Это означает, что он передает методу доменной службы ссылку на себя, как в DomainService.DoSomething (this, name, Address).
Надеюсь, это поможет.
Комментарии приветствуются, если я сделал что-то не так или это противоречит лучшим методикам DDD.