实现领域驱动设计——领域逻辑与应用逻辑

As mentioned before, Business Logic in the Domain Driven Design is split into two parts (layers): Domain Logic and Application Logic:

正如前面所提到的,领域驱动设计(DDD)中的业务逻辑被分成了两个部分(层次):领域逻辑和应用逻辑:

image

Domain Logic consists of the Core Domain Rules of the system while Application Logic implements application specific Use Cases.

领域逻辑(Domain Logic) 由系统的核心业务规则组成,而 应用逻辑(Application Logic) 则负责实现特定于应用程序的用例(Use Cases)。

While the definition is clear, the implementation may not be easy. You may be undecided which code should stand in the Application Layer, which code should be in the Domain Layer. This section tries to explain the differences.

虽然定义很清晰,但实现起来可能并不容易。你可能会犹豫,究竟哪部分代码应该放在应用层(Application Layer),哪部分应该放在领域层(Domain Layer)。本节试图解释这两者的区别。

Multiple Application Layers    多应用层(Multiple Application Layers)场景

DDD helps to deal with complexity when your system is large. Especially, if there are multiple applications are being developed in a single domain, then the Domain Logic vs Application Logic separation becomes much more important.

当你的系统规模很大时,DDD 有助于处理这种复杂性。特别是,如果在一个单一的领域内正在开发多个应用程序,那么将“领域逻辑”与“应用逻辑”分离开来就变得至关重要。

Assume that you are building a system that has multiple applications:

假设你正在构建一个拥有多款应用程序的系统:

  • A Public Web Site Application, built with ASP.NET Core MVC, to show your products to users. Such a web site doesn't require authentication to see the products. The users login to the web site, only if they are performing some actions (like adding a product to the basket).

    一个公共网站应用:使用 ASP.NET Core MVC 构建,用于向用户展示产品。这种网站不需要登录认证即可浏览产品,只有在用户执行某些操作时(例如将产品加入购物车)才需要登录。

  • A Back Office Application, built with Angular UI (that uses REST APIs). This application used by office workers of the company to manage the system (like editing product descriptions).

    一个后台管理应用:使用 Angular 前端(调用 REST APIs)。该应用供公司内部的办公室员工使用,用于管理系统(例如编辑产品描述)。

  • A Mobile Application that has much simpler UI compared to the Public Web Site. It may communicate to the server via REST APIs or another technology (like TCP sockets).

    一个移动应用:相比公共网站,它的用户界面要简单得多。它可能通过 REST API 或其他技术(例如 TCP 套接字)与服务器通信。

image

Every application will have different requirements, different use cases (Application Service methods), different DTOs, different validation and authorization rules... etc.

每个应用程序都会有不同的需求、不同的用例(应用服务方法)、不同的 DTO(数据传输对象)、不同的验证和授权规则……等等。

Mixing all these logics into a single application layer makes your services contain too many if conditions with complicated business logic, makes your code harder to develop, maintain and test, and leads to potential bugs.

如果将所有这些逻辑都混杂在同一个应用层中,会导致你的服务里充斥着大量的 if 条件判断和复杂的业务逻辑。这会让你的代码变得难以开发、维护和测试,并容易引发潜在的 Bug。

If you've multiple applications with a single domain:

如果你在单一领域下有多个应用程序,建议采取以下做法:

  • Create separate application layers for each application/client type and implement application specific business logic in these separate layers.

    为每种应用程序/客户端类型创建独立的应用层,并在这些独立的层中实现特定于该应用程序的业务逻辑。

  • Use a single domain layer to share the core domain logic.

    使用单一的领域层来共享核心领域逻辑。

Such a design makes it even more important to distinguish between Domain logic and Application Logic.

这样的设计使得区分“领域逻辑”和“应用逻辑”变得尤为重要。

To be more clear about the implementation, you can create different projects (.csproj) for each application types.

为了更清晰地实现这一点,你可以为每种应用程序类型创建不同的项目文件(.csproj)。

For example:

例如:

  • IssueTracker.Admin.Application & IssueTracker.Admin.Application.Contracts projects for the Back Office (admin) Application.

    为后台管理(Admin)应用创建 IssueTracker.Admin.ApplicationIssueTracker.Admin.Application.Contracts 项目。

  • IssueTracker.Public.Application & IssueTracker.Public.Application.Contracts projects for the Public Web Application.

    为公共网站(Public Web)应用创建 IssueTracker.Public.ApplicationIssueTracker.Public.Application.Contracts 项目。

  • IssueTracker.Mobile.Application & IssueTracker.Mobile.Application.Contracts projects for the Mobile Application.

    为移动应用(Mobile)创建 IssueTracker.Mobile.ApplicationIssueTracker.Mobile.Application.Contracts 项目。

Examples    示例

This section contains some Application Service and Domain Service examples to discuss how to decide to place business logic inside these services.

本节包含了一些应用服务和领域服务的示例,我们将通过这些例子来讨论如何决定将业务逻辑放在这些服务中的哪一个里。

Example: Creating a new Organization in a Domain Service    示例:在领域服务中创建一个新的组织机构(Organization)

image

Let's see the CreateAsync method step by step to discuss if the code part should be in the Domain Service, or not:

让我们一步步查看 CreateAsync 方法,来讨论其中的代码部分是否应该放在领域服务(Domain Service)里:

  • Correct: It first checks for duplicate organization name and throws exception in this case. This is something related to core domain rule and we never allow duplicated names.

    正确: 它首先检查是否有重复的组织机构名称,如果有则抛出异常。这属于核心领域规则,我们绝不允许出现重名的情况。

  • Wrong: Domain Services should not perform authorization. Authorization should be done in the Application Layer.

    错误: 领域服务不应该执行授权(权限)检查。授权操作应该在应用层(Application Layer)完成。

  • Wrong: It logs a message including the Current User's UserName. Domain service should not be dependent on the Current User. Domain Services should be usable even if there is no user in the system. Current User (Session) should be a Presentation/Application Layer related concept.

    错误: 它记录了一条包含当前用户(Current User)用户名的日志消息。领域服务不应该依赖于当前用户。即使系统中没有任何用户,领域服务也应该是可以正常使用的。“当前用户(会话)”应该是一个表现层/应用层相关的概念。

  • Wrong: It sends an email about this new organization creation. We think this is also a use case specific business logic. You may want to create different types of emails in different use cases or don't need to send emails in some cases.

    错误: 它发送了一封关于创建新组织机构的邮件。我们认为这也属于特定用例的业务逻辑。在不同的用例中,你可能希望发送不同类型的邮件,或者在某些情况下根本不需要发送邮件。

Example: Creating a new Organization in an Application Service    示例:在应用服务中创建一个新的组织机构(Organization)

image

Let's see the CreateAsync method step by step to discuss if the code part should be in the Application Service, or not:

让我们一步步查看 CreateAsync 方法,来讨论其中的代码部分是否应该放在应用服务(Application Service)里:

  • Correct: Application Service methods should be unit of work (transactional). ABP's Unit Of Work system makes this automatic (even without the need to add [UnitOfWork] attribute for the Application Services).

    正确: 应用服务的方法应该是一个工作单元(Unit of Work,即具备事务性)。ABP 的工作单元(Unit Of Work)系统会自动处理这一点(甚至不需要为应用服务额外添加 [UnitOfWork] 属性)。

  • Correct: Authorization should be done in the application layer. Here, it is done by using the [Authorize] attribute.

    正确: 授权(权限检查)应该在应用层完成。在这里,是通过使用 [Authorize] 属性来实现的。

  • Correct: Payment (an infrastructure service) is called to charge money for this operation (Creating an Organization is a paid service in our business).

    正确: 调用了支付服务(一种基础设施服务)来为本次操作扣费(在我们的业务中,创建组织机构是一项付费服务)。

  • Correct: Application Service method is responsible to save changes to the database.

    正确: 应用服务的方法负责将变更保存到数据库。

  • Correct: We can send email as a notification to the system admin.

    正确: 我们可以发送一封邮件作为通知发给系统管理员。

  • Wrong: Do not return entities from the Application Services. Return a DTO instead.

    错误: 不要从应用服务中直接返回实体(Entity),应该返回数据传输对象(DTO)。

Discussion: Why don't we move the payment logic into the domain service?    讨论:我们为什么不把支付逻辑移到领域服务(Domain Service)里?

You may wonder why the payment code is not inside the OrganizationManager. It is an important thing and we never want to miss the payment.

你可能会纳闷,为什么支付相关的代码没有放在 OrganizationManager(组织机构管理器)里?毕竟这是一件非常重要的事情,我们绝不能漏掉收款。

However, being important is not sufficient to consider a code as a Core Business Logic. We may have other use cases where we don't charge money to create a new Organization. Examples:

然而,“重要”并不足以成为将某段代码视为核心业务逻辑(Core Business Logic)的充分条件。我们可能会遇到其他不需要收费就能创建新组织机构的用例。例如:

  • An admin user can use a Back Office Application to create a new organization without any payment.

    管理员用户可以使用后台管理应用(Back Office Application)来创建一个新的组织机构,而无需支付任何费用。

  • A background-working data import/integration/synchronization system may also need to create organizations without any payment operation.

    一个在后台运行的数据导入/集成/同步系统,可能也需要在不涉及支付操作的情况下创建组织机构。

As you see, payment is not a necessary operation to create a valid organization. It is a use-case specific application logic.

正如你所见,支付并不是创建一个有效组织机构所必需的操作。它属于特定于用例的应用逻辑。

Example: CRUD Operations    示例:CRUD(增删改查)操作

image

This Application Service does nothing itself and delegates all the work to the Domain Service. It even passes the DTOs to the IssueManager.

(反例)这个应用服务自己什么也没做,把所有工作都推给了领域服务。它甚至把 DTO(数据传输对象)直接传给了 IssueManager

  • Do not create Domain Service methods just for simple CRUD operations without any domain logic.

    不要仅仅为了没有任何领域逻辑的简单 CRUD 操作而去创建领域服务的方法。

  • Never pass DTOs to or return DTOs from the Domain Services.

    绝对不要把 DTO 传给领域服务,或者从领域服务返回 DTO。

Application Services can directly work with repositories to query, create, update or delete data unless there are some domain logics that should be performed during these operations. In such cases, create Domain Service methods, but only for those really necessary.

除非在这些操作过程中需要执行某些领域逻辑,否则应用服务(Application Services)可以直接与仓储(Repositories)协作来查询、创建、更新或删除数据。如果确实有这种情况,再创建领域服务的方法,但也仅限于那些真正有必要的方法。

Do not create such CRUD domain service methods just by thinking that they may be needed in the future (YAGNI)! Do it when you need and refactor the existing code. Since the Application Layer gracefully abstracts the Domain Layer, the refactoring process doesn't affect the UI Layer and other clients.

不要仅仅想着“未来可能会用到”,就去创建这种 CRUD 的领域服务方法(切记 YAGNI 原则——你不需要它)!等到真正需要的时候再去做,并对现有代码进行重构。由于应用层很好地抽象并隔离了领域层,这种重构过程并不会影响到 UI 层和其他客户端。

 

posted @ 2026-05-17 16:39  菜鸟吊思  阅读(15)  评论(0)    收藏  举报