.NET领域驱动设计|实战指南|核心架构与企业级项目落地全流程解析

.NET领域驱动设计|实战指南|核心架构与企业级项目落地全流程解析 一

文章目录CloseOpen

从”业务迷宫”到”领域地图”:.NET领域驱动设计的核心架构解析

领域模型设计:让代码听懂业务的”翻译术”

很多人觉得DDD难,是把它当成了”高级架构技巧”,其实它更像”业务翻译工具”——把复杂需求转化成代码能理解的模型!我去年带团队做物流管理系统时,一开始产品经理说”要支持多仓库调拨,还要考虑批次效期和库存锁定”,后端几个同事直接懵了:这业务逻辑放哪?放Service还是Repository?后来我们用DDD的思路梳理,先画了张领域模型草图,结果发现问题出在”没搞懂业务边界”。

限界上下文

就是划分业务边界的” fences”。比如物流系统里,”库存管理”和”订单履约”虽然有关联,但规则完全不同:库存关注”怎么存、存多少”,订单关注”怎么发、发给谁”。一开始我们把这俩放一个项目里,结果库存的”锁定规则”改了,订单的发货逻辑跟着出bug。后来用限界上下文拆开,各自建项目,通过事件总线通信,问题一下解决了。你划分上下文时可以问自己:”这部分逻辑,换个部门的人来接手,需要重新学一套术语吗?”如果是,那大概率要分开。
聚合根是领域模型的”CEO”,负责管理一群相关对象。比如订单系统里,”订单”就是聚合根,它下面有”订单项”、”收货地址”这些”员工”,你不能直接改订单项的数量,必须通过订单来操作——就像公司里报销得找部门经理批,不能直接找财务。之前帮一个电商团队看代码,他们让前端直接调”订单项修改接口”,结果出现”订单项数量大于库存”的bug,后来改成通过订单聚合根的”UpdateItemQuantity”方法,在方法里加库存校验,问题再也没出现过。
值对象是业务中的”形容词”,比如”金额”(包含数值和币种)、”地址”(省市区街道门牌号)。很多人习惯把这些拆成单独的字段存在数据库,其实用值对象能避免重复代码。我见过一个项目把”手机号”当成字符串存,结果有的存11位,有的带区号,校验逻辑散落在各个Service里。后来改成值对象PhoneNumber,在构造函数里统一校验格式,所有地方直接用,代码清爽多了!

.NET技术栈怎么”接住”领域模型?别让工具拖后腿

光有模型还不够,得让.NET技术栈和DDD”好好配合”。不少团队失败在这一步:模型设计得挺漂亮,一用EF Core保存就变味了——把聚合根当成普通实体,Repository里写满Join查询,等于白忙活!

EF Core存领域对象的”正确姿势”

:聚合根是最小持久化单位,也就是说你只能保存整个聚合,不能单独保存聚合里的子对象。比如订单聚合,你应该调用_orderRepository.Add(order),而不是_dbContext.OrderItems.Add(item)。去年做医疗系统时,我们一开始图方便,直接用EF的DbSet操作订单项,结果出现”聚合根状态和子对象不一致”的问题——订单明明取消了,订单项还能被修改。后来改成只暴露聚合根的Repository,问题解决。微软文档里专门提到,EF Core的”聚合根模式”支持这种设计,你可以去看看,里面有具体代码示例。
ASP.NET Core怎么”不越界”:控制器要做”传话筒”,别当”业务处理机”。之前审查一个项目代码,发现控制器里写了200多行逻辑:先查数据库,再判断用户权限,然后计算价格,最后调第三方接口。这就是典型的”控制器越界”!DDD里,控制器应该只负责接收请求、调用领域服务、返回结果,业务逻辑全放领域层。你可以这样做:控制器里注入IDomainService,调用service.Handle(request),然后返回结果——就像餐厅服务员只管点菜和上菜,做菜是后厨(领域层)的事。

企业级项目落地:从”画图纸”到”盖大楼”的全流程

需求分析:先听懂”业务方言”再动手

很多人做DDD落地,一上来就画类图,这就像盖楼不看地基直接画施工图!正确的步骤是先做”业务调研”——不是听产品经理讲功能,而是听业务专家说”行话”。我带团队做供应链系统时,一开始产品经理说”要做供应商评级”,我们以为就是弄个打分表。后来找采购总监聊,他说”得考虑账期履约率、质量投诉率,还要区分战略供应商和普通供应商,战略的评分权重不一样”——这些”行话”才是领域模型的关键!

你可以用”事件风暴”工作坊:找业务、产品、开发坐一起,用便利贴写”发生了什么事”(领域事件)、”谁来做”(聚合根)、”需要什么数据”(值对象)。比如物流系统的事件风暴,我们写出了”订单创建时锁定库存”、”库存不足时触发预警”这些事件,然后自然就梳理出”订单”、”库存”两个聚合根。这个方法亲测有效,比对着PRD干想效率高3倍!

从模型到代码:.NET项目的”DDD文件夹结构”

模型设计好后,代码怎么组织?分享一个我们团队在用的文件夹结构,简单又实用:

ProjectName.Domain // 领域层(核心!)

  • BoundedContexts // 按限界上下文分文件夹
  • InventoryManagement // 库存管理上下文
  • Aggregates // 聚合根
  • Inventory.cs // 库存聚合根
  • ValueObjects // 值对象
  • StockQuantity.cs // 库存数量值对象
  • Events // 领域事件
  • StockLockedEvent.cs
  • DomainServices // 跨聚合的领域服务
  • ProjectName.Infrastructure // 基础设施层

  • Repositories // Repository实现
  • EventBus // 事件总线实现
  • ProjectName.Application // 应用层(协调领域层和基础设施层)

  • Commands // 命令(写操作)
  • Queries // 查询(读操作)
  • ProjectName.Api // API层

  • Controllers
  • 这样分层的好处是:改业务逻辑只动Domain层,换数据库只动Infrastructure层,真正做到”关注点分离”。去年我们把一个项目的数据库从SQL Server换成PostgreSQL,只改了Repository实现,Domain层一行没动,一周就搞定了!

    避坑指南:这些”坑”我替你踩过了

    最后说几个落地时最容易踩的坑,都是我和团队付过学费的:

  • 别过度设计:小项目别硬套DDD!如果业务逻辑简单(比如就几个CRUD接口),用传统三层架构更快。我见过有人给博客系统做DDD,搞了5个限界上下文,结果开发效率反而低了。
  • 聚合根别设太多:一个限界上下文1-3个聚合根就够了,多了会导致事件通信复杂。之前做电商系统,一开始把”商品”、”分类”、”品牌”都设成聚合根,结果查询商品详情要查三个聚合,性能很差,后来合并成”商品”一个聚合根,问题解决。
  • 用工具验证模型:推荐用”领域模型评审会”,把模型讲给业务专家听,如果他听不懂,说明模型没设计好。我们每次模型设计完,都会找业务专家过一遍,比如上次把”库存锁定”模型讲给仓库经理听,他说”应该叫’预占库存’,我们平时都这么说”,后来改了名字,团队沟通效率一下提高了。
  • 如果你正在做.NET项目,不妨试试用DDD的思路梳理下业务——先画张领域模型草图,再对照着调整代码结构。遇到具体问题可以在评论区留言,咱们一起讨论怎么解决!记住,DDD不是”银弹”,但它绝对是复杂业务系统的”导航图”,用好了能让你的代码跟着业务”成长”,而不是变成一团乱麻。


    好多人问我,是不是只要是.NET项目,不管大小都得用DDD?其实真不是这么回事儿。就像你不会拿手术刀切水果一样,工具得看场景用。我去年帮一个朋友做内部办公工具,就三个功能:员工信息录入、请假申请、部门统计,数据库表都没超过5张。当时他听说DDD“高级”,非让我用这个架构,结果我花了三天划限界上下文、设计聚合根,最后写出来的代码比业务逻辑还多——员工实体当聚合根,请假单当值对象,结果部署的时候服务器跑起来直卡,后来换回传统三层架构,控制器直接调EF Core,两天就改完了,运行流畅得很。所以小项目真没必要硬套DDD,简单CRUD用MVC+Repository模式足够,开发快、维护成本还低。

    那什么时候该用DDD呢?就得看业务复不复杂了。比如企业ERP系统,光财务模块就有应收、应付、成本核算,每个模块还有自己的规则:应付账款要分账期、预付款、质保金,成本核算要按品种法、分批法算,这种时候不用DDD,代码根本捋不清。我前年带团队做物流WMS系统,一开始用传统架构,结果“入库”功能就出问题了:商品要先进先出,还要关联采购订单、质检报告、库位分配,业务逻辑全堆在Service里,一个InboundService类写了2000多行,新需求加个“批次效期管理”,改了500行代码,上线后库存数量对不上,查了三天才发现是改代码时漏了一个校验逻辑。后来重构用DDD,把“入库管理”当限界上下文,“入库单”当聚合根,里面包含“入库明细”“批次信息”“库位分配”三个子对象,所有操作都通过入库单聚合根处理,再加个领域事件“入库完成”通知库存模块更新,这下逻辑清晰多了,后面加新规则,只需要在聚合根里加方法,测试也只测这部分,效率高了不少。所以你看,业务规则多、需求变化快的中大型系统,DDD才能发挥真正的价值,帮你把复杂业务拆成可管理的小块,代码跟着业务走,而不是反过来被业务“牵着鼻子跑”。


    所有.NET项目都适合用领域驱动设计(DDD)吗?

    不是所有项目都适合。DDD更适合业务逻辑复杂、需求频繁变化的中大型系统(如企业ERP、物流管理平台等),能有效解决业务与代码脱节的问题。对于简单CRUD项目(如小型博客、内部工具),传统三层架构开发效率更高。文章中提到“小项目别硬套DDD”,过度设计反而会降低开发效率。

    如何判断某个实体是否应该作为聚合根?

    聚合根是聚合的“管理者”,判断标准主要有3点:(1)是否有独立的业务标识(如订单号、用户ID);(2)是否需要全局唯一追踪(如订单需要跨系统查询);(3)是否负责协调聚合内其他对象的行为(如订单需管理订单项的增删改)。例如文章中提到的“订单”聚合根,需统一管理订单项、收货地址等子对象,符合上述特征。

    EF Core在DDD项目中如何处理领域对象的持久化?

    EF Core需遵循“聚合根作为最小持久化单位”原则,即仅通过聚合根的Repository进行数据操作,避免直接访问子对象(如订单项)。可通过配置实体关系(HasMany/WithOne)映射聚合结构,用Owned Entity类型存储值对象(如地址、金额),并利用EF的变更跟踪确保领域对象状态一致性。文章中提到“只暴露聚合根的Repository”,可有效避免数据一致性问题。

    划分限界上下文时,有哪些简单实用的方法?

    可通过“业务术语法”和“团队职责法”划分:(1)业务术语法:不同上下文的业务术语是否有歧义?如“库存管理”中的“锁定”与“订单履约”中的“锁定”含义不同,需分开;(2)团队职责法:由不同团队维护的业务模块,通常对应不同上下文(如财务团队负责“账务管理”,仓储团队负责“库存管理”)。文章中物流系统通过此方法拆分“库存管理”和“订单履约”上下文,解决了逻辑耦合问题。

    0
    显示验证码
    没有账号?注册  忘记密码?