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

时间:2010-02-15 20:29:55

标签: architecture domain-driven-design

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

8 个答案:

答案 0 :(得分:296)

服务有三种形式:域名服务应用服务基础架构服务

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

保持域名服务与域对象一起是明智的 - 他们都专注于域逻辑。是的,您可以将存储库注入您的服务。

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

希望有所帮助!

答案 1 :(得分:103)

(如果您不想阅读,请在底部附上摘要: - )

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

其他资源

关于应用程序服务的信息非常少。广泛讨论了聚合根,存储库和域服务等主题,但是应用服务只是简单提及或完全忽略。

MSDN杂志文章An Introduction To Domain-Driven Design将应用程序服务描述为将您的域模型转换和/或公开给外部客户端的方法,例如作为WCF服务。这就是Vijay如何描述应用程序服务。从这个角度来看,应用程序服务是您域的界面

杰弗里·巴勒莫关于洋葱建筑的文章(onetwothree)是一本很好的读物。他将应用程序服务视为应用程序级概念,例如用户会话。虽然这更接近我对应用程序服务的理解,但它仍然不符合我对该主题的看法。

我的想法

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

摘要

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

  • 基础设施
  • 应用

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

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

应用程序层包含应用程序服务的实现。如果基础架构层提供的实现不充分,则应用层还可以包含域接口的其他实现。

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

答案 2 :(得分:33)

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

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

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

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

  

我发现将苹果与橘子分开有很大帮助   思考应用程序工作流程。有关的所有逻辑   应用程序工作流通常最终成为Application Services   在应用层中考虑因素,而来自域的概念   似乎不适合作为模型对象最终形成一个或多个   域名服务。

答案 3 :(得分:23)

来自红皮书(Vaughn Vernon实施领域驱动设计),这就是我理解这些概念的方法:

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

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

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

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

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

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

答案 4 :(得分:21)

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

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

通常可以将存储库注入域服务,但这种情况非常罕见。应用层虽然大部分时间都是这样做的。

答案 5 :(得分:7)

  

域名服务:域内包含不真正适合单个实体或需要访问存储库的方法   服务。域服务层还可以包含域逻辑   它本身就是实体和价值的领域模型的一部分   对象。

     

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

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

答案 6 :(得分:0)

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

  • 您有2个汇总:

    • const api1 = new ApiService("One"); const api2 = new ApiService("Two"); api1.message.receive(); // One recieveMessage api2.message.receive(); // Two receiveMessage api1.media.get(); // One getMedia api2.media.get(); // Two getMedia ,其中包含名称和价格。
    • Product,其中包含购买日期,当时订购的产品列表以及当时的数量和产品价格,以及付款方式。
  • Purchase既不是这两种模型的一部分,也不是您业务中的概念。

  • 可以将
  • Checkout创建为域服务,以获取所有产品并计算总价,并通过调用具有基础结构实现部分的另一个域服务Checkout来支付总价,并将其转换为{ {1}}。

应用程序服务“协调” 或执行域方法的服务。就像您的控制器一样简单。

这是您通常要做的地方:

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类的一部分,并且会与多个聚合交互。

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

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

答案 7 :(得分:0)

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

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

以下示例可能只是为了说明目的而考虑的一个示例:我们必须实现一个很小的domotic实用应用程序,该应用程序执行一个简单的操作,即“当有人打开门时开灯”关闭房间的门,进入房间并关闭电灯”。

简化很多,我们只考虑两个域实体: Door Lamp ,它们各自具有2个状态,分别为{{1 }}和open/closed,以及操作状态更改的特定方法。

在这种情况下,我们需要一个域服务,当有人从外面打开门进入房间时,该服务将执行打开灯的特定操作,因为门和灯对象无法在一种我们认为适合其性质的方式

我们可以将域服务称为 on/off ,并实现两种方法: DomoticDomainService OpenTheDoorAndTurnOnTheLight ,这两种方法分别将两个对象CloseTheDoorAndTurnOffTheLightDoor的状态更改为 Lamp open/on

进入或退出房间的状态在域服务对象和域对象中都不存在,但是将由应用程序服务实现为简单的用户交互,我们可以称之为 { {1}} ,它实现了一些事件处理程序,如 closed/off HouseService ,依此类推,每个房间 (这只是一个说明目的的示例。),将分别关注用于执行伴随行为的调用域服务方法(我们没有考虑实体onOpenRoom1DoorToEnter,因为它是只是一个例子)

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