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

Chr*_*ris 243 architecture domain-driven-design

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

Vij*_*tel 325

服务有三种形式:域服务,应用服务基础设施服务.

  • 域服务:封装 不自然地适合域对象的业务逻辑,并且不是典型的CRUD操作 - 那些属于存储库.
  • 应用程序服务:由外部使用者用于与您的系统通信(想想Web服务).如果消费者需要访问CRUD操作,他们就会在这里公开.
  • 基础设施服务:用于抽象技术问题(例如MSMQ,电子邮件提供商等).

保持域服务和域对象是明智的 - 它们都专注于域逻辑.是的,您可以将存储库注入您的服务.

Application Services通常使用域服务存储库来处理外部请求.

希望有所帮助!

  • 我认为应用服务应该独立于"Web服务"之类的技术细节,它们被这些服务使用.请参阅[域驱动设计中的服务](http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/) (5认同)
  • 在使用任何类型的服务类之前请三思。很可能你可以[没有它们](https://hackernoon.com/you-dont-need-a-domain-service-class-in-ddd-9ecd3140782)并且你的代码最终会变得更加清晰和可维护。 (3认同)
  • 你会把CQRS的命令和查询放在哪里?哪个服务生成它们以及哪个服务处理它们? (2认同)
  • 这是一个很好的答案,但它需要参考书籍和作者(以使其更好)。 (2认同)

Nie*_*est 108

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

我也一直在努力精确定义应用程序服务.尽管维杰的回答对我一个月前的思考过程非常有帮助,但我对其中的一部分提出了不同意见.

其他资源

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

MSDN杂志文章"域驱动设计简介"将应用程序服务描述为将域模型转换和/或公开给外部客户端的方法,例如作为WCF服务.这就是Vijay如何描述应用程序服务.从这个角度来看,应用程序服务是接口.

杰弗里巴勒莫的洋葱架构的文章(部分一个,23)是一个很好看的.他将应用程序服务视为应用程序级概念,例如用户会话.虽然这更接近我对应用程序服务的理解,但它仍然不符合我对该主题的看法.

我的想法

我将应用程序服务视为应用程序提供的依赖项.在这种情况下,应用程序可以是桌面应用程序或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

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.
}
Run Code Online (Sandbox Code Playgroud)

注意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);
Run Code Online (Sandbox Code Playgroud)

摘要

完整的应用程序包含三个主要层:

  • 基础设施
  • 应用

域层包含域实体和独立域服务.依赖于外部资源的任何域概念(包括域服务,还包括存储库)都由接口定义.

基础结构层包含域层的接口实现.这些实现可能引入必须为应用程序提供的新的非域依赖性.这些是应用程序服务,由接口表示.

应用程序层包含应用程序服务的实现.如果基础设施层提供的实现不充分,则应用层还可以包含域接口的附加实现.

尽管此透视图可能与服务的常规DDD定义不匹配,但它确实将域与应用程序分开,并允许您在多个应用程序之间共享域(和基础结构)程序集.

  • 我不同意你不同意Vijay的部分,这就是原因.CachingExchangeRateService是一个基础架构问题.即使您通常接受ICache,该ICache的实现依赖于所涉及的技术(即Web,Windows).仅仅因为它的通用性并不能使它成为一种应用服务.应用程序服务是您域的API.如果您想向其他人编写应用程序显示您的域名,他们将使用什么?应用服务,他们可能不需要缓存,所以你的缓存impl对他们没用(即,为什么它的基础设施) (6认同)
  • @Tiendq:在传统的分层架构中,基础架构通常与域无关.但在洋葱架构中(参见我的答案中的链接),基础架构实现了域的外部依赖关系.但我不会说基础设施*取决于域名,只是*引用*它.我从洋葱建筑中采用了"基础设施"一词,但"外部"可能是一个更好的名称. (3认同)
  • @ dario-g:您必须从请求模型重建/重新填充域模型,并将域模型传递给域服务.[这个问题](http://stackoverflow.com/q/2206005/332100)可能会为您提供一些想法.如果没有,请告诉我,我会看看是否有时间为另一个问题添加答案. (2认同)

Gho*_*ola 37

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

我的"啊哈"时刻是由两件事引发的:

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

域服务以无处不在的语言和域类型表示,即方法参数和返回值是适当的域类.

  • 阅读这篇博文,特别是这部分内容:

我发现在将苹果与橙子分开方面有很大帮助,从应用工作流程的角度思考.有关应用程序工作流的所有逻辑通常最终都是应用程序服务因素,而应用程序服务因素最终构成一个或多个域服务.

  • GitHub链接到该DDD货物示例:https://github.com/citerus/dddsample-core (3认同)
  • 我同意,这正是我定义应用服务的方式,它适合我到目前为止遇到的所有情况.域服务处理与域对象相关的所有内容,但这超出了单个实体的范围.例如:BookReferencesService.GetNextAvailableUniqueTrackingNumber(),重点显然是业务规则*.关于应用程序服务,它正是您所描述的,大部分时间我将此业务工作流程放入我的控制器操作中,当我注意到它时,我在应用程序服务层重构此逻辑.我们可以说这个层是用于用例的 (2认同)

Tim*_*imo 30

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

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

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

应用程序服务处理用例流,包括域之上所需的任何其他问题.它经常通过其API公开此类方法,供外部客户使用.为了构建我们之前的示例,我们的应用程序服务可能会公开一个方法LendBookToClient(Guid bookGuid, Guid clientGuid):

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

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

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

  • 我想说的是,“应用程序服务”也是解决依赖关系的关键。它的方法是一个用例,一个流程,因此它可以针对要使用的具体实现做出明智的决策。数据库事务也适用于此。 (2认同)
  • 就我个人而言,我发现“权限”通常比人们想象的更多地是一个领域问题。例如,“只向信誉良好的客户借书”对我来说听起来像是一条商业规则。当然,您可能需要一些应用程序级逻辑来将原始权限等内容转换为“客户端”实体的域级“常设”状态。 (2认同)
  • @Ali.Rashidi 实现“LendBook”的应用程序服务可能: 执行身份验证和授权。解释并验证输入合约模型(例如“LendBookRequest”)。加载“Client”和“Book”。确认它们存在或使请求无效。调用领域服务来执行领域逻辑。将结果调整为输出合约模型(例如“LendBookResponse”)。_(请记住,虽然领域模型可以自由更改,但契约模型很难更改。它需要稳定,成为 API 的一部分,由外部上下文使用。)_ (2认同)

kbo*_*oom 28

域服务的扩展.它应该只在域的上下文中看到.这不是一些用户操作,例如关闭帐户或其他东西.域服务适用于没有状态的地方.否则它将是一个域对象.域服务只有在与其他协作者(域对象或其他服务)完成时才会有意义.这使得感觉是另一层的责任.

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

一般来说,存储库可以注入域服务,但这种情况相当罕见.虽然应用层大多数时间都是这样做的.

  • "域服务适用于没有状态的地方.否则它将成为域对象." 让它为我点击.谢谢. (10认同)

Cir*_*ino 18

领域服务视为在领域对象上实现业务逻辑或业务规则相关逻辑的对象,该逻辑很难适应同一个领域对象,也不会引起领域服务的状态变化(领域服务是一个不存在任何状态的对象)一个“状态”或更好的状态,没有具有商业意义的状态),但最终仅更改所操作的域对象的状态。

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

一个例子可能是以下仅用于解释目的的场景:我们必须实现一个非常小的家庭自动化实用应用程序,它执行一个简单的操作,即“当有人打开房间的门进入时,打开灯”关上门离开房间时关灯”。

简化很多,我们只考虑 2 个域实体,它们不属于同一聚合:DoorLamp,每个实体都有 2 个状态,分别为open/closedon/off,以及对它们进行状态更改的特定方法。这些实体需要是不同聚合的一部分,以便以下逻辑无法在聚合根中实现。

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

我们可以将我们的领域服务称为DomoticDomainService并实现 2 个方法:OpenTheDoorAndTurnOnTheLight和,这两个方法分别将对象和 的CloseTheDoorAndTurnOffTheLight状态更改为和。DoorLampopen/onclosed/off

The state of enter or exit from a room it isn't present in the domain service object and either in the domain objects, but will be implemented as simple user interaction by an application service, that we may call HouseService, that implements some event handlers as onOpenRoom1DoorToEnter and onCloseRoom1DoorToExit, and so on for each room (this is only an example for explaining purpose..), that will respectively concern about call domain service methods to execute the attended behaviour (we haven't considered the entity Room because it is only an example).

This example, far to be a well designed real world application, has the only purpose (as more times said) to explain what a Domain Service is and its difference from an Application Service, hope it is clear and useful.

Also, the example domain service above could easily be replaced by domain events which are used to explicitly implement side effects across one or multiple aggregates, but since these are not the subject of this question, I only mention them here so the reader can be aware of their existence and later decide which approach is better for them.

  • 你好,Morteza,你能说得更具体一些吗?你的风险可能只是一个“判断”,没有任何真正的论据。谢谢 (7认同)

Gor*_*ulu 8

域服务:域服务中包含不真正适合单个实体或需要访问存储库的方法.域服务层还可以包含其自己的域逻辑,并且作为实体和值对象的域模型的一部分.

应用程序服务:应用程序服务是位于域模型上方并协调应用程序活动的薄层.它不包含业务逻辑,也不包含任何实体的状态; 但是,它可以存储业务工作流事务的状态.您使用Application服务使用Request-Reply消息传递模式将API提供到域模型中.

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


doe*_*ter 8

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

  • 你有 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);
}

Run Code Online (Sandbox Code Playgroud)

您可以在此处进行验证,例如检查 aProduct是否唯一。除非Product唯一性是不变量,否则它应该是可能被调用的域服务的一部分,UniqueProductChecker因为它不能是Product类的一部分并且它与多个聚合进行交互。

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

你可以找到很多应用服务和领域服务的例子