Я думаю, что у меня может быть некоторая путаница в отношении разделения уровня домена/сервиса.
В моем приложении код домена должен генерировать общесистемный уникальный идентификатор с бизнес-значением. Это означает, что он должен получить доступ к хранилищу с монопольным доступом. Я мог бы сделать это прямо в коде домена, но считаю это неправильным по двум причинам:
- Код домена не должен обращаться к хранилищу (я думаю...)
- Получение эксклюзивной блокировки хранилища означает, что я буду блокировать одновременные операции с другими объектами домена, которым также нужен этот доступ, поскольку блокировка будет снята только после фиксации всего сеанса. Здесь кажется расточительным использовать оптимистичный параллелизм: хотя генерация идентификатора происходит быстро, бизнес-операции относительно длительны, что означает высокую вероятность двух параллельных бизнес-операций.
В качестве примера рассмотрим следующее:
class UnitOfWork
{
public OrderRepository TheOrderRepository { get; private set; }
public void Commit() { /* ... */ }
}
class UnitOfWorkFactory
{
UnitOfWork CreateNewUnitOfWork()
{
var sess = CreateNewSession();
var trans = CreateNewTransaction(session);
return new UnitOfWork
{
TheOrderRepository = new OrderRepository(sess, trans);
}
}
}
class OrderService // application service layer
{
public void ProcessOrder(ID id, Details details)
{
using (uow = UnitOfWorkFactory.CreateNewUnitOfWork())
{
Order order = TheOrderRepository.Load(id);
order.Process(details);
uow.TheOrderRepository.Update(order);
uow.Commit();
}
}
}
class Order // domain layer
{
public void Process(Details details)
{
// need to get a unique, business-related identifier here.
// Where do I get it from?
}
}
Есть два варианта, о которых я мог подумать:
// Option 1 - get BusinessIdRepository from Service layer
class Order // domain layer
{
public void Process(Details details)
{
// bad, because other Orders will block in Process() until my Process()
// is done.
// also, I access the storage in my domain layer, which is a no-no (?)
var id = m_businessIdRepository.GetUniqueId(details);
}
}
А также:
// Option 2 - introduce a service. For this example, suppose the business ID is
// just an increasing counter
class BusinessIdBrokerService
{
int GetBusinessId(Details details)
{
int latestId;
using (uow = UnitOfWorkFactory.CreateNewUnitOfWork())
{
latestId = TheIdRepository.GetLatest(); // takes lock
latestId ++;
TheIdRepository.SetLatestId(latestId);
uow.Commit(); // lock is released
}
return latestId;
}
}
class Order // domain layer
{
public void Process(Details details)
{
// domain layer accessing the service layer. Is this bad?
var id = m_businessIdBroker.GetBusinessId(details);
}
}
Итак, вариант (1) имеет очевидные недостатки, но вариант (2) имеет уровень домена, обращающийся к сервисному уровню. На всех диаграммах, которые я видел, это большое нет-нет.
Следуя терминологии Джимми Богарда (http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/), похоже, мне нужна доменная служба (в отличие от службы приложений), но этот доменный сервис будет обращаться к хранилищу (не только через репозиторий: он создаст независимую сессию + транзакцию.)
Должен заметить, что вариант (2) имеет тот недостаток, что генерацию ID нельзя откатить в случае проблемы в Order.Process, потому что она уже зафиксирована. У меня нет проблем с этим в моем сценарии. Я не против тратить удостоверения личности.
Если это имеет значение, я использую NHibernate в качестве ORM.
Какой подход вы бы порекомендовали?