领域驱动设计:领域服务、应用服务

有人能举例说明域服务和应用程序服务之间的区别吗?而且,如果一个服务是一个域服务,我是否会将该服务的实际实现放在域程序集中,如果是的话,我是否也会将存储库注入到该域服务中?一些信息会很有帮助。

119793 次浏览

服务有3种类型:域名服务应用程序服务基础设施服务

  • 域名服务:封装 业务逻辑,不自然 适合域对象,并且是典型的CRUD操作-这些操作将属于存储库.
  • . properties
  • 应用程序服务:用于 外部消费者与你交谈 system(想想Web服务)。如果使用者需要访问CRUD操作,它们将在这里被暴露
  • 基础设施服务:习惯 抽象的技术问题(例如: MSMQ,电子邮件提供商等)

把域服务和域对象放在一起是明智的——它们都关注域逻辑。是的,您可以将存储库注入到服务中。

应用程序服务通常使用两个域服务而且存储库来处理外部请求。

希望有帮助!

(如果你不想读,下面有一个总结:-)

我也曾为应用程序服务的精确定义而苦恼。虽然维贾伊的回答对我一个月前的思考过程很有帮助,但我对其中的一部分不同意。

其他资源

关于应用程序服务的信息很少。像聚合根、存储库和域服务这样的主题被广泛讨论,但应用程序服务只被简要提及或完全忽略。

MSDN杂志文章领域驱动设计简介将应用程序服务描述为一种转换和/或向外部客户端公开域模型的方式,例如作为WCF服务。Vijay也是这样描述应用程序服务的。从这个角度来看,应用程序服务是一个到域的接口

Jeffrey Palermo关于洋葱架构的文章(一个两个三个部分)是很好的读物。他将应用程序服务视为应用程序级的概念,例如用户的会话。尽管这更接近于我对应用程序服务的理解,但它仍然不符合我对这个主题的想法。

我的思想

我认为应用程序服务是应用程序提供的依赖项。在这种情况下,应用程序可以是桌面应用程序或WCF服务。

下面是一个例子。你从你的域名开始。不依赖于外部资源的所有实体和任何域服务都在这里实现。任何依赖于外部资源的域概念都是由接口定义的。下面是一个可能的解决方案布局(项目名称以粗体显示):

My Solution
- My.Product.Core (My.Product.dll)
- DomainServices
IExchangeRateService
Product
ProductFactory
IProductRepository

ProductProductFactory类已经在核心程序集中实现。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域服务。通过包含基础结构程序集,可以由利用域模型的应用程序使用此实现。

应用程序

注意,我还没有提到应用程序服务。我们现在来看一下。假设我们想要提供一个使用缓存进行快速查找的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 Solution
- My.Product.Core (My.Product.dll)
- DomainServices
IExchangeRateService
Product
ProductFactory
IProductRepository
- My.Product.Infrastructure (My.Product.Infrastructure.dll)
- ApplicationServices
ICache
- 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定义不匹配,但它确实将域与应用程序分离,并允许您在几个应用程序之间共享域(和基础结构)程序集。

帮助我理解应用程序服务和域服务之间区别的最好的资源是Eric Evans的cargo示例的java实现,找到在这里。如果你下载了它,你可以检查RoutingService(域服务)和BookingService, CargoInspectionService(应用服务)的内部结构。

我顿悟的时刻是由两件事引发的:

  • 阅读上述链接中对服务的描述,更准确地说是这句话:

域服务用通用语言和表示 域类型,即方法参数和返回值为

  • 读取这个博客,特别是这一部分:
我发现区分苹果和橘子的一个很大的帮助是 从应用程序工作流的角度考虑。所有关于 应用程序工作流通常以应用程序服务结束 考虑到应用层,而来自领域的概念 这似乎不适合模型对象最终形成一个或多个 域服务。< / p >

域服务是域的扩展名。应该只在域的上下文中看到它。这不是像关闭帐户之类的用户操作。域服务适用于没有状态的地方。否则它就是一个域对象。域服务只有在与其他协作者(域对象或其他服务)合作时才有意义。而搞清楚是另一层的职责。

应用程序服务是初始化和监督域对象和服务之间交互的层。流程通常是这样的:从存储库中获取域对象(或多个对象),执行一个操作并将其(或它们)放回存储库(或不放回存储库)。它可以做更多的事情——例如,它可以检查一个域对象是否存在,并相应地抛出异常。因此,它允许用户通过操作域对象和服务与应用程序交互(这可能就是它的名字的来源)。应用程序服务通常应该表示所有可能的用例。在考虑域之前,您能做的最好的事情可能是创建应用程序服务接口,这将使您更好地了解您真正要做的事情。拥有这些知识可以使您专注于该领域。

一般来说,存储库可以被注入到域服务中,但这种情况相当罕见。不过,大多数时候是应用层在做这件事。

从红皮书(实现领域驱动设计,由Vaughn Vernon)中,我是这样理解这些概念的:

域对象 (实体值对象)封装了(子)域所需的行为,使其自然、有表现力和可理解。

域名服务封装了不适合域对象的行为。例如,一个图书馆将Book借给Client(有相应的Inventory更改)可以从域服务中这样做。

应用程序服务处理用例流,包括域所需的任何额外关注点。它经常通过API公开这样的方法,供外部客户端使用。基于前面的例子,我们的应用程序服务可以公开一个方法LendBookToClient(Guid bookGuid, Guid clientGuid):

  • 检索Client
  • 确认其权限。(请注意,我们如何使域模型不受安全/用户管理问题的影响。这样的污染会导致很多问题。相反,我们在我们的应用程序服务中满足了这个技术需求。)
  • 检索Book
  • 调用域服务(传递ClientBook)来处理将书借给客户端的实际域逻辑。例如,我认为确认书籍的可用性肯定是域逻辑的一部分。

应用程序服务通常应该具有非常简单的流程。复杂的应用程序服务流通常表明域逻辑已经泄漏出域。

正如你所希望看到的,域模型在这种方式下保持非常清洁,并且很容易理解和与领域专家讨论,因为它只包含它自己的实际业务关注点。另一方面,应用程序流更容易管理,因为它消除了领域问题,变得简洁和直接。

不适合单个实体或需要访问存储库的方法包含在domain中 服务。域服务层还可以包含的域逻辑 它自己的,与实体和值一样都是领域模型的一部分 对象。< / p > 应用程序服务是一个薄层,位于域模型之上,协调应用程序 活动。对象不包含业务逻辑,也不包含 任何实体的状态;但是,它可以存储业务的状态 工作流事务。您使用应用程序服务来提供API

.使用请求-应答消息模式进入域模型

米勒特,C(2010)。专业的ASP。NET设计模式。威利出版。92。

域名服务:表示不属于任何聚合根的业务逻辑的服务。

  • 你有2个聚合:

    • Product包含名称和价格。
    • Purchase包含购买日期,订购产品的数量和当时的产品价格的清单,以及付款方式。
    • 李< / ul > < / >
    • 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);
    }
    
    
    

    你可以在这里进行验证,比如检查Product是否唯一。除非唯一的Product是一个不变量,否则它应该是域服务的一部分,可能被称为UniqueProductChecker,因为它不能是Product类的一部分,并且它与多个聚合交互。

    下面是DDD项目的完整示例:https://github.com/VaughnVernon/IDDD_Samples

    你可以找到很多应用程序服务和一些域服务的例子

域服务视为在域对象上实现业务逻辑或业务规则相关逻辑的对象,这种逻辑很难适合相同的域对象,也不会导致域服务域服务是一个没有“状态”的对象。或者最好没有一个具有业务意义的状态)的状态更改,但最终只会更改操作的域对象的状态。

虽然应用程序服务实现了应用级逻辑,如用户交互、输入验证、与业务无关的逻辑,但与其他关注点有关:身份验证、安全性、电子邮件等等。,将自身限制为简单地使用域对象公开的服务。

这方面的一个例子可能是以下仅用于解释目的的场景

简化很多,我们只考虑2个域实体,它们不是同一个聚合的一部分:DoorLamp,它们每个都有2个状态,分别是open/closedon/off,以及在它们上操作状态更改的特定方法。实体需要是不同聚合的一部分,这样下面的逻辑就不能在聚合根中实现。

在这种情况下,我们需要一个域服务来执行特定的操作:当有人从外面打开门进入房间时打开灯,因为门和灯对象不能以我们认为适合其业务性质的方式实现此逻辑。这个新的域服务需要封装一些应该总是发生的业务流程,由一些域事件/方法触发。

我们可以调用域服务DomoticDomainService并实现两个方法:OpenTheDoorAndTurnOnTheLightCloseTheDoorAndTurnOffTheLight,这两个方法分别将DoorLamp对象的状态更改为OpenTheDoorAndTurnOnTheLight0和OpenTheDoorAndTurnOnTheLight1。

进入或退出房间的状态不存在于域服务对象中,也不存在于域对象中,而是由应用程序服务实现为简单的用户交互,我们可以将其称为HouseService,它实现了一些事件处理程序,如onOpenRoom1DoorToEnteronCloseRoom1DoorToExit,等等,对于每个房间(这只是解释目的的一个例子..),它将分别关心调用域服务方法来执行参与行为(我们没有考虑实体Room,因为它只是一个例子)

这个例子,远远不是一个设计良好的现实世界的应用程序,有唯一的目的(已经说过很多次了)解释什么是域服务以及它与应用程序服务的区别,希望它是清晰和有用的。

同样,上面的示例域服务可以很容易地被用于显式实现跨一个或多个聚合的副作用的域事件所取代,但由于这些不是这个问题的主题,所以我在这里只提到它们,以便读者可以意识到它们的存在,然后决定哪种方法更适合它们。