域驱动设计:域服务,应用程序服务


268

有人可以通过提供一些示例来解释域服务和应用程序服务之间的区别吗?而且,如果服务是域服务,我会将该服务的实际实现放在域程序集中吗?如果是,我是否还会将存储库注入该域服务中?一些信息将非常有帮助。

Answers:


356

服务分为三种:域服务应用程序服务基础结构服务

  • 域服务:封装 业务逻辑,这些业务逻辑自然不适合域对象,并且不是典型的CRUD操作–那些属于Repository
  • 应用程序服务:供外部使用者用来与您的系统对话(请考虑Web服务)。如果消费者需要访问CRUD操作,则可以在此处找到他们。
  • 基础设施服务:用于抽象技术问题(例如MSMQ,电子邮件提供商等)。

将域服务与域对象保持在一起是明智的–它们都集中在域逻辑上。是的,您可以将存储库注入服务中。

应用程序服务通常将同时使用域服务存储库来处理外部请求。

希望有帮助!


2
您会将CQRS的命令和查询放在哪里?哪个服务生成它们,哪个服务处理它们?
inf3rno

5
我认为应用程序服务应该独立于诸如“ Web服务”之类的技术细节,它们被此类服务使用。参见领域驱动设计服务
deamon 2015年

1
@prograhammer-域服务的示例可以是FundsTransferService,其中域模型是BankAccount,转账可能具有某些业务逻辑,而该逻辑不能直接适合帐户对象(摘自Evans DDD书)。
BornToCode '16

因此说,例如Loginuser()将是域服务的示例。哪里的getUsers()将是应用程序服务?
filthy_wizard,2016年

两者都是相当的应用程序服务,因为身份验证和授权决策通常也不属于核心域。
MauganRa

114

(如果您不想阅读,则在底部有一个摘要:-)

我也对应用程序服务的精确定义感到困惑。尽管一个月前Vijay的回答对我的思考过程非常有帮助,但我还是不同意其中的一部分。

其他资源

关于应用程序服务的信息很少。广泛讨论了诸如聚合根,存储库和域服务之类的主题,但是仅简要提及或完全省略了应用程序服务。

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

Jeffrey Palermo撰写的有关洋葱体系结构的文章(第1部分,第2部分和第3部分)是一本好书。他将应用程序服务视为应用程序级概念,例如用户会话。尽管这更接近我对应用程序服务的理解,但仍然与我对此主题的想法不符。

我的想法

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

时间为例。您从您的域开始。不依赖外部资源的所有实体和任何域服务都在此处实现。依赖于外部资源的任何域概念都由接口定义。这是可能的解决方案布局(项目名称为粗体):

我的解决方案
- My.Product.Core(My.Product.dll)
  -DomainServices
      IExchangeRateService
    产品
    产品工厂
    IProduct存储库

ProductProductFactory班已经在核心组件中实现。这IProductRepository可能是由数据库支持的。这的实现与域无关,因此由接口定义。

目前,我们将专注于IExchangeRateService。该服务的业务逻辑由外部Web服务实现。但是,其概念仍然是领域的一部分,并由此接口表示。

基础设施

外部依赖项的实现是应用程序基础结构的一部分:

我的解决方案
+ My.Product.Core(My.Product.dll)
- My.Product.Infrastructure(My.Product.Infrastructure.dll)
  -DomainServices
      XEExchangeRateService
    SqlServerProductRepository

XEExchangeRateServiceIExchangeRateService通过与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定义不匹配,但它确实将域与应用程序分开,并允许您在多个应用程序之间共享域(和基础结构)程序集。


2
@ dario-g:您必须从请求模型重构/重新填充域模型,然后将域模型传递给域服务。这个问题可能会为您提供一些想法。如果没有,请告诉我,我看看是否有时间为另一个问题添加答案。
Niels van der Rest

1
@Tiendq:您的意思是IExchangeRateService界面吗?这是一个领域概念,即客户普遍使用的语言中所包含的内容。域的其他部分可能依赖于此服务,这就是其接口在域层中定义的原因。但是,由于其实现涉及外部Web服务,因此实现类驻留在基础结构层中。这样,域层仅与业务逻辑有关。
Niels van der Rest

4
@Tiendq:在传统的分层体系结构中,基础结构通常是领域无关的。但是在洋葱架构(请参阅我的答案中的链接)中,基础架构实现了域的外部依赖关系。但是我不会说基础架构取决于域,而只是引用它。我从洋葱架构中使用了“基础设施”一词,但“外部”可能是一个更好的名称。
Niels van der Rest

1
@Derek:这些“事物”之一可能是一个ExchangeRate实例,其中包含基础货币,相对货币和这两种货币之间的汇率值。这些紧密相关的值表示域中的“汇率”概念,因此它们存在于域层中。尽管它看起来像是简单的DTO,但在DDD中它被称为值对象,它可能包含用于比较或转换实例的其他业务逻辑。
Niels van der Rest 2012年

6
我不同意您不同意Vijay的部分,这就是原因。CachingExchangeRateService是基础结构方面的问题。即使您通常接受ICache,该ICache的实现也取决于所涉及的技术(即Web,Windows)。仅仅因为它是通用的就不能使其成为应用程序服务。应用程序服务是您域的API。如果您想向其他编写应用程序的人透露您的域名,他们将使用什么?应用服务,并且它们可能不需要缓存,因此您的缓存暗示对他们毫无用处(即,为什么是基础架构)
Aaron Hawkins

38

帮助我了解应用程序服务和域服务之间差异的最佳资源是Eric Evans的货运示例的Java实现,可在此处找到。如果下载它,则可以签出RoutingService(域服务)和BookingService,CargoInspectionService(它们是应用程序服务)的内部。

我的“啊哈”时刻是由两件事触发的:

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

    领域服务用普遍存在的语言和领域类型来表示,即方法参数和返回值是正确的领域类。

  • 阅读此博客文章,尤其是这一部分:

    我发现从苹果中分离苹果的最大帮助是在应用程序工作流程方面的思考。通常,与应用程序工作流有关的所有逻辑最终都会被应用程序服务分解到应用程序层中,而域中似乎不适合作为模型对象的概念最终会形成一个或多个域服务。


3
我同意,这正是我定义应用程序服务的方式,并且适合我到目前为止遇到的所有情况。域服务处理与域对象相关的所有内容,但超出单个实体的范围。例如:BookReferencesService.GetNextAvailableUniqueTrackingNumber(),重点显然是业务规则*。关于应用程序服务,正是您所描述的,大多数时候,我都是先将业务工作流放入控制器操作中,当我注意到它时,便在应用程序服务层中重构了此逻辑。我们可能会说这一层用于用例
tobiak777

1
*并且此类域服务接口由域实体使用。
tobiak777

32

域服务的扩展。应该仅在域的上下文中看到它。这不是诸如关闭帐户之类的用户操作。域服务适合没有状态的地方。否则它将是一个域对象。域服务仅在与其他协作者(域对象或其他服务)一起完成时才有意义。这使得感觉是另一层的责任。

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

一般而言,可以将存储库注入到域服务中,但是这种情况很少见。不过,大部分时间都是由应用程序层执行的。


10
“域服务适合没有状态的地方。否则它将是域对象。” 让它为我点击。谢谢。
尼克

31

从红皮书(《实现域驱动设计》,沃恩·弗农(Vaughn Vernon)着)中,这就是我理解这些概念的方式:

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

域服务封装了不适合单个域对象的行为。例如,图书阅览室,妆点BookClient(有相应Inventory的变化)可能会从一个域服务这样做。

应用程序服务处理用例流,包括域顶部所需的任何其他问题。它通常通过其API公开此类方法,以供外部客户端使用。为了建立在前面的示例的基础上,我们的应用程序服务可能会公开以下方法LendBookToClient(Guid bookGuid, Guid clientGuid)

  • 检索Client
  • 确认其权限。(请注意我们如何使我们的域模型免于安全/用户管理方面的顾虑。这种污染可能导致许多问题。相反,我们在此通过应用服务满足此技术要求。
  • 检索Book
  • 调用域服务(传递ClientBook)来处理将书借给客户端的实际域逻辑。例如,我想象确认这本书的可用性绝对是领域逻辑的一部分。

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

如您所希望看到的那样,域模型以这种方式保持非常干净,并且易于理解和与域专家讨论,因为它仅包含自己的实际业务问题。该应用程序流,而另一方面,是更容易管理,因为它解除域的关注,并成为简洁,明了。


2
我想说应用程序服务也是解决依赖关系的关键。它的方法是一个用例,一个流程,因此它可以针对要使用的具体实现做出明智的决定。数据库事务也适用于此。
Timo

10

域服务:域服务中包含的方法实际上并不适合单个实体或需要访问存储库。域服务层还可以包含其自身的域逻辑,并且与实体和值对象一样,是域模型的一部分。

应用程序服务:应用程序服务是位于领域模型之上并协调应用程序活动的薄层。它不包含业务逻辑,也不保存任何实体的状态;但是,它可以存储业务工作流程交易的状态。您可以使用应用程序服务通过请求-答复消息传递模式将API提供到域模型中。

Millett,C(2010)。专业的ASP.NET设计模式。威利出版社。92。


6

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

  • 您有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

您可以找到许多应用程序服务示例和几个域服务


是否仅在应用程序服务中强制验证和保存实体?如果我有实体A,B和C,并且它们彼此相关(A-> B-> C),并且对A的操作应该通过从另一个域服务调用一个域服务来引起对B和C的更改,该怎么做?
MrNVK

>是否必须仅在Application Services中验证和保存实体?如果需要,那么可以。大多数时候,您必须检查ID是否存在,因为否则您将使用null变量。
notnotmatter

1
>如果我拥有实体A,B和C,并且它们彼此相关(A-> B-> C),并且在A上进行的操作应通过从另一个服务调用一个域服务来引起B和C的更改,该如何做?我不确定“从另一个调用一个域服务”是什么意思,但是对于对实体更改的反应,您可以使用“事件”或仅将其与应用程序服务进行编排,例如:aggregateA.doOperation(),aggregateB.doAnother( )。搜索:编排与编排
无关重要

谢谢您的回复!“从另一个调用一个域服务”-我的意思是,如果对实体A进行复杂的操作,则必须使用ADomainService。但是,除了实体A之外,此操作还会影响实体B。必须在ADomainService中对实体B执行的操作也很复杂。因此,我必须使用ADomainService中的BDomainService。现在,我对这种方法表示怀疑:)但是,如果我将这种逻辑放入ApplicationService中,会不会破坏只应在域层而不是在应用层的业务流程的封装?
MrNVK

如果您认为它应该在域服务而​​不是应用程序服务中,则只能从域服务中发出事件。
donnotmatter '19

0

域服务视为在对象上实现业务逻辑或与业务规则相关的逻辑的对象,并且该逻辑很难放入相同的域对象中,并且也不会引起域服务的状态更改(域服务是没有对象的对象(“状态”或更好的状态,没有具有业务意义的状态),但最终仅更改对其进行操作的域对象的状态。

虽然应用程序服务将应用程序级逻辑实现为用户交互,输入验证,与业务无关但与其他问题(身份验证,安全性,电子邮件等)无关的逻辑,从而将自身限制为仅使用域对象公开的服务。

例如,以下情况只是出于解释目的而考虑:我们必须实现一个很小的domotic实用应用程序,该应用程序执行一个简单的操作,即“当有人打开房屋房间的门进入房间时,开灯”当关上从房间出来的门时,请关上并关闭灯。”

为简化起见,我们仅考虑2个域实体:DoorLamp,每个实体分别具有2个状态,分别为open/closedon/off,以及用于对其进行状态更改的特定方法。

在这种情况下,我们需要一个域服务,当有人从外面打开门进入房间时,服务会执行打开灯的特定操作,因为门和灯对象无法以我们认为合适的方式实现此逻辑他们的天性

我们可以将域服务称为,DomoticDomainService并实现2个方法:OpenTheDoorAndTurnOnTheLightCloseTheDoorAndTurnOffTheLight,这2个方法分别更改对象Door和以及Lampopen/on和的状态closed/off

进入或退出房间的状态在域服务对象和域对象中都不存在,但是将由应用程序服务实现为简单的用户交互,我们可以将其称为HouseService,将某些事件处理程序实现为onOpenRoom1DoorToEnteronCloseRoom1DoorToExit,等等,对于每个房间(这只是一个说明目的的示例。),这将分别涉及用于执行出席行为的调用域服务方法(我们没有考虑实体,Room因为它只是一个示例)

该示例远不是一个设计良好的现实应用程序,它的唯一目的(如更多次所述)旨在解释域服务是什么以及它与应用程序服务的区别,希望它清楚且有用。


Ciro:您的例子不切实际,而且非常令人困惑。
Morteza Azizi

嗨,Morteza,您能具体一点吗?没有任何真实论据,您的风险只是“判断”。谢谢
Ciro Corvino
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.