如何从域层访问存储
我想我可能对域/服务层分离有一些困惑。
在我的应用程序中,域代码需要生成具有业务含义的系统范围唯一标识符。这意味着它必须以独占访问方式访问存储。我可以直接在域代码中执行此操作,但我认为这是错误的,原因有两个:
- 域代码不应该访问存储(我认为......)
- 获得独占存储锁意味着我将阻止对其他域对象的并发操作也需要此访问权限,因为只有在提交整个会话时才会释放锁。这里使用乐观并发感觉很浪费:虽然ID生成很快,但业务操作相对较长,这意味着两个业务并发操作的概率很高。
举个例子,考虑以下情况:
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。
您会推荐什么方法?
I think I may have some confusion about the domain/service layer separation.
In my application, the domain code needs to generate a system-wide unique identifier with a business meaning. This means it has to access the storage with exclusive access. I could do this directly in the domain code, but I think this is wrong for two reasons:
- Domain code should not access the storage (I think...)
- Getting an exclusive storage lock means I will block concurrent operations on other domain objects who also need this access, since the lock will be released only when the entire session is committed. It feels wasteful to use optimistic concurrency here: while the ID generation is quick, the business operations are relatively long, which means high probability of two concurrent business operations.
As an example, consider the following:
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?
}
}
There are two options I could think of:
// 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);
}
}
And:
// 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);
}
}
Now, option (1) has obvious disadvantages, but option (2) has the domain layer accessing the service layer. In all the diagrams I saw, this is a big no-no.
Following Jimmy Bogard's terminology (http://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/) it looks like I want a Domain Service (as opposed to an Application Service) here, but this Domain Service will access the storage (not only through a repository: it will create an independent session + transaction.)
I should note that option (2) has the drawback that the ID generation cannot be rolled-back in case of a problem in Order.Process, because it's already been committed. I have no problem with that in my scenario. I'm fine with wasting IDs.
If it makes a difference, I'm using NHibernate as my ORM.
What approach would you recommend?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
要删除 Order 对 IdBroker 的依赖关系,您可以在 OrderService 中注入 IdBrokerService,并将新的 Id 作为参数传递给 Order.Process 方法。
如果您不介意浪费 id,那么生成新 id 的独立服务可能是您的情况下的最佳解决方案。然后,您可以将此服务添加为对获取 id 并将其传递给域对象的其他服务的依赖项。这样您的域对象就可以避免任何外部交互。
To remove the dependency from the Order to the IdBroker you could inject the IdBrokerService injected in the OrderService and pass the new Id to the Order.Process method as a parameter.
If you are fine with wasting id's that an independent service that generates a new id is probably the best solution in your case. You then add this service as a dependency to other services that get the ids and pass them to domain objects. This way your domain objects stay clear of any external interaction.