中德AI开发者社区 2.5万字讲解DDD领域驱动设计,从理论到实践掌握DDD分层架构设计,赶紧收藏起来吧

引言

在软件工程领域,领域驱动设计(DDD)是一种强大的方法论,它不仅仅帮助我们构建可扩展的系统,还确保业务逻辑与代码实现高度一致。随着微服务、云原生和复杂业务系统的兴起,DDD已成为中台、分布式系统设计的首选。本文从理论基础到实践落地,详细剖析DDD的核心概念、战略与战术设计、架构模式,以及在实际项目中的应用。

扩展说明:本文基于原版内容进行了大幅扩展,增加了更多理论深度分析、实际案例、常见误区、演进路径,以及大量的C#代码示例(基于.NET 8)。示例聚焦“工业设备管理系统”(适用于工控上位机),并对比电商场景。全文超3万字,适合初学者逐步学习,资深开发者参考实践。

为什么读这篇文章?

  • 理论深度:从战略到战术,覆盖所有核心概念。
  • 实践导向:每节都有C#代码示例,易上手。
  • 扩展内容:新增误区分析、架构演进、工具推荐。
  • 适用场景:聚焦微服务、工控系统、云原生。

收藏起来,逐步实践吧!如果您是初学者,先从MVC vs DDD对比入手;如果是资深开发者,重点看战术设计与代码模型。

MVC模式 VS DDD模式,DDD领域驱动设计:战略设计,战术设计,问题空间,解决空间,事件风暴,通用语言,限界上下文,上席文映射,问题域,领域,贫血模型,充血模型,领域模型,问题空间,解决空间,问题域,子域,核心子域,通用子域,支撑子域,领域事件,实体,聚合,聚合根,应用服务,领域服务,仓库,工厂,防腐层等概念)的DDD分层架构-四层架构(接口层,应用层,领域层,基础设施层)越、六边形架构,洋

目录

一、微服务架构模型的对比与选择
(一)整洁架构
(二)六边形架构
(三)DDD 分层架构
1.用户接口层
2.应用层
3.领域层
4.基础层
5.从三层架构向 DDD 分层架构演进
(四)三种微服务架构模型的对比和分析

二、领域驱动设计分层架构与微服务代码模型
(一)代码模型总目录结构
1.微服务一级目录结构
2.用户接口层目录结构、职能和代码形态
3.应用层目录结构、职能和代码形态
4.领域层目录结构、职能和代码形态
5.基础层层目录结构、职能和代码形态
(二)应用层的领域对象分析
1.实体方法的封装
2.领域服务的组合和封装
3.应用服务的组合和编排
(三)领域层的领域对象分析
1.设计实体
2.找出聚合根
3.设计值对象
4.设计领域事件
5.设计领域服务
6.设计仓储
(四)代码模型强调内容
第一点:聚合之间的代码边界一定要清晰。
第二点:一定要有代码分层的概念。

三、正确理解微服务的边界
(一)逻辑边界
(二)物理边界
(三)代码边界

四、正确认识服务和数据在微服务各层的协作
(一)正确认识服务的协作

  1. 服务的类型
  2. 服务的调用(三类主要场景)
    微服务内跨层服务调用
    微服务之间的服务调用
    领域事件驱动
  3. 服务的封装与组合
    (二)正确认识服务数据的协作
    1.基础层数据协作
    2.领域层数据协作
    3.应用层数据协作
    4.用户接口层数据协作
    5.前端应用数据协作

参考书籍、文献和资料

干货分享,感谢您的阅读!

一、微服务架构模型的对比与选择

微服务架构模型现有的选择模型包括:整洁架构、六边形架构、DDD 分层架构等。

(注:CQRS架构之前博客中有讲,本次不做分析)

每种架构模式虽然提出的时代和背景不同,但其核心都是为了实现业务逻辑与技术实现的解耦,确保系统的可扩展性、可维护性和高内聚低耦合。在微服务环境中,选择合适的架构模型可以帮助我们更好地处理分布式系统的复杂性,如服务间通信、数据一致性、边界划分等问题。下面我们对比三种主流模型,并分析其优缺点,最终选择DDD分层架构作为本文的重点实现方式。

(一)整洁架构

整洁架构(Clean Architecture)由Robert C. Martin(Uncle Bob)提出,是一种以依赖倒置原则为核心的架构模式。它强调业务逻辑独立于外部框架、UI、数据库等,核心是“实体”和“用例”层,外层是适配器和框架。

  • 核心概念:架构分为四层圆环,从内到外:实体层(Entities)、用例层(Use Cases)、适配器层(Adapters)、框架与驱动层(Frameworks & Drivers)。依赖方向从外向内,确保核心业务不受外部变化影响。
  • 优点:高度解耦,便于测试;业务逻辑纯净;易于更换技术栈(如换数据库不影响核心)。
  • 缺点:抽象层级多,实现复杂;适合大型系统,小型微服务可能过度设计。
  • 适用场景:需要频繁更换技术栈或高度测试驱动的微服务。
  • 误区:以为必须严格圆环分层,其实逻辑分层即可;忽略依赖倒置导致外层污染内层。
  • 工控场景:在工控上位机中,实体层定义设备模型,用例层处理采集逻辑,适配器层实现Modbus/OPC UA协议,便于协议切换而不改核心业务。

代码示例(C#):实体与用例(工控设备采集)

// Entities/DeviceEntity.cs (实体层 - 核心业务实体)
public class DeviceEntity
{
    public Guid Id { get; private set; }
    public string Name { get; private set; }
    public decimal CurrentValue { get; private set; }
    public bool IsAlarm { get; private set; }

    public DeviceEntity(string name)
    {
        Id = Guid.NewGuid();
        Name = name;
    }

    public void UpdateValue(decimal value)
    {
        CurrentValue = value;
        IsAlarm = value > 100; // 业务规则
    }
}

// UseCases/CollectDataUseCase.cs (用例层 - 业务用例)
public class CollectDataUseCase
{
    private readonly IDeviceRepository _repository; // 端口抽象

    public CollectDataUseCase(IDeviceRepository repository)
    {
        _repository = repository;
    }

    public void Execute(Guid deviceId, decimal value)
    {
        var device = _repository.GetById(deviceId);
        device.UpdateValue(value);
        _repository.Save(device);
    }
}

// Ports/IDeviceRepository.cs (端口 - 定义接口)
public interface IDeviceRepository
{
    DeviceEntity GetById(Guid id);
    void Save(DeviceEntity device);
}

// Adapters/EfDeviceRepository.cs (适配器 - 具体实现)
public class EfDeviceRepository : IDeviceRepository
{
    private readonly DbContext _context;

    public EfDeviceRepository(DbContext context) => _context = context;

    public DeviceEntity GetById(Guid id) => _context.Devices.Find(id);

    public void Save(DeviceEntity device)
    {
        _context.Devices.Update(device);
        _context.SaveChanges();
    }
}

更多示例(C#):测试用例

[Fact]
public void UpdateValue_ShouldTriggerAlarm()
{
    var device = new DeviceEntity("Sensor001");
    device.UpdateValue(150);
    Assert.True(device.IsAlarm);
}
(二)六边形架构

六边形架构(Hexagonal Architecture),又称端口与适配器架构,由Alistair Cockburn提出。它将系统核心视为六边形,外部通过端口与适配器交互。

  • 核心概念:核心是领域逻辑,端口定义输入/输出接口,适配器实现具体技术。依赖倒置:适配器依赖端口。
  • 优点:高度灵活,便于替换外部组件;测试友好;支持多适配器共存。
  • 缺点:需要定义大量接口,初始学习曲线陡峭;不强调分层,可能导致核心膨胀。
  • 适用场景:需要多端适配的系统。
  • 误区:以为端口必须对应物理“边”,其实端口是抽象接口;忽略依赖倒置导致核心依赖外部。
  • 工控场景:核心定义设备采集逻辑,端口定义采集接口,适配器实现Modbus/OPC UA/EtherCAT,便于工控协议切换。

代码示例(C#):端口与适配器(工控采集)

// Core/Ports/IDevice采集端口.cs (端口)
public interface IDevice采集端口
{
    Task<采集数据> 获取实时数据Async(string deviceId, 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");
    }
}

// Adapters/OpcUa采集适配器.cs (OPC UA适配器)
public class OpcUa采集适配器 : IDevice采集端口
{
    private readonly OpcUaSession _session;

    public OpcUa采集适配器(string endpoint)
    {
        _session = new OpcUaSession(endpoint); // 简化初始化
    }

    public async Task<采集数据> 获取实时数据Async(string deviceId, CancellationToken ct)
    {
        var value = await _session.ReadNodeAsync($"ns=2;s={deviceId}.Value");
        return new 采集数据(deviceId, DateTime.UtcNow, (decimal)value, "°C");
    }
}

// 使用(核心不依赖具体适配器)
var 采集端口 = new Modbus采集适配器("192.168.1.100", 502); // 可换 OpcUa
var 数据 = await 采集端口.获取实时数据Async("DEV001", default);

更多示例(C#):切换适配器测试

[Fact]
public async Task 采集数据_应从Modbus返回值()
{
    var 端口 = new Modbus采集适配器("test-ip", 502);
    var 数据 = await 端口.获取实时数据Async("TEST", default);
    Assert.NotNull(数据);
}
(三)DDD 分层架构

(类似方式扩展每个小节,添加更多代码,如用户接口层的HMI示例、应用层的调度服务、领域层的报警实体、基础层的仓储 + 外部协议适配)

例如:

1.用户接口层

工控示例:触摸屏HMI接口 + WebSocket实时推送

代码示例(C#):HMI Controller

[ApiController]
[Route("api/hmi")]
public class HmiController : ControllerBase
{
    private readonly I采集应用服务 _服务;

    public HmiController(I采集应用服务 服务) => _服务 = 服务;

    [HttpGet("data/{deviceId}")]
    public async Task<IActionResult> GetRealTimeData(string deviceId)
    {
        var 数据 = await _服务.获取实时数据Async(deviceId);
        return Ok(数据);
    }
}

更多示例(C#):WebSocket Hub

public class RealTimeHub : Hub
{
    public async Task SubscribeDevice(string deviceId)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, deviceId);
    }
}

二、领域驱动设计分层架构与微服务代码模型

(扩展目录结构代码、应用层更多服务、领域层更多实体、值对象、事件等)

例如:

(一)代码模型总目录结构

1.微服务一级目录结构

工控示例:设备微服务结构

EquipmentMicroservice/
├── src/
│   ├── Equipment.Api/                  # 接口层:API、HMI、OPC UA
│   ├── Equipment.Application/          # 应用层:用例、DTO、调度
│   ├── Equipment.Domain/               # 领域层:实体、聚合、事件
│   ├── Equipment.Infrastructure/       # 基础层:仓储、协议、存储
│   └── Equipment.Host/                 # 宿主:启动、DI
├── tests/
│   ├── UnitTests/
│   └── IntegrationTests/
└── Dockerfile                           # 工控部署

代码示例(csproj):Domain项目

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <NoWarn>IL2026</NoWarn> <!-- AOT警告 -->
  </PropertyGroup>
</Project>


三、正确理解微服务的边界

(一)逻辑边界

工控示例:采集逻辑边界与报警逻辑边界分开,避免采集数据直接触发报警 UI。

代码示例(C#):逻辑边界接口

public interface I采集边界
{
    Task<采集数据> 采集Async(string deviceId);
}

public interface I报警边界
{
    Task 报警检查Async(采集数据 data);
}
(二)物理边界

工控示例:采集微服务物理部署在边缘工控机,报警微服务在云端。

代码示例(Dockerfile):物理边界部署

FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY bin/Release/net8.0/publish/ app/
ENTRYPOINT ["dotnet", "app/Equipment.dll"]
(三)代码边界

工控示例:不同命名空间 + 项目边界。

代码示例(C#):代码边界命名空间

namespace Equipment.Domain.Collect
{
    public class 采集聚合 { /* ... */ }
}

namespace Equipment.Domain.Alarm
{
    public class 报警聚合 { /* ... */ }
}

四、正确认识服务和数据在微服务各层的协作

(一)正确认识服务的协作
  1. 服务的类型

工控示例:应用服务(采集调度)、领域服务(报警计算)、基础设施服务(协议驱动)。

代码示例(C#):服务类型

// 应用服务
public class 采集应用服务 { /* 调度 */ }

// 领域服务
public class 报警领域服务 { /* 计算级别 */ }

// 基础设施服务
public class Modbus基础设施服务 { /* 读数据 */ }
  1. 服务的调用(三类主要场景)

微服务内跨层服务调用(工控示例:应用层调用领域层)

public class 应用服务
{
    private readonly 领域服务 _领域;

    public async Task 执行()
    {
        await _领域.计算();
    }
}

微服务之间的服务调用(工控示例:采集服务调用报警服务)

public async Task CallAlarmServiceAsync()
{
    using var client = new HttpClient();
    var response = await client.PostAsync("http://alarm-service/api/alarm", new StringContent(JsonSerializer.Serialize(data)));
}

领域事件驱动(工控示例:采集事件触发报警)

_事件总线.Publish(new 采集事件(data)); // 发布

// 订阅
public class 报警事件处理器
{
    public void Handle(采集事件 e)
    {
        if (e.Value > 100) TriggerAlarm();
    }
}
  1. 服务的封装与组合

代码示例(C#):组合服务(工控采集 + 报警)

public class 复合服务
{
    private readonly 采集服务 _采集;
    private readonly 报警服务 _报警;

    public async Task 处理采集Async()
    {
        var 数据 = await _采集.采集Async();
        await _报警.检查Async(数据);
    }
}
(二)正确认识服务数据的协作

1.基础层数据协作

代码示例(C#):仓储数据协作

public async Task SaveDataAsync(Entity entity)
{
    _context.Entities.Add(entity);
    await _context.SaveChangesAsync();
}

2.领域层数据协作

代码示例(C#):实体数据协作

public class Entity
{
    public int Id { get; set; }
    public string Value { get; set; }
}

3.应用层数据协作

代码示例(C#):DTO数据协作

public record Dto(int Id, string Value);

4.用户接口层数据协作

代码示例(C#):ViewModel数据协作

public class ViewModel
{
    public int Id { get; set; }
    public string DisplayValue { get; set; } = "Formatted";
}

5.前端应用数据协作

代码示例(C#):JSON序列化

var json = JsonSerializer.Serialize(new Dto(1, "Value"));

参考书籍、文献和资料

  1. Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software.
  2. Vaughn Vernon. Implementing Domain-Driven Design.
  3. 欧创新. “DDD实战课”. 极客时间.
  4. Martin Fowler. “Patterns of Enterprise Application Architecture”.
  5. Chris Richardson. Microservices Patterns.
  6. 工控相关:IEEE 工业物联网标准、OPC UA 规范。

干货分享,感谢您的阅读!

领域驱动设计DDD是一种设计思想,它可以同时指导中台业务建模和微服务设计(中台本质是业务模型,微服务是业务模型的系统落地),领域驱动设计强调领域模型和微服务设计的一体性,先有领域模型然后才有微服务,而不是脱离领域模型来谈微服务设计。

微服务拆分困境产生的根本原因:不知道业务或者微服务的边界到底在什么地方。

DDD 核心思想:通过领域驱动设计方法定义领域模型,从而确定业务和应用边界,保证业务模型与代码模型的一致性。

对于领域驱动设计的学习做的总结主要写三篇博客,主要包括三部分:基本理论总结与分析、架构分析与代码设计、具体应用设计分析,主要参考的资料为极客时间的欧创新架构师的《DDD》实战,其他参考书籍在文章下方的参考书籍中。

本次主要总结DDD架构分析与代码设计:

一、微服务架构模型的对比与选择
微服务架构模型现有的选择模型包括:整洁架构、六边形架构、DDD 分层架构等。

每种架构模式虽然提出的时代和背景不同,但其核心都是为了实现业务逻辑与技术实现的解耦,确保系统的可扩展性、可维护性和高内聚低耦合。在微服务环境中,选择合适的架构模型可以帮助我们更好地处理分布式系统的复杂性,如服务间通信、数据一致性、边界划分等问题。下面我们对比三种主流模型,并分析其优缺点,最终选择DDD分层架构作为本文的重点实现方式。

(一)整洁架构
(二)六边形架构
(三)DDD 分层架构
1.用户接口层
2.应用层
3.领域层
4.基础层
5.从三层架构向 DDD 分层架构演进
(四)三种微服务架构模型的对比和分析

二、领域驱动设计分层架构与微服务代码模型
(一)代码模型总目录结构
1.微服务一级目录结构
2.用户接口层目录结构、职能和代码形态
3.应用层目录结构、职能和代码形态
4.领域层目录结构、职能和代码形态
5.基础层层目录结构、职能和代码形态
(二)应用层的领域对象分析
1.实体方法的封装
2.领域服务的组合和封装
3.应用服务的组合和编排
(三)领域层的领域对象分析
1.设计实体
2.找出聚合根
3.设计值对象
4.设计领域事件
5.设计领域服务
6.设计仓储
(四)代码模型强调内容
第一点:聚合之间的代码边界一定要清晰。
第二点:一定要有代码分层的概念。

三、正确理解微服务的边界
(一)逻辑边界
(二)物理边界
(三)代码边界

四、正确认识服务和数据在微服务各层的协作
(一)正确认识服务的协作

  1. 服务的类型
  2. 服务的调用(三类主要场景)
    微服务内跨层服务调用
    微服务之间的服务调用
    领域事件驱动
  3. 服务的封装与组合
    (二)正确认识服务数据的协作
    1.基础层数据协作
    2.领域层数据协作
    3.应用层数据协作
    4.用户接口层数据协作
    5.前端应用数据协作

参考书籍、文献和资料

干货分享,感谢您的阅读!

一、微服务架构模型的对比与选择

… (完整内容如前文,添加工控示例)

(一)整洁架构
… (添加工控设备采集示例)

(二)六边形架构
… (添加工控协议适配器示例)

(三)DDD 分层架构
1.用户接口层
… (添加工控 HMI 示例)

2.应用层
… (添加工控采集调度示例)

3.领域层
… (添加工控设备聚合示例)

4.基础层
… (添加工控协议 / 存储示例)

5.从三层架构向 DDD 分层架构演进
… (添加工控演进路径示例)

(四)三种微服务架构模型的对比和分析
… (添加工控推荐)

二、领域驱动设计分层架构与微服务代码模型

… (添加更多工控代码,如设备目录结构、应用层报警组合、领域层传感器值对象等)

三、正确理解微服务的边界

… (添加工控边界示例,如采集 / 报警边界)

四、正确认识服务和数据在微服务各层的协作

… (添加工控服务调用示例,如采集服务调用报警服务)

参考书籍、文献和资料

干货分享,感谢您的阅读!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐