详解DDD领域驱动设计:从理论到实践掌握DDD分层架构设计
前言
在当今软件开发领域,尤其是微服务和复杂业务系统设计中,领域驱动设计(DDD)已成为一种核心方法论。它不仅仅是一种技术实践,更是一种思想体系,帮助我们从业务本质出发,构建高内聚、低耦合、可维护的软件架构。DDD强调业务模型与代码模型的一致性,避免了传统MVC模式中常见的“贫血模型”问题,让软件更贴合业务变化。
本文增加了更多理论深度分析、实际案例、常见误区、演进路径,以及大量的C#代码示例(基于.NET 8)。示例以典型场景如“设备管理系统”(适用于工控上位机)为主,结合电商、医疗等场景进行对比。全文约3万字,适合初学者到高级开发者阅读。
为什么读这篇文章?
- 理论深度:从战略到战术,覆盖所有核心概念。
- 实践导向:每节都有C#代码示例,易上手。
- 扩展内容:新增误区分析、架构演进、工具推荐。
- 适用场景:聚焦微服务、工控系统、云原生。
收藏起来,逐步实践吧!如果您是初学者,先从MVC vs DDD对比入手;如果是资深开发者,重点看战术设计与代码模型。
1、背景
1.1. 前言
软件开发的核心挑战在于应对复杂性。传统开发往往从数据结构入手,导致代码与业务脱节。随着微服务流行,DDD重新成为焦点,因为它提供了一种从业务驱动的技术路径。
小编先问大家一个问题,也算是高级工程师面试中常问的问题,怎么样才能设计出一个好的软件系统,或者说一个高质量的大型软件系统应该具有哪些特点?
- 可扩展性:业务变化时,代码易调整。
- 可维护性:新手快速上手,老代码不腐烂。
- 高性能:应对高并发、低延迟。
- 可靠性:7×24运行,故障隔离。
DDD也是设计一个高质量软件系统中的一种解决方案。了解DDD之前,小编建议读者还是需要具备一定的设计模式的思想,不太了解设计模式的可以先参考小编的文章:2.5万字详解23种设计模式
DDD这个思想呢,最早是Eric Evans(埃里克·埃文斯)在2003年《Domain-Driven Design –Tackling Complexity in the Heart of Software》书中提出的一个概念,该书翻译过来就是领域驱动设计—软件核心复杂性应对之道,但是提出的时候微服务当时并没有流行,所以一直没有火起来,DDD最近开始流行的原因,主要是借着微服务的东风。
1.2. MVC模式 VS DDD模式
MVC三层开发模式大家应该都非常熟悉,现在公司开发基本都是这种模式。

MVC开发流程:
这一步步下来,是不是感觉非常的丝滑。比如产品提了一个需求,首先我们一般会想考虑设计几张表,怎么存储数据,然后建立dao层,service层,controller层来实现这个功能。但是严格来讲,mvc本质上是一种面向数据的设计,主要关注数据,自低向上的思想。虽然在开发速度上有一定优势,如果只追求开发速度,面向数据模型编程在短期之内可以搞定需求,但一味追求速度,如果你系统的业务变化快速,从长远来看随着时间的增长,系统堆了杂七杂八以后,MVC的短板就会日益明显。
1.2.1. MVC存在的问题:
示例:MVC模式下的“贫血模型”问题
// MVC 贫血模型示例(问题代码)
public class Order // 贫血实体,只有数据无行为
{
public int Id { get; set; }
public string Customer { get; set; }
public decimal Amount { get; set; }
}
public class OrderService // Service 层承载所有逻辑,胖服务
{
public void CreateOrder(Order order)
{
if (order.Amount < 0) throw new Exception("金额无效"); // 业务规则散落在Service
_db.Orders.Add(order); // 数据访问混在业务中
_db.SaveChanges();
SendEmail(order); // 外部调用也混在
}
}
1.2.2. 使用DDD的意义:
单体架构局部业务膨胀可以拆分成微服务,微服务架构局部业务膨胀,又拆成什么呢?
DDD就是为了解决这些问题的存在,从一个软件系统的长期价值来看,就需要用DDD,虽然一开始从设计到开发需要成本,但是随着时间的增长,N年以后代码依然很整洁,利于扩展和维护,高度自治,高度内聚,边界领域划分的很清楚。当然了,针对于简单的系统用DDD反而用复杂了,杀鸡焉用宰牛刀!
MVC的开发模式:是数据驱动,自低向上的思想,关注数据。
DDD的开发模式:是领域驱动,自顶向下,关注业务活动。
示例:DDD模式下的“充血模型”
// DDD 充血模型示例(实体承载行为)
public class Order : AggregateRoot
{
public int Id { get; private set; }
public string Customer { get; private set; }
public decimal Amount { get; private set; }
public Order(string customer, decimal amount)
{
if (amount < 0) throw new DomainException("金额不能为负"); // 业务规则在实体中
Customer = customer;
Amount = amount;
}
public void ConfirmPayment()
{
// 行为封装在实体
RaiseEvent(new OrderPaidEvent(Id));
}
}
// 应用服务只协调
public class OrderApplicationService
{
private readonly IOrderRepository _repo;
public void CreateOrder(CreateOrderCommand cmd)
{
var order = new Order(cmd.Customer, cmd.Amount);
_repo.Add(order);
_repo.Commit();
}
}
1.3. 总结
MVC是一个短暂的快乐但不足以支撑漫长的生活,DDD是一个不要短暂的温存而是一世的陪伴,如果是你来抉择你会选择哪一个?
MVC关注数据流动,DDD关注业务领域。MVC适合简单CRUD,DDD适合复杂业务系统。在工控场景,DDD能更好地处理实时采集、报警等动态逻辑。
2、DDD领域驱动模型
2.1. 概念
DDD:
DDD(Domain Driven Design)领域驱动模型,是一种处理高度复杂领域的设计思想,不是一种架构,而是一种架构设计方法论,是一种设计模式。说白了就是把一个复杂的软件应用系统的其中各个部分进行一个很好的拆解和封装,以达到高内聚低耦合的这样一个效果。
说白了就是,DDD就是以高内聚低耦合为目的,对软件系统进行模块化的一种思想。
示例:在设备管理系统中,DDD将“设备采集”与“报警处理”拆分为独立领域,避免耦合。
战略设计:
指的是领域名词、动词分析、提取领域模型。官方解释,在某个领域,核心围绕上下文的设计,主要关注上下文的划分、上下文映射的设计,通用语言的设计。
说白了就是,在某个系统,核心围绕子系统的设计;主要关注,这些子系统的划分,子系统的交互方式,还有子系统的核心术语的定义。
战术设计:
用领域模型指导设计及编码的实现。官方解释,核心关注上下文中的实体建模,定义值对象,实体等,更偏向开发细节。
说白了就是,上下文对应的就是某一个子系统,子系统里代码实现怎么设计,就是战术设计要解决的问题。核心关注某个子系统的代码实现,以面向对象的思维设计类的属性和方法,和设计类图没有什么区别,只是有一些规则而已。就是指导我们划分类。
问题空间:
问题空间属于需求分析阶段,重点是明确这个系统要解决什么问题,能够提供什么价值,也就是关注系统的What与Why。
问题空间将问题域提炼成更多可管理的子域,是针对问题域而言的。问题空间在研究和解决业务问题时,DDD 会按照一定的规则将业务领域进行细分,当领域细分到一定的程度后,DDD 会将问题范围限定在特定的边界内,在这个边界内建立领域模型,进而用代码实现该领域模型,解决相应的业务问题。简言之,DDD 的领域就是这个边界内要解决的业务问题域。
领域可以进一步划分为子领域。我们把划分出来的多个子领域称为子域,每个子域对应一个更小的问题域或更小的业务范围,每个子域又包含了核心子域,支撑子域,通用子域。
领域的核心思想就是将问题域逐级细分,来降低业务理解和系统实现的复杂度。通过领域细分,逐步缩小服务需要解决的问题域,构建合适的领域模型。
说白了就是,就是系统下面有多个子系统,就是分了一些类型,比如电商系统,订单就是核心领域,支付调用银行,支付宝什么的就是支撑子域,相当于我们俗称的下游,通用子域,就是一些鉴权,用户中心,每个系统都会用到,就设计成通用子域,关键就是讨论过程如何得出这些问题域,是战略设计要解决的。
解决空间:
解决方案域属于系统设计阶段,针对识别出来的问题域,寻求合理的解决方案,也就是关注系统的How。在领域驱动设计中,核心领域(Core Domain)与子领域(Sub Domain)属于问题域的范畴,限界上下文(Bounded Context)则属于解决方案域的范畴。
说白了就是,得出这些问题域之后,就基于这些问题域来求解,属于解决空间。相当于,知道了y=2x,知道了x是多少,然后求y的值。解决空间就是指,领域之间的关系是什么样子,每个领域中通用的术语 ,具体在领域内怎么实现代码,进行领域建模就可以了。
从问题域到解决方案域,实际上就是从需求分析到设计的过程,也是我们逐步识别限界上下文的过程。
事件风暴:
事件风暴的基本思想,就是将软件开发人员和领域专家聚集在一起,完成领域模型设计(领域分析和领域建模)。划分出微服务逻辑边界和物理边界,定义领域模型中的领域对象,指导微服务设计和开发。
领域分析,是根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;然后分析每个上下文内部,抽取每个子域的领域概念,识别出哪些是实体,哪些是值对象;
领域建模,就是对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
DDD需要进行领域分析和领域建模,除了事件风暴之外实现的方法有,领域故事讲述,四色建模法,用例法等。
事件风暴是建立领域模型的主要方法,但是在 DDD 领域建模和系统建设过程中,有很多的参与者,包括领域专家、产品经理、项目经理、架构师、开发经理和测试经理等。对同样的领域知识,不同的参与角色可能会有不同的理解,那大家交流起来就会有障碍,怎么办呢?因此,在 DDD 中就出现了“通用语言”和“限界上下文”这两个重要的概念。
示例:事件风暴在设备管理系统中的应用
- 事件:设备已上线、报警已触发、数据已采集
- 命令:上线设备、确认报警
- 聚合:设备聚合(设备实体 + 报警值对象)
代码示例:事件类
public class 设备已上线事件 : IDomainEvent
{
public string 设备ID { get; }
public DateTime 上线时间 { get; }
public 设备已上线事件(string 设备ID, DateTime 上线时间)
{
this.设备ID = 设备ID;
this.上线时间 = 上线时间;
}
}
通用语言:
DDD的主要参与者:领域专家+开发人员。领域专家擅长某个领域的知识,专注于交付的业务价值。而开发人员则注重于技术实现,总是想着类、接口、方法、设计模式、架构等。这也就导致了团队交流的困难性。因此找到双方的通用语言是解决该问题的有效途径。
通用语言定义上下文含义。在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。
通用语言包含术语和用例场景,并且能够直接反映在代码中。通用语言中的名词可以给领域对象命名,如商品、订单等,对应实体对象;而动词则表示一个动作或事件,如商品已下单、订单已付款等,对应领域事件或者命令。
通用语言说白了就是,使用团队(业务人员、产品经理、UI设计、前端设计、后端技术等)中大家都懂的概念,统一语言用可以用于需求文档、PRD文档、代码以及日常的沟通,可以解决交流障碍的问题,使领域专家和开发人员能够协同合作,从而能够确保业务需求的正确表达。
限界上下文:
官方解释:限界上下文主要用来封装通用语言和领域对象。
限界上下文可以拆分为两个词,限界和上下文。
限界:适用的对象一般是抽象事物,指不同事物的分界,指定某些事物的范围。
上下文:个人理解就是语境。语言都有它的语义环境,同样,通用语言也有它的上下文环境。为了避免同样的概念或语义在不同的上下文环境中产生歧义,DDD 在战略设计上提出了“限界上下文”这个概念,用来确定语义所在的领域边界。限界上下文就是用来定义领域边界,以确保每个上下文含义在它特定的边界内都具有唯一的含义,领域模型则存在于这个边界之内。这个边界定义了模型的适用范围,使团队所有成员能够明确地知道什么应该在模型中实现,什么不应该在模型中实现。
比如说,商品在不同的阶段有不同的术语,在销售阶段是商品,而在运输阶段则变成了货物。同样的一个东西,由于业务领域的不同,赋予了这些术语不同的涵义和职责边界,这个边界就可能会成为未来微服务设计的边界。那么,领域边界就是通过限界上下文来定义的。
限界上下文是微服务设计和拆分的主要依据。在领域模型中,如果不考虑技术异构、团队沟通等其它外部因素,一个限界上下文理论上就可以设计为一个微服务。
限界上下文是业务概念的边界,是业务问题最小粒度的划分。在某个业务领域中会包含多个限界上下文,我们通过找出这些确定的限界上下文对系统进行解耦,要求每一个限界上下文其内部必须是紧密组织的、职责明确的、具有较高的内聚性。说白了就是,上下文对应的就是某一个子系统,系统之间要划分好边界。
示例:在工控系统中,“设备监控上下文”与“报警处理上下文”分开,避免监控逻辑污染报警。
上下文映射:
上下文之间交互方式就是上下文映射,相对于系统里面这就是RPC,http等交互方式。
示例:设备监控上下文通过事件映射报警上下文。
代码示例:上下文映射服务
public class 监控上下文映射
{
private readonly I报警上下文服务 _报警服务;
public void 映射报警(DomainEvent e)
{
_报警服务.处理事件(e);
}
}
领域:
从广义上讲,领域具体指一种特定的范围或区域。在DDD中上下文的划分完的东西叫作领域,领域下面又划分了,核心领域,支撑子域,通用子域。
领域模型:
领域模型是对领域内的概念类或现实世界中对象的可视化表示。它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。
是描述业务用例实现的对象模型。它是对业务角色和业务实体之间应该如何联系和协作以执行业务的一种抽象。
领域模型分为领域对象和领域服务两大类,领域对象用于存储状态,领域服务用于改变领域对象的状态。
特点:
- 领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分;
- 领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等;
- 领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;
- 领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造;
- 要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通共同努力,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型;
- 为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型;
领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;
领域事件:
聚合之间产生的业务协同使用领域事件的方式来完成,领域事件就是将上游聚合处理完成这个动作通过事件的方式进行抽象。
在DDD中有一个原则,一个业务用例对应一个事务,一个事务对应一个聚合根,也就是在一次事务中只能对一个聚合根操作。但在实际应用中,一个业务用例往往需要修改多个聚合根,而不同的聚合根可能在不同的限界上下文中,引入领域事件即不破坏DDD的一个事务只修改一个聚合根的原则,也能实现限界上下文之间的解耦。对于领域事件发布,在领域服务发布,在不使用领域服务的情况下,则由应用层在调用资源库持久化聚合根之后再发布领域事件。
一个事件可能当前限界上下文内也需要消费,即可能有多个限界上下文需要消费,一个事件对应多个消费者。
一个完整的领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。
事件发布:构建一个事件,需要唯一标识,然后发布;
事件存储:发布事件前需要存储,因为接收后的事建也会存储,可用于重试或对账等;就是每次执行一次具体的操作时,把行为记录下来,执行持久化。
事件分发:服务内的应用服务或者领域服务直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等,支持同步或者异步。
事件处理:先将事件存储,然后再处理。
当然了,实际开发中事件存储和事件处理不是必须的。
因此实现方案:发布订阅模式,分为跨上下文(kafka,RocketMq)和上下文内(spring事件,Guava Event Bus)的领域事件。
示例:用户注册后,发送短信和邮件,使用spring事件实现领域事件代码如下:
1.创建用户注册事件
// C# 版本(使用 .NET 事件)
public class UserRegisterEvent : EventArgs
{
public User User { get; }
public UserRegisterEvent(User user)
{
User = user;
}
}
2.用户监听事件
public class UserListener
{
public event EventHandler<UserRegisterEvent> UserRegistered;
public void OnUserRegistered(UserRegisterEvent e)
{
Console.WriteLine($"用户注册:发送短信… {e.User}");
Console.WriteLine($"用户注册:发送邮件… {e.User}");
}
}
3.发布用户注册事件
public class MyClient
{
private readonly UserListener _listener = new();
public MyClient()
{
_listener.UserRegistered += (s, e) => _listener.OnUserRegistered(e);
}
public void Test()
{
var user = new User();
_listener.UserRegistered?.Invoke(this, new UserRegisterEvent(user));
}
}
实体:
官方解释,实体和值对象会形成聚合,每个聚合一般是在一个事务中操作,一般都有持久性操作。聚合中,跟实体的生命周期决定了聚合整体的生命周期。说白了,就是对象之间的关联,只是规定了关联对象规则(必须是由实体和值对象组成的),操作聚合时类似hibernate的One-Many对象的操作,一起操作,不能单独操作。
e.g. 权限管理系统——用户实体,代码如下:
public class User : AggregateRoot<long>
{
public long UserId { get; private set; } // 聚合根唯一标识
public string UserName { get; private set; }
public string RealName { get; private set; }
public string Phone { get; private set; }
public string Password { get; private set; }
public DateTime? LockEndTime { get; private set; }
public int FailNumber { get; private set; }
public List<Role> Roles { get; private set; }
public Department Department { get; private set; }
public User Leader { get; private set; }
public List<User> SubordinationList { get; private set; } = new List<User>();
public UserStatus UserStatus { get; private set; }
public Address Address { get; private set; }
public User(string userName, string phone, string password)
{
SaveUserName(userName);
SavePhone(phone);
SavePassword(password);
}
private void SaveUserName(string userName)
{
if (string.IsNullOrWhiteSpace(userName))
throw new DomainException("用户名不能为空!");
UserName = userName;
}
// … 其他私有方法类似
}
官方解释,描述了领域中的一件东西,将不同的相关属性组合成了一个概念整体,当度量和描述改变时,可以用另外一个值对象予以替换,属性判等,固定不变。
说白了就是,不关心唯一值,具有校验逻辑,等值判断逻辑,只关心值的类。只有数据初始化操作和有限的不涉及修改数据的行为,基本不包含业务逻辑。比如下单的地址。
示例:地址值对象
public record Address(string Province, string City, string Region)
{
public Address(string province, string city, string region)
{
if (string.IsNullOrWhiteSpace(province))
throw new DomainException("province不能为空!");
// … 类似校验
}
}
聚合:
聚合是实体/值对象的集合,确保一致性。
聚合根:
聚合的入口实体。
贫血模型:
只有数据无行为的对象。
充血模型:
数据 + 行为的实体。
领域服务:
跨实体逻辑。
应用服务:
用例协调。
仓库:
聚合持久化。
工厂:
复杂对象创建。
防腐层:
隔离外部依赖。
2.4. 落地方式
2.4.1. 战略设计:
…
2.4.2. 战术设计:
…
3、DDD架构
…
4、 CQRS实战
…
5、总结
以下是目录中DDD架构分析与代码设计部分的大幅扩展版本,我为每一章每一节都增加了更多、更详细的C#代码示例(基于 .NET 8),并附上详细注释、设计意图、常见误区、适用场景说明,以及在工控上位机/工业物联网场景下的实际落地建议。
代码示例全部采用充血模型 + 六边形 + DDD分层混合风格,并尽量体现AOT友好、可测试、跨平台(Windows/麒麟/统信)特性。示例主题统一为**“工业设备实时监控与报警系统”**,让您更容易代入实际项目。
一、微服务架构模型的对比与选择
(一)整洁架构
核心思想:依赖倒置 + 圆环分层,最内层是业务实体,向外依次是用例、适配器、框架。核心业务完全不依赖外部技术。
优点:业务纯净、测试极易、技术栈更换成本低
缺点:抽象层多、代码量大、小型系统过度设计
工控场景适用性:适合需要长期维护、频繁更换协议(Modbus → OPC UA → EtherCAT)的系统
代码示例1:整洁架构中的用例层(创建设备报警规则)
// Entities/AlarmRule.cs(最内层 – 实体)
public class AlarmRule
{
public Guid Id { get; private set; }
public string DeviceId { get; private set; }
public decimal UpperLimit { get; private set; }
public decimal LowerLimit { get; private set; }
public bool IsEnabled { get; private set; }
public AlarmRule(string deviceId, decimal upper, decimal lower)
{
Id = Guid.NewGuid();
DeviceId = deviceId;
UpperLimit = upper;
LowerLimit = lower;
IsEnabled = true;
}
public bool IsViolated(decimal currentValue)
{
return currentValue > UpperLimit || currentValue < LowerLimit;
}
public void Disable()
{
if (!IsEnabled) return;
IsEnabled = false;
}
}
// UseCases/CreateAlarmRuleUseCase.cs(用例层)
public class CreateAlarmRuleUseCase
{
private readonly IAlarmRuleRepository _repository; // 端口
public CreateAlarmRuleUseCase(IAlarmRuleRepository repository)
{
_repository = repository;
}
public Result<Guid> Execute(CreateAlarmRuleRequest request)
{
var rule = new AlarmRule(
request.DeviceId,
request.UpperLimit,
request.LowerLimit
);
_repository.Save(rule);
return Result.Success(rule.Id);
}
}
// Ports/IAlarmRuleRepository.cs(端口定义)
public interface IAlarmRuleRepository
{
void Save(AlarmRule rule);
AlarmRule GetById(Guid id);
}
// Adapters/EfAlarmRuleRepository.cs(外层适配器)
public class EfAlarmRuleRepository : IAlarmRuleRepository
{
private readonly AppDbContext _context;
public EfAlarmRuleRepository(AppDbContext context) => _context = context;
public void Save(AlarmRule rule)
{
_context.AlarmRules.Add(rule);
_context.SaveChanges();
}
public AlarmRule GetById(Guid id) => _context.AlarmRules.Find(id);
}
代码示例2:整洁架构中的测试(Mock 外层)
[Fact]
public void 创建报警规则_应保存到仓储()
{
// Arrange
var mockRepo = new Mock<IAlarmRuleRepository>();
var useCase = new CreateAlarmRuleUseCase(mockRepo.Object);
var request = new CreateAlarmRuleRequest("DEV001", 100m, 0m);
// Act
var result = useCase.Execute(request);
// Assert
mockRepo.Verify(r => r.Save(It.IsAny<AlarmRule>()), Times.Once);
Assert.True(result.IsSuccess);
}
(二)六边形架构
核心思想:核心业务(Domain)是六边形,外部通过端口与适配器交互,核心不依赖任何外部技术。
优点:协议/UI/存储随意替换、测试极简
缺点:接口定义多、初期工作量大
工控场景适用性:非常适合采集协议频繁切换的系统(如从Modbus切换到EtherCAT)
代码示例:六边形架构中的设备采集端口与多适配器
// Core/Ports/IDevice采集端口.cs(端口)
public interface IDevice采集端口
{
Task<采集数据> 获取实时数据Async(string deviceId, CancellationToken ct);
Task 发送控制指令Async(string deviceId, string instruction, CancellationToken ct);
}
// Core/Domain/采集数据.cs(核心领域对象)
public record 采集数据(string DeviceId, DateTime Timestamp, decimal Value, string Unit);
// Adapters/Modbus采集适配器.cs(Modbus实现)
public class Modbus采集适配器 : IDevice采集端口
{
private readonly ModbusClient _client;
public Modbus采集适配器(string ip, int port)
{
_client = new ModbusClient(ip, port);
}
public async Task<采集数据> 获取实时数据Async(string deviceId, CancellationToken ct)
{
var value = await _client.ReadHoldingRegisterAsync(1, 0); // 示例
return new 采集数据(deviceId, DateTime.UtcNow, value, "°C");
}
public async Task 发送控制指令Async(string deviceId, string instruction, CancellationToken ct)
{
await _client.WriteSingleRegisterAsync(1, 10, ushort.Parse(instruction), ct);
}
}
// Adapters/OpcUa采集适配器.cs(OPC UA实现)
public class OpcUa采集适配器 : IDevice采集端口
{
private readonly Session _session;
public OpcUa采集适配器(string endpointUrl)
{
// OPC UA 会话初始化(省略细节)
_session = Session.Create(...).GetAwaiter().GetResult();
}
public async Task<采集数据> 获取实时数据Async(string deviceId, CancellationToken ct)
{
var nodeId = new NodeId($"ns=2;s={deviceId}.Temperature");
var value = await _session.ReadValueAsync(nodeId);
return new 采集数据(deviceId, DateTime.UtcNow, Convert.ToDecimal(value), "°C");
}
public async Task 发送控制指令Async(string deviceId, string instruction, CancellationToken ct)
{
// OPC UA 写操作(省略)
}
}
// 使用示例(核心不依赖具体适配器)
var 采集端口 = new Modbus采集适配器("192.168.1.100", 502); // 随时换成OPC UA
var 数据 = await 采集端口.获取实时数据Async("DEV001", default);
(三)DDD 分层架构
1. 用户接口层
职能:接收外部请求、参数校验、DTO转换、异常包装。
代码示例(扩展:支持gRPC + REST)
// 用户接口层 – REST Controller
[ApiController]
[Route("api/devices")]
public class DeviceController : ControllerBase
{
private readonly IDeviceApplicationService _service;
public DeviceController(IDeviceApplicationService service) => _service = service;
[HttpPost("{id}/command")]
public async Task<IActionResult> SendCommand(string id, [FromBody] SendCommandDto dto)
{
var command = new SendDeviceCommand(id, dto.Instruction);
await _service.ExecuteCommandAsync(command);
return Ok();
}
}
// 用户接口层 – gRPC 服务(可选)
public class DeviceGrpcService : Device.DeviceBase
{
private readonly IDeviceApplicationService _service;
public DeviceGrpcService(IDeviceApplicationService service) => _service = service;
public override async Task<CommandResponse> SendCommand(CommandRequest request, ServerCallContext context)
{
var cmd = new SendDeviceCommand(request.DeviceId, request.Instruction);
await _service.ExecuteCommandAsync(cmd);
return new CommandResponse { Success = true };
}
}
2. 应用层
代码示例(扩展:支持事务 + 事件发布)
public class DeviceApplicationService : IDeviceApplicationService
{
private readonly IDeviceRepository _repo;
private readonly IUnitOfWork _uow;
private readonly IDomainEventBus _eventBus;
public async Task ExecuteCommandAsync(SendDeviceCommand cmd)
{
await _uow.ExecuteInTransactionAsync(async () =>
{
var device = await _repo.GetByIdAsync(cmd.DeviceId);
if (device == null) throw new DomainException("设备不存在");
device.SendCommand(cmd.Instruction);
await _repo.CommitAsync();
// 发布领域事件(异步通知其他模块)
await _eventBus.PublishAsync(new DeviceCommandExecutedEvent(device.Id, cmd.Instruction));
});
}
}
3. 领域层
代码示例(扩展:聚合根 + 领域事件 + 值对象)
// 聚合根
public class DeviceAggregate : AggregateRoot
{
public string Id { get; private set; }
public DeviceStatus Status { get; private set; }
public decimal CurrentValue { get; private set; }
private readonly List<AlarmRecord> _alarms = new();
public IReadOnlyCollection<AlarmRecord> Alarms => _alarms.AsReadOnly();
public void UpdateValue(decimal newValue)
{
CurrentValue = newValue;
if (newValue > 100)
{
var alarm = new AlarmRecord(Guid.NewGuid(), Id, AlarmLevel.Critical, "温度超限", DateTime.UtcNow);
_alarms.Add(alarm);
AddDomainEvent(new AlarmRaisedEvent(alarm.Id, Id, AlarmLevel.Critical));
}
}
public void SendCommand(string instruction)
{
// 业务规则校验
if (Status == DeviceStatus.Offline)
throw new DomainException("设备离线");
AddDomainEvent(new DeviceCommandSentEvent(Id, instruction));
}
}
// 值对象
public record AlarmLevel(string Value)
{
public static readonly AlarmLevel Normal = new("Normal");
public static readonly AlarmLevel Warning = new("Warning");
public static readonly AlarmLevel Critical = new("Critical");
}
// 领域事件
public record AlarmRaisedEvent(Guid AlarmId, string DeviceId, AlarmLevel Level) : IDomainEvent;
4. 基础层
代码示例(扩展:仓储 + 事件总线)
// 仓储实现
public class EfDeviceRepository : IDeviceRepository
{
private readonly AppDbContext _context;
public EfDeviceRepository(AppDbContext context) => _context = context;
public async Task<DeviceAggregate> GetByIdAsync(string id)
{
return await _context.Devices
.Include(d => d.Alarms)
.FirstOrDefaultAsync(d => d.Id == id);
}
public void Add(DeviceAggregate device)
{
_context.Devices.Add(device);
}
}
// 事件总线(参考前文优先级版本)
public class PriorityDomainEventBus : IDomainEventBus { /* 见前文完整实现 */ }
(四)三种微服务架构模型的对比和分析(扩展)
| 核心关注点 | 依赖倒置、业务独立 | 端口适配、核心隔离 | 领域模型为核心、分层清晰 | DDD + 六边形混合 |
| 实时性支持 | 中(需额外优化) | 高(易替换协议适配器) | 高(领域层可优化实时规则) | 六边形适配器 + DDD 领域层 |
| 测试难度 | 极低 | 低 | 低 | 推荐 |
| 工控协议切换成本 | 中 | 极低 | 中 | 六边形最佳 |
| 代码量 | 多 | 中等 | 中等 | 适中 |
| 微服务边界清晰度 | 中 | 高 | 高 | 高 |
推荐组合:DDD 分层(核心业务) + 六边形适配器(协议/存储/UI)
这种混合模式在工控系统中表现最佳:领域层保持业务纯净,适配器层灵活应对Modbus/OPC UA/EtherCAT切换。
后续章节(二、三、四)如果需要继续扩展(例如完整仓储、事件总线、CQRS 读写模型、单元测试、集成测试等),请告诉我具体想深入哪一部分,我可以再补充几千字代码和说明。
Add CQRS Implementation Examples
Explore Event Sourcing Patterns
Make Code Examples More Concise
需要进一步调整或添加特定场景示
2.5万字详解23种设计模式



