原文链接: https://dev.to/salah856/implementing-domain-driven-design-part-i-5a72
简单的代码!
踢足球很简单,难的是踢简单的足球。— 克鲁伊夫
如果我们将这句话用到编程上,我们可以说;
写代码很简单,难的是写简单的代码。
什么是领域驱动设计?
领域驱动设计(DDD)是一种软件开发方法,通过将实现结合不断发展的模型来满足复杂的需求。
DDD适用于复杂的领域和大规模的应用程序,而不是简单的CRUD应用程序。
它专注于核心领域逻辑,而不是基础设施细节。它有助于构建灵活、模块化和可维护的代码库。
OOP & SOLID
DDD的实现高度依赖于面向对象编程(OOP)和SOLID原则。
实际上,它实现并扩展了这些原则。因此,对OOP和SOLID的良好理解对你实现DDD有很大帮助。
DDD分层和整洁架构
基于领域驱动的解决方案有四个基本分层。
业务逻辑分为两层,领域层和应用层,它们包含不同类型的业务逻辑;
领域层实现领域/系统的核心、独立于用例的业务逻辑。
应用层基于领域实现应用的用例。可以将用例视为用户界面 (UI) 上的用户交互。
表示层包含应用程序的UI元素(页面、组件)。
基础设施层通过实现对第三方库和系统的抽象和集成来支持其他层。
相同的分层可以如下图所示,称为整洁架构,有时也称为洋葱架构:
核心构建块
DDD主要关注领域层和应用层,而忽略了表示层和基础设施。它们被视为细节,业务层不应依赖它们。
这并不意味着表示层和基础设施层不重要。
它们非常重要。UI框架和数据库提供商有自己的规则和最佳实践,你需要了解和应用它们。但是,这些不在DDD的主题中。
本节介绍领域层和应用层的基本构建块。
领域层构建块
实体Entity:实体是具有自己的属性(状态、数据)和实现在这些属性上执行的业务逻辑的方法的对象。实体由其唯一标识符 (Id) 表示。具有不同Id的两个实体对象被视为不同的实体。
值对象Value Object:值对象是另一种领域对象,由其属性而不是唯一的Id进行标识。这意味着具有相同属性的两个值对象被视为同一个对象。值对象通常被实现为不可变的,并且大多比实体简单得多。
聚合Aggregate和聚合根Aggregate Root:聚合是由聚合根对象绑定在一起的一组对象(实体和值对象)。聚合根是具有一些额外职责的特定类型的实体。
仓储Repository(接口):仓储是一个类似于接口的集合,领域和应用层使用它来访问数据持久性系统(数据库)。它对业务代码隐藏了DBMS的复杂性。领域层包含存储库的接口。
领域服务Domain Service:领域服务是实现领域核心业务规则的无状态服务。它是对于实现依赖于多个聚合(实体)类型或一些外部服务的领域逻辑很有用。
规范Specification:规范用于为实体和其他业务对象定义命名的、可重用的和可组合的过滤器。
领域事件Domain Event:领域事件是一种在发生领域特定事件时以松散耦合的方式通知其他服务的方式。
应用层构建块
应用服务Application Service:应用服务是无状态的实现应用用例的服务。应用服务通常会获取并返回DTO。它由表示层使用。它使用和协调领域对象来实现用例。用例通常被视为一个工作单元。
数据传输对象(DTO):DTO是一个没有任何业务逻辑的简单对象,用于在应用层和表示层之间传输状态(数据)。
工作单元(UOW):工作单元是应作为事务单元完成的原子工作。UOW中的所有操作都应该在成功时提交或在失败时回滚。
实现: 整体结构
.NET解决方案
下图显示了使用ABP的应用程序启动模板创建的Visual Studio解决方案:
领域层
领域层分为两个项目;
IssueTracking.Domain,是基本的领域层,包含之前介绍的所有构建块(实体、值对象、领域服务、规范、仓储库接口等)。
IssueTracking.Domain.Shared,是一个精简项目,其中包含一些属于领域层但与所有其他层共享的类型。例如,它可能包含一些与领域对象相关的常量和枚举,但需要被其他层重用。
应用层
应用层也分为两个项目;
IssueTracking.Application.Contracts,包含应用服务接口和这些接口使用的DTO。该项目可以由客户端应用程序(包括 UI)共享。
IssueTracking.Application,是实现Contracts项目中定义的接口的基本应用层。
表示层
IssueTracking.Web,是此示例的ASP.NET Core MVC/Razor Pages应用程序。这是为应用程序和API提供服务的唯一可执行应用程序。
远程服务层
IssueTracking.HttpApi,包含解决方案定义的HTTP API。它通常包含MVC控制器和相关模型(如果可用)。因此,在此项目中编写HTTP API。
IssueTracking.HttpApi.Client,一个需要使用你的HTTP API的C#应用程序。一旦客户端应用程序引用了这个项目,它就可以直接注入和使用应用程序服务。这可以在ABP框架的动态C#客户端API代理系统的帮助下实现。
基础设施层
在DDD实现中,你可能有一个基础设施项目来实现所有的抽象和集成,或者可能对每个依赖项都有不同的项目。
我们建议采取平衡的方法;为主要基础设施依赖项(如 Entity Framework Core)创建单独的项目,为其他基础设施创建一个通用基础设施项目。
ABP的启动解决方案有两个用于Entity Framework Core集成的项目;
IssueTracking.EntityFrameworkCore,是EF Core必不可少的集成包。应用程序的DbContext、数据库映射、存储库的实现以及其他与EF Core相关的内容都位于此处。
IssueTracking.EntityFrameworkCore.DbMigrations,是一个用于管理Code First数据库迁移的特殊项目。这个项目中有一个单独的DbContext来跟踪迁移。除了需要创建新的数据库迁移或添加具有一些数据库表并且需要创建新的数据库迁移的应用程序模块外,通常不会过多地接触这个项目。
还有一个项目,IssueTracking.DbMigrator,它是一个简单的控制台应用程序,它迁移数据库模式并在执行它时初始种子数据。
它是一个有用的实用程序,你可以在开发和生产环境中使用它。
解决方案中项目的依赖关系
基于DDD的应用程序执行流程
下图显示了基于DDD模式开发的Web应用程序的典型请求流。
请求通常从用户界面(一个用例)上的用户交互开始,该交互导致对服务器的HTTP请求。
表示层(或分布式服务层)中的MVC控制器或Razor页面处理程序处理请求,并可以在此阶段执行一些横切关注点(授权、验证、异常处理等)。控制器/页面注入相关的应用程序服务接口并通过发送和接收DTO调用其方法。
应用服务使用领域对象(实体、存储库接口、领域服务等)来实现用例。应用层实现了一些横切关注点(授权、验证等)。应用程序服务方法应该是一个工作单元。这意味着它应该是原子的。
大多数横切关注点都是由ABP框架自动且按惯例实现的,你通常不需要为它们编写代码。
数据库提供者/ORM 独立性
领域层和应用层应该与ORM/数据库提供者无关。
它们应该只依赖于Repository接口,并且Repository接口不使用任何ORM特定对象。
使用这个原则的主要原因如下;
使你的领域/应用程序基础架构独立,因为基础架构将来可能会发生变化,或者你以后可能需要支持第二种数据库类型。
使你的领域/应用程序专注于业务。通过隐藏仓储后面的基础设施细节来编写代码。
为了使你的自动化测试更容易,因为你可以在这种情况下模拟存储库。
如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“