请问有人可以通过提供一些示例来解释域和应用程序服务之间的区别吗? 如果一个服务是域服务,我是否应该将该服务的实际实现放在域程序集中,如果是这样,我是否还应该将存储库注入到该域服务中? 一些信息会非常有帮助。
能不能请您解释一下什么是“注入”和“程序集”,这样我才能更好地进行翻译呢?请问有人可以通过提供一些示例来解释域和应用程序服务之间的区别吗? 如果一个服务是域服务,我是否应该将该服务的实际实现放在域程序集中,如果是这样,我是否还应该将存储库注入到该域服务中? 一些信息会非常有帮助。
能不能请您解释一下什么是“注入”和“程序集”,这样我才能更好地进行翻译呢?服务分为三种类型:域服务、应用服务和基础设施服务。
将域服务与域对象一起保留是明智的——它们都专注于领域逻辑。是的,您可以将存储库注入到您的服务中。
应用服务通常会同时使用域服务和存储库来处理外部请求。
(如果您不想阅读,底部有摘要 :-))
我也曾经苦恼于应用程序服务的精确定义。虽然一个月前Vijay的答案对我的思考过程非常有帮助,但我现在不同意其中的一部分。
关于应用程序服务的信息很少。像聚合根、仓储和领域服务这样的主题被广泛讨论,但是应用程序服务只是简单地提到或者根本没有提到。
MSDN Magazine文章《领域驱动设计入门》将应用程序服务描述为将您的领域模型转换和/或公开给外部客户端的一种方式,例如作为WCF服务。这也是Vijay描述应用程序服务的方式。从这个角度来看,应用程序服务是您领域的接口。
Jeffrey Palermo关于洋葱架构的文章(第1、2和3部分)是不错的阅读材料。他将应用程序服务视为应用程序级别的概念,例如用户会话。虽然这更接近我的应用程序服务理解,但仍与我的思考不一致。
我认为应用程序服务是应用程序提供的依赖项。在这种情况下,应用程序可以是桌面应用程序或WCF服务。
现在是时候举个例子了。首先从您的领域开始。所有实体以及不依赖外部资源的任何领域服务都在此处实现。任何依赖于外部资源的领域概念都由一个接口定义。以下是可能的解决方案布局(粗体表示项目名称):
My Solution - My.Product.Core (My.Product.dll) - DomainServices IExchangeRateService Product ProductFactory IProductRepository
Product
和 ProductFactory
类已经在核心程序集中实现了。 IProductRepository
是由数据库支持的东西。其实现不是领域的问题,因此由一个接口定义。
现在,我们将关注 IExchangeRateService
。这个服务的业务逻辑是由外部Web服务实现的。但是,它的概念仍然是领域的一部分,并由此接口表示。
外部依赖的实现是应用程序基础设施的一部分:
My Solution + My.Product.Core (My.Product.dll) - My.Product.Infrastructure (My.Product.Infrastructure.dll) - DomainServices XEExchangeRateService SqlServerProductRepository
XEExchangeRateService
通过与xe.com通信实现了IExchangeRateService
领域服务。此实现可以被使用您的领域模
public class CachingExchangeRateService : IExchangeRateService
{
private IExchangeRateService service;
private ICache cache;
public CachingExchangeRateService(IExchangeRateService service, ICache cache)
{
this.service = service;
this.cache = cache;
}
// Implementation that utilizes the provided service and cache.
}
注意到ICache
参数了吗?这个概念不属于我们的领域,因此它不是一个领域服务。它是一个应用服务,是我们基础设施的依赖项,可能由应用程序提供。让我们引入一个演示这个概念的应用程序:
我的解决方案 - My.Product.Core (My.Product.dll) - 领域服务 IExchangeRateService 产品 ProductFactory IProductRepository - My.Product.Infrastructure (My.Product.Infrastructure.dll) - 应用服务 ICache - 领域服务 CachingExchangeRateService XEExchangeRateService SqlServerProductRepository - My.Product.WcfService (My.Product.WcfService.dll) - 应用服务 MemcachedCache IMyWcfService.cs + MyWcfService.svc + Web.config
所有这些在应用程序中汇集在一起:
// Set up all the dependencies and register them in the IoC container.
var service = new XEExchangeRateService();
var cache = new MemcachedCache();
var cachingService = new CachingExchangeRateService(service, cache);
ServiceLocator.For<IExchangeRateService>().Use(cachingService);
一个完整的应用程序由三个主要层组成:
领域层包含领域实体和独立的领域服务。任何依赖于外部资源的领域概念(包括领域服务和存储库)都是通过接口定义的。
基础设施层包含来自领域层的接口实现。这些实现可能引入新的非领域依赖项,需要应用程序来提供。这些是应用程序服务,并由接口表示。
应用层包含应用程序服务的实现。如果基础设施层提供的实现不足够,应用层还可以包含领域接口的附加实现。
尽管这种观点可能与DDD中服务的一般定义不匹配,但它确实将领域与应用程序分开,并允许您在多个应用程序之间共享领域(和基础架构)程序集。
IExchangeRateService
接口吗?这是一个领域概念,也就是包含在客户通用语言中的内容。你的领域的其他部分可能依赖于此服务,因此它的接口定义在领域层中。但由于其实现涉及外部 Web 服务,因此实现类位于基础设施层。这样,领域层只关注业务逻辑。 - Niels van der Rest领域服务使用通用语言和领域类型来表达,即方法参数和返回值都是适当的领域类。
我发现在将应用程序流程与领域模型分离方面非常有帮助。涉及应用程序流程的所有逻辑通常最终成为应用程序服务,并分解到应用程序层中,而来自领域的不适合作为模型对象的概念最终形成一个或多个领域服务。
这是我对《实现领域驱动设计》(作者:Vaughn Vernon)中一些概念的理解:
领域对象(包括实体和值对象)封装了子域所需的行为,使其自然、表达力强、易于理解。
领域服务封装了不适合于一个单独的领域对象的行为。例如,借书馆将Book
借给Client
(与相应的Inventory
更改),可能就需要使用领域服务。
应用服务处理用例的流程,包括在领域之上所需的任何其他问题。它通常通过API公开这些方法,供外部客户端使用。继续上面的例子,我们的应用服务可以公开一个方法LendBookToClient(Guid bookGuid, Guid clientGuid)
,它:
Client
。Book
。Client
和Book
)来处理借给客户的实际领域逻辑。例如,我认为确认书籍的可用性肯定是领域逻辑的一部分。应用程序服务通常应该具有非常简单的流程。复杂的应用程序服务流往往表明领域逻辑已经泄露出去了。
希望您能够看到,这种方式下领域模型非常清晰简洁,易于理解和与领域专家进行讨论,因为它只包含自己的业务关注点。另一方面,应用程序流程因为不再承担领域相关的责任,变得更加简洁和直接,也更容易管理。
Book
和Client
将是领域对象。领域模型是用于在业务领域实现功能的整个模型。由于将书借给客户可能很复杂,因此可能涉及领域服务,因为该过程的责任涉及多个实体:书是否可用?客户是否有空间借更多书?客户是否有权首先借书(活动订阅,无债务等)?这些都是需要在模型中某处执行的领域规则。 - TimoLendBook
的应用程序服务可能会:执行身份验证和授权。解释和验证输入的合同模型(例如 LendBookRequest
)。加载 Client
和 Book
。确认它们存在或使请求无效。调用领域服务执行领域逻辑。将结果适配为输出合同模型(例如 LendBookResponse
)。
(请记住,虽然领域模型可以自由更改,但合同模型很难更改。它需要稳定,成为您的 API 的一部分,并被外部上下文使用。) - Timo领域服务是域的扩展,只在域的范围内看到。这不像“关闭账户”之类的某些用户操作。如果存在状态,则应该是一个领域对象。领域服务执行的操作只有与其他协作者(领域对象或其他服务)一起使用时才有意义,并且“有意义”的责任属于另一层。
应用程序服务是初始化和监督领域对象和服务之间交互的那一层。通常的流程是:从存储库获取领域对象(或多个对象),执行操作,然后将它(它们)放回去(或不放回)。它也可以做更多的事情,例如检查领域对象是否存在,根据情况抛出异常。因此,它使用户可以通过操纵领域对象和服务与应用程序互动(这可能是其名称的起源所在)。应用程序服务通常代表所有可能的用例。在考虑领域之前,最好创建应用程序服务接口,这将让您更好地了解自己真正想要做什么。拥有这样的知识能够让您专注于领域。
存储库通常可以注入到领域服务中,但这种情况相对较少。大多数情况下,是应用程序层这样做。
而应用服务实现应用级别的逻辑,例如用户交互、输入验证和与业务无关的逻辑,例如身份验证、安全、电子邮件等,仅限于简单地使用领域对象公开的服务。
以下是一个仅用于解释目的的示例场景:我们必须实现一个非常小的domotic实用程序应用程序,执行一个简单的操作,即“当有人打开进入房间的门时,打开灯,当关闭房间的门时关闭灯”。
简化后,我们只考虑两个不属于同一个聚合的领域实体:门
和灯
,它们各自有两种状态:打开/关闭
和开/关
,并且具有特定的方法来操作它们的状态变化。这些实体需要成为不同的聚合部分,以便以下逻辑无法在聚合根中实现。
在这种情况下,我们需要一个领域服务,在有人从外面打开门进入房间时执行打开灯光的特定操作,因为门和灯对象不能以我们认为适合它们的业务性质的方式实现此逻辑。这个新的领域服务需要封装一些应该始终发生的业务流程,由某些领域事件/方法触发。
我们可以把我们的领域服务称为DomoticDomainService
,并实现两个方法:OpenTheDoorAndTurnOnTheLight
和CloseTheDoorAndTurnOffTheLight
,这两个方法分别将Door
和Lamp
对象的状态更改为打开/开
和关闭/关
。进入或离开房间的状态既不在领域服务对象中,也不在领域对象中,而将通过一个应用服务实现简单的用户交互。我们可以称之为 HouseService
的应用服务,该服务实现一些事件处理程序,如 onOpenRoom1DoorToEnter
和 onCloseRoom1DoorToExit
,对于每个房间(这只是为了说明的一个例子),分别涉及调用领域服务方法以执行所需的行为。(由于这只是一个示例,我们没有考虑实体Room
)。域服务:不适合单个实体或需要访问存储库的方法包含在域服务中。域服务层也可以包含自身的域逻辑,与实体和值对象一样是域模型的一部分。
应用服务:应用程序服务是位于域模型之上的薄层,协调应用程序活动。它不包含业务逻辑,也不保存任何实体的状态;但是,它可以存储业��工作流事务的状态。您可以使用应用程序服务使用请求-响应消息模式将API提供给域模型。
出处:Millett,C (2010). Professional ASP.NET Design Patterns. Wiley Publishing. 92.
域服务:表达不属于任何聚合根的业务逻辑的服务。
你有两个聚合根:
Product
包含名称和价格。Purchase
包含购买日期,订单中所订购的产品列表、数量和当时的产品价格,以及支付方式。Checkout
不属于这两个模型的任何一个,是你业务中的一个概念。
Checkout
可以作为一个域服务创建,它获取所有产品并计算总价,通过调用另一个基础设施实现的域服务 PaymentService
进行支付,并将其转换为 Purchase
。应用服务:一个“编排”或执行域方法的服务。这可以只是你的控制器。
这通常是你完成以下工作的地方:
public String createProduct(...some attributes) {
if (productRepo.getByName(name) != null) {
throw new Exception();
}
productId = productRepository.nextIdentity();
product = new Product(productId, ...some attributes);
productRepository.save(product);
return productId.value();
// or Product itself
// or just void if you dont care about result
}
public void renameProduct(productId, newName) {
product = productRepo.getById(productId);
product.rename(newName);
productRepo.save(product);
}
在这里,您可以进行验证,例如检查产品是否唯一。除非产品的唯一性是不变量,否则应该作为域服务的一部分,可能被称为UniqueProductChecker,因为它不能成为Product类的一部分,并且与多个聚合进行交互。
以下是DDD项目的完整示例:https://github.com/VaughnVernon/IDDD_Samples
您可以找到很多应用程序服务和几个领域服务的示例。