Answers:
服务分为三种:域服务,应用程序服务和基础结构服务。
将域服务与域对象保持在一起是明智的–它们都集中在域逻辑上。是的,您可以将存储库注入服务中。
应用程序服务通常将同时使用域服务和存储库来处理外部请求。
希望有帮助!
(如果您不想阅读,则在底部有一个摘要:-)
我也对应用程序服务的精确定义感到困惑。尽管一个月前Vijay的回答对我的思考过程非常有帮助,但我还是不同意其中的一部分。
关于应用程序服务的信息很少。广泛讨论了诸如聚合根,存储库和域服务之类的主题,但是仅简要提及或完全省略了应用程序服务。
《 MSDN杂志》文章“域驱动设计简介”将应用程序服务描述为一种将域模型转换和/或向外部客户端公开的方法,例如作为WCF服务。Vijay也是这样描述应用程序服务的。从这个角度来看,应用程序服务是您域的接口。
Jeffrey Palermo撰写的有关洋葱体系结构的文章(第1部分,第2部分和第3部分)是一本好书。他将应用程序服务视为应用程序级概念,例如用户会话。尽管这更接近我对应用程序服务的理解,但仍然与我对此主题的想法不符。
我已经将应用程序服务视为应用程序提供的依赖项。在这种情况下,该应用程序可以是桌面应用程序或WCF服务。
时间为例。您从您的域开始。不依赖外部资源的所有实体和任何域服务都在此处实现。依赖于外部资源的任何域概念都由接口定义。这是可能的解决方案布局(项目名称为粗体):
我的解决方案 - My.Product.Core(My.Product.dll) -DomainServices IExchangeRateService 产品 产品工厂 IProduct存储库
在Product
和ProductFactory
班已经在核心组件中实现。这IProductRepository
可能是由数据库支持的。这的实现与域无关,因此由接口定义。
目前,我们将专注于IExchangeRateService
。该服务的业务逻辑由外部Web服务实现。但是,其概念仍然是领域的一部分,并由此接口表示。
外部依赖项的实现是应用程序基础结构的一部分:
我的解决方案 + My.Product.Core(My.Product.dll) - My.Product.Infrastructure(My.Product.Infrastructure.dll) -DomainServices XEExchangeRateService SqlServerProductRepository
XEExchangeRateService
IExchangeRateService
通过与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) -DomainServices IExchangeRateService 产品 产品工厂 IProduct存储库 - My.Product.Infrastructure(My.Product.Infrastructure.dll) -ApplicationServices 缓存 -DomainServices CachingExchangeRateService XEExchangeRateService SqlServerProductRepository - My.Product.WcfService(My.Product.WcfService.dll) -ApplicationServices 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服务,因此实现类驻留在基础结构层中。这样,域层仅与业务逻辑有关。
ExchangeRate
实例,其中包含基础货币,相对货币和这两种货币之间的汇率值。这些紧密相关的值表示域中的“汇率”概念,因此它们存在于域层中。尽管它看起来像是简单的DTO,但在DDD中它被称为值对象,它可能包含用于比较或转换实例的其他业务逻辑。
帮助我了解应用程序服务和域服务之间差异的最佳资源是Eric Evans的货运示例的Java实现,可在此处找到。如果下载它,则可以签出RoutingService(域服务)和BookingService,CargoInspectionService(它们是应用程序服务)的内部。
我的“啊哈”时刻是由两件事触发的:
阅读以上链接中的服务描述,更准确地说是:
领域服务用普遍存在的语言和领域类型来表示,即方法参数和返回值是正确的领域类。
阅读此博客文章,尤其是这一部分:
我发现从苹果中分离苹果的最大帮助是在应用程序工作流程方面的思考。通常,与应用程序工作流有关的所有逻辑最终都会被应用程序服务分解到应用程序层中,而域中似乎不适合作为模型对象的概念最终会形成一个或多个域服务。
域服务是域的扩展。应该仅在域的上下文中看到它。这不是诸如关闭帐户之类的用户操作。域服务适合没有状态的地方。否则它将是一个域对象。域服务仅在与其他协作者(域对象或其他服务)一起完成时才有意义。这使得感觉是另一层的责任。
应用程序服务是初始化和监督域对象与服务之间的交互的层。流程通常是这样的:从存储库中获取一个或多个域对象,执行一个动作,然后将其放回那里(或不放回那里)。它可以做更多的事情-例如,它可以检查域对象是否存在,并相应地引发异常。因此,它通过处理域对象和服务,使用户与应用程序进行交互(这可能是其名称的来源)。应用程序服务通常应代表所有可能的用例。考虑域之前,您可能要做的最好的事情就是创建应用程序服务接口,这将使您对真正要执行的操作有更好的了解。拥有这些知识可以使您专注于领域。
一般而言,可以将存储库注入到域服务中,但是这种情况很少见。不过,大部分时间都是由应用程序层执行的。
从红皮书(《实现域驱动设计》,沃恩·弗农(Vaughn Vernon)着)中,这就是我理解这些概念的方式:
域对象(实体和值对象)封装了(子)域所需的行为,使其变得自然,富有表现力和易于理解。
域服务封装了不适合单个域对象的行为。例如,图书阅览室,妆点Book
到Client
(有相应Inventory
的变化)可能会从一个域服务这样做。
应用程序服务处理用例流,包括域顶部所需的任何其他问题。它通常通过其API公开此类方法,以供外部客户端使用。为了建立在前面的示例的基础上,我们的应用程序服务可能会公开以下方法LendBookToClient(Guid bookGuid, Guid clientGuid)
:
Client
。Book
。Client
和Book
)来处理将书借给客户端的实际域逻辑。例如,我想象确认这本书的可用性绝对是领域逻辑的一部分。应用程序服务通常应具有非常简单的流程。复杂的应用程序服务流通常表明域逻辑已泄漏出域。
如您所希望看到的那样,域模型以这种方式保持非常干净,并且易于理解和与域专家讨论,因为它仅包含自己的实际业务问题。该应用程序流,而另一方面,是也更容易管理,因为它解除域的关注,并成为简洁,明了。
域服务:域服务中包含的方法实际上并不适合单个实体或需要访问存储库。域服务层还可以包含其自身的域逻辑,并且与实体和值对象一样,是域模型的一部分。
应用程序服务:应用程序服务是位于领域模型之上并协调应用程序活动的薄层。它不包含业务逻辑,也不保存任何实体的状态;但是,它可以存储业务工作流程交易的状态。您可以使用应用程序服务通过请求-答复消息传递模式将API提供到域模型中。
Millett,C(2010)。专业的ASP.NET设计模式。威利出版社。92。
域服务:表示业务逻辑的服务,该业务逻辑不属于任何聚合根。
您有2个汇总:
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);
}
您可以在此处进行验证,例如检查a Product
是否唯一。除非Product
唯一性是不变式,否则它应该是域服务的一部分,UniqueProductChecker
因为它不能成为Product
类的一部分,并且可以与多个聚合交互,所以它应该被调用。
这是DDD项目的完整示例:https : //github.com/VaughnVernon/IDDD_Samples
您可以找到许多应用程序服务示例和几个域服务
将域服务视为在域对象上实现业务逻辑或与业务规则相关的逻辑的对象,并且该逻辑很难放入相同的域对象中,并且也不会引起域服务的状态更改(域服务是没有对象的对象(“状态”或更好的状态,但没有具有业务意义的状态),但最终仅更改对其进行操作的域对象的状态。
虽然应用程序服务将应用程序级逻辑实现为用户交互,输入验证,与业务无关但与其他问题(身份验证,安全性,电子邮件等)无关的逻辑,从而将自身限制为仅使用域对象公开的服务。
例如,以下情况只是出于解释目的而考虑:我们必须实现一个很小的domotic实用应用程序,该应用程序执行一个简单的操作,即“当有人打开房屋房间的门进入房间时,开灯”当关上从房间出来的门时,请关上并关闭灯。”
为简化起见,我们仅考虑2个域实体:Door
和Lamp
,每个实体分别具有2个状态,分别为open/closed
和on/off
,以及用于对其进行状态更改的特定方法。
在这种情况下,我们需要一个域服务,当有人从外面打开门进入房间时,该服务会执行打开灯的特定操作,因为门和灯对象无法以我们认为合适的方式实现此逻辑他们的天性。
我们可以将域服务称为,DomoticDomainService
并实现2个方法:OpenTheDoorAndTurnOnTheLight
和CloseTheDoorAndTurnOffTheLight
,这2个方法分别更改对象Door
和以及Lamp
与open/on
和的状态closed/off
。
进入或退出房间的状态在域服务对象和域对象中都不存在,但是将由应用程序服务实现为简单的用户交互,我们可以将其称为HouseService
,将某些事件处理程序实现为onOpenRoom1DoorToEnter
和onCloseRoom1DoorToExit
,等等,对于每个房间(这只是一个说明目的的示例。),这将分别涉及用于执行出席行为的调用域服务方法(我们没有考虑实体,Room
因为它只是一个示例)。
该示例远不是一个设计良好的现实应用程序,它的唯一目的(如更多次所述)旨在解释域服务是什么以及它与应用程序服务的区别,希望它清楚且有用。