领域逻辑 & 应用逻辑

如前所述,领域驱动设计中的业务逻辑分为两部分(层):领域逻辑和应用逻辑:

  • 领域逻辑由系统的核心领域规则组成,应用逻辑实现应用特定的用例

虽然定义很明确,但实现起来可能并不容易。您可能无法决定哪些代码应该位于应用程序层,哪些代码应该位于领域层。本节试图解释其中的差异

多个应用程序层

当系统比较大时,DDD有助于处理复杂性。特别是,如果在一个领域中开发了多个应用程序,那么领域逻辑与应用程序逻辑的分离就变得重要得多。

假设您正在构建一个具有多个应用程序的系统

  • 一个网站应用程序,用 ASP.NET Core MVC 构建,向用户展示你的产品。这样的网站不需要认证就可以看到产品。用户只有在执行某些操作(比如将产品添加到购物车中)时才会登录到网站。

  • 一个后台管理程序,使用 Angular UI 构建(使用REST APIs)。本应用被公司办公人员使用来管理系统(如编辑产品描述)

  • 一个移动应用程序, 与网站相比,它具有更简单的UI。它可以通过 REST APIs 或其他技术(如TCP套接字)与服务器通信。

每个应用程序都有不同的需求、不同的用例(应用服务方法)、不同的dto、不同的验证和授权规则……等

如果将所有这些逻辑混合到单个应用程序层会使您的服务包含太多的逻辑,使代码更难开发、维护和测试,并导致潜在的bug

如果一个领域有多个应用程序:

  • 为每个应用程序/客户端类型创建单独的应用程序层,并在这些单独的层中实现应用程序特定的业务逻辑。
  • 使用单个领域层共享核心领域逻辑。

这样的设计使得区分领域逻辑和应用程序逻辑变得更加重要。

为了更清楚地了解实现,您可以为每个应用程序类型创建不同的项目(.csproj)。例如:

  • 后台管理应用: IssueTracker.Admin.Application & IssueTracker.Admin.Application.Contracts

  • 公共网站应用: IssueTracker.Public.Application & IssueTracker.Public.Application.Contracts

  • 移动应用: IssueTracker.Mobile.Application & IssueTracker.Mobile.Application.Contracts

案例

本节包含一些应用程序服务和领域服务示例,以讨论如何决定将业务逻辑放置在这些服务中

示例:在领域服务中新建组织

public class OrganizationManager : DomainService
{
//省略了依赖注入
public async Task<Organization> CreateAsync(string name)
{
if(await _organizationRepository.AnyAsync(x => x.Name == name))
{
throw new BusinessException("IssueTracking:DuplicateOrganizationName");
} await _authorizationService.CheckAsync("OrganizationCreationPermissin"); Logger.LogDebug($"Creating organization {name} by {_currentUser.UserName}"); var organization = new Organization(); await _emailSender.SendAsync(
"admin@issuetracking.com",
"New Organization",
"A new organization created with name: " + name
); return organization;
}
}

让我们一步一步地看看 CreateAsync 方法,来讨论代码部分是否应该放在领域服务中

  • 正确: 它首先检查重复的组织名称,并在这种情况下抛出异常。这与核心领域规则有关,我们不允许重复名称

  • 错误: 领域服务不应该执行授权。授权 应该在应用层中完成。

  • 错误: 它记录了包含当前用户的用户名的消息。领域服务不应该依赖于当前用户。即使系统中没有用户,领域服务也应该可用。当前用户(会话)应该是一个与表示/应用层相关的概念。

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

示例:在应用服务中新建组织

public class OrganizationAppService : ApplicationService
{
//省略了依赖注入 [UnitOfWork]
[Authorize("OrganizationCreationPermissin")]
public async Task<Organization> CreateAsync(CreateOrganizationDto input)
{
await _paymentService.ChargeAsync(
CurrentUser.Id,
GetOrganizationPrice()
); var organization = await _organizationManager.CreateAsync(input.Name); await _organizationManager.InsertAsync(organization); await _emailSender.SendAsync(
"admin@issuetracking.com",
"New Organization",
"A new organization created with name: " + input.Name
); return organization; //!!!
} private double GetOrganizationPrice()
{
return 42.0; //或者从其他地方获取
}
}

让我们一步一步地看看 CreateAsync 方法,来讨论代码部分是否应该放在应用程序服务中

  • 正确: 应用程序服务方法应该是工作单元(事务)。ABP的 工作单元 系统使这个自动完成(甚至不需要为应用服务添加 [UnitOfWork] 属性)。

  • 正确: 授权 应该在应用层完成。这里,它是通过使用 [Authorize] 属性来完成的

  • 正确: 调用支付(基础设施服务)来为该操作收费(创建组织在我们的业务中是一种付费服务)

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

  • 正确: 我们可以发送 电子邮件 通知系统管理员

  • 错误: 不要从应用程序服务返回实体。而是返回一个DTO。

讨论:为什么我们不将支付逻辑转移到领域服务中?

您可能想知道为什么支付代码不在 OrganizationManager 中。这是一件很重要的事情,我们不想错过付款

然而,仅仅重要还不足以将代码视为核心业务逻辑。 我们可能还有其他的用例,在这些用例中创建一个新的 Organization 是不需要收费的。比如:

  • 管理员用户可以使用后台应用程序创建新的组织,而无需支付任何费用

  • 后台工作的数据导入/集成/同步系统也可能需要创建没有任何支付操作的组织。

如您所见,支付不是创建有效组织的必要操作。它是特定于用例的应用程序逻辑。

示例: 增删改查 操作

public class IssueAppService
{
private readonly IssueManager _issueManager;
public IssueAppService(IssueManager issueManager)
{
_issueManager = issueManager;
} public async Task<IssueDto> GetAsync(Guid id)
{
return _issueManager.GetAsync(id);
} public async Task CreateAsync(IssueCreationDto input)
{
return _issueManager.CreateAsync(input);
} public async Task UpdateAsync(UpdateIssueDto input)
{
return _issueManager.UpdateAsync(input);
} public async Task DeleteAsync(Guid id)
{
return _issueManager.DeleteAsync(id);
}
}

这个应用程序服务本身不做任何事情,而是将所有工作委托给领域服务。它甚至将 dto 传递给 IssueManager

  • 不要仅仅为没有任何领域逻辑的简单CRUD操作创建领域服务。
  • 永远不要向领域服务传递dto或从领域服务返回dto。

应用程序服务可以直接使用存储库来查询、创建、更新或删除数据,除非在这些操作期间需要执行一些领域逻辑。在这种情况下,创建 Domain Service 方法,但只针对那些真正需要的方法

如果你对领域驱动设计和构建大型企业系统更感兴趣,推荐以下书籍作为参考书:

  • "Domain Driven Design" by Eric Evans
  • "Implementing Domain Driven Design" by Vaughn

    Vernon
  • "Clean Architecture" by Robert C. Martin

完结

实现领域驱动设计 - 使用ABP框架 - 领域逻辑 & 应用逻辑的更多相关文章

  1. 实现领域驱动设计 - 使用ABP框架 - 什么是领域驱动设计?

    前言: 最近看到ABP官网的一本电子书,感觉写的很好,翻译出来,一起学习下 (Implementing Domain Driven Design) https://abp.io/books DDD简介 ...

  2. 实现领域驱动设计 - 使用ABP框架 - 通用准则

    在进入细节之前,让我们看看一些总体的 DDD 原则 数据库提供者 / ORM 无关性 领域和应用程序层应该与 ORM / 数据库提供程序 无关.它们应该只依赖于 Repository 接口,而 Rep ...

  3. 实现领域驱动设计 - 使用ABP框架 - 解决方案概览

    .NET解决方案的分层 下图显示了使用ABP的 应用启动模板 创建的Visual Studio解决方案: 解决方案名称为问题跟踪,它由多个项目组成.通过考虑DDD原则以及开发和部署实践,该解决方案是分 ...

  4. 实现领域驱动设计 - 使用ABP框架 - 存储库

    存储库 Repository 是一个类似于集合的接口,领域层和应用程序层使用它来访问数据持久性系统(数据库),以读写业务对象(通常是聚合) 常见的存储库原则是: 在领域层定义一个存储库接口(因为它被用 ...

  5. 实现领域驱动设计 - 使用ABP框架 - 创建实体

    用例演示 - 创建实体 本节将演示一些示例用例并讨论可选场景. 创建实体 从实体/聚合根类创建对象是实体生命周期的第一步.聚合/聚合根规则和最佳实践部分建议为Entity类创建一个主构造函数,以保证创 ...

  6. 基于ABP落地领域驱动设计-06.正确区分领域逻辑和应用逻辑

    目录 系列文章 领域逻辑和应用逻辑 多应用层 示例:正确区分应用逻辑和领域逻辑 学习帮助 系列文章 基于ABP落地领域驱动设计-00.目录和前言 基于ABP落地领域驱动设计-01.全景图 基于ABP落 ...

  7. 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

    前言 领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已. 互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂. 不过,这些文章对于那些初学者而 ...

  8. .net core +codefirst(.net core 基础入门,适合这方面的小白阅读) 【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

    .net core +codefirst(.net core 基础入门,适合这方面的小白阅读)   前言 .net core mvc和 .net mvc开发很相似,比如 视图-模型-控制器结构.所以. ...

  9. 基于ABP落地领域驱动设计-00.目录和小结

    <实现领域驱动设计> -- 基于 ABP Framework 实现领域驱动设计实用指南 翻译缘由 自 ABP vNext 1.0 开始学习和使用该框架,被其优雅的设计和实现吸引,适逢 AB ...

  10. 基于ABP落地领域驱动设计-05.实体创建和更新最佳实践

    目录 系列文章 数据传输对象 输入DTO最佳实践 不要在输入DTO中定义不使用的属性 不要重用输入DTO 输入DTO中验证逻辑 输出DTO最佳实践 对象映射 学习帮助 系列文章 基于ABP落地领域驱动 ...

随机推荐

  1. 长连接网关技术专题(十一):揭秘腾讯公网TGW网关系统的技术架构演进

    本文由腾讯技术团队peter分享,原题"腾讯网关TGW架构演进之路",下文进行了排版和内容优化等. 1.引言 TGW全称Tencent Gateway,是一套实现多网统一接入,支持 ...

  2. Swagger UI、RESTful简介

    Swagger UI 简介 Swagger UI允许任何人(无论您是开发团队还是最终用户)都可以可视化API资源并与之交互,而无需任何实现逻辑.它是根据您的OpenAPI(以前称为Swagger)规范 ...

  3. 隐藏zsh前面用户名或者主机名

    1. zshrc 修改 vim ~/.zshrc文件,在文件底部增加: prompt_context() {} 只保留用户名,隐藏主机名 prompt_context() { if [[ " ...

  4. 了解 ASP.NET Core 中的中间件

    在 .NET Core 中,中间件(Middleware) 是处理 HTTP 请求和响应的核心组件.它们被组织成一个请求处理管道,每个中间件都可以在请求到达最终处理程序之前或之后执行操作.中间件可以用 ...

  5. CDS标准视图:优先级数据 I_GenericPriorityData

    视图名称:优先级数据 I_GenericPriorityData 视图类型:基础视图 视图代码: 点击查看代码 @AbapCatalog.sqlViewName: 'IGENERICPRIODATA' ...

  6. Swagger介绍和应用

    1.什么是swaggerSwagger是一个规范和完整的框架,用于生成.描述.调用和可视化RESTful风格的Web服务.简单来说,Swagger是一个功能强大的接口管理工具,并且提供了多种编程语言的 ...

  7. java代码之美(3)

    Map中的computeIfAbsent方法 Map接口的实现类如HashMap,ConcurrentHashMap,HashTable等继承了此方法,通过此方法可以在特定需求下,让你的代码更加简洁. ...

  8. [学习笔记]最近公共祖先(LCA)之倍增算法

    1.定义 倍增法,顾名思义就是翻倍.它能够大大地优化时间复杂度.这个方法在很多算法中均有应用,例如求 LCA(最近公共祖先).(大雾) 2.框架 如下图,我们想找 \(4\) 和 \(8\) 的最近公 ...

  9. 富数-AnonymFL

    本文学习文章"2022 WAIC|「全匿踪联邦学习」AnonymFL正式发布:破解用户ID暴露难题,实现真正合规可信的隐私计算",记录笔记. 引言 2022年08月26日,富数科技 ...

  10. Phi小模型开发教程:C#使用本地模型Phi视觉模型分析图像,实现图片分类、搜索等功能

    大家好,我是编程乐趣. 我们都知道,要实现对结构化的数据(文本)搜索是比较容易的,但是对于非结构化的数据,比如图片,视频就没那么简单了. 但是现在有了AI模型,实现图片分类.搜索等功能,就变得容易很多 ...