如何一步一步用DDD设计一个电商网站(三)—— 初涉核心域
本系列所有文章
如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念
如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户
如何一步一步用DDD设计一个电商网站(五)—— 停下脚步,重新出发
如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
如何一步一步用DDD设计一个电商网站(十一)—— 最后的准备
如何一步一步用DDD设计一个电商网站(十二)—— 提交并生成订单
如何一步一步用DDD设计一个电商网站(十三)—— 领域事件扩展
阅读目录
一、前言
结合我们本次系列的第一篇博文中提到的上下文映射图(传送门:如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念),得知我们这个电商网站的核心域就是销售子域。因为电子商务是以信息网络技术为手段,以商品交换为中心的商务活动,一个好的核心域设计可以大大提升企业的竞争力和对市场变化的相应速度。
那么我们开始设计领域对象。对于设计领域对象的基本概念不了解的可以先阅读我的该系列第二篇文章(传送门:如何一步一步用DDD设计一个电商网站(二)—— 项目架构)。
二、定义几个基类
我相信我们大部分人会以如下的方式去存放我们定义的基类,见图1。
【图1】
这是一种比较常规的技术分层思维方式产生的结果,在某些项目文件中或多或少有那么几个"Base"、"Core"、"Common"等的文件夹存放着一些通用的类,它们起着对当前项目中类的抽象、实现通用性支撑性功能的作用。然而在DDD中这些都应属于基础设施层的事情,这样能够保证其他层专注于自身的职责,不会把本应内聚的东西泄露到这些类中。如我们当前的领域层就专注于领域建模,里面的概念全部与通用语言相关。说干就干,搬到基础设施层去,再取个能表达出一致概念的名字的模块存放,如图2。
【图2】
三、核心域(销售子域)中有什么
“销售”用通俗的话讲就是“把商品卖给用户”,这几个字中就已经凸显出几个概念:“商品”,“用户”,“卖”。以下就是”商品“和”用户“的代码实现:
【图3】
【图4】
我相信有许多人会以图3和图4的方式定义我们的商品和用户,乍一看的确符合对商品、用户概念的独立的理解。但在DDD中有不同的限界上下文,每个限界上下文专注处理自身的业务,多个限界上下文之间是以协作的方式工作。保证多个限界上下文之间的良好协作关系的方式是提高自治性。提高自治性的方式又有很多,技术方面如领域事件、消息队列、事件源等,这里暂时不展开描述。从代码层面来看,建模的时候只获取对当前上下文业务处理刚刚好大小的数据,也可以提高当前项目的自治性。
根据我们划分的上下文映射图,用户和商品是属于另外的上下文的,那么在这里我们都应该建模为值对象,因为我们是无法直接修改这些对象的内部属性的。另外,在上面2个图中,获取其他上下文中的资源时,我们作为客户方基本上是不会原封不动的消费服务方提供的数据的。比如这里面的Product.PermitNo(批文号),在大部分行业里,它与销售商品没什么关系。再如User.BlockedBalance(冻结余额),在销售的过程中,只需要知道用户有多少可用余额就好了。这个思路总结一下就是,从业务角度我们不求大而全,只求刚好满足当前业务即可,但是当业务发生变化的时候我们的领域模型也要及时反映出调整后的通用语言概念。得到修改后的模型:
【图5】
【图6】
对了,不管是值对象还是实体和聚合,习惯在构造函数中的做好守卫验证,有利于表达出什么样的领域对象是合法的。
四、更好的存活于分布式背景下
在某些背景下一个限界上下文是作为独立的服务对外提供API进行访问的,特别在电商行业,分布式系统的构建是个普遍情况,方式也多元化,各种RPC框架、Restful等技术选型,SOA、微服务等实现理念层出不穷。如何最大化的降低技术变更和业务变化导致的上下文划分调整的影响,也是我们要考虑的重要问题。
对于我们.Net开发人员来说,在分布式场景下用的最多的方式无非是WebAPI和WCF了。这种方式也就是在第一篇文章中所提到的发布语言和开放主机服务,那么对于客户端来说需要做好防腐层(第一篇文章中有提到)的工作,好避免外部上下文的概念侵入到自身的领域概念中。一个普遍的防腐层实现时序图,其中真正负责防腐层工作的是XXXAdapter和XXXTranslator,如下(摘自[Vaughn Vernon]《实现领域驱动设计》):
【图7】
我们这里实现的相关类如下图所示定义:
【图8】
其中1存放着访问远程资源的接口定义,2是其实现方式。这样设计的好处是,对于领域层的建模隐藏了数据获取的实现细节。并且当我们实际开发的时候可能由于需要配合服务方还未准备好,但是这丝毫不影响我们的开发工作,我们可以定义个Mock类来实现这里的IRemoteServices中的接口,就可以顺利地进行开发工作。
其中ProductAdapter、UserAdapter分别负责请求商品上下文和用户上下文并取得原始结果,ProductTranslator、UserTranslator则是通过解析原始结果,转换为我方上下文中需要的领域模型概念。以下则是核心部分的实现:
【图9】
【图10】
五、结语
作者:Zachary
出处:https://zacharyfan.com/archives/121.html
▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描右侧的二维码~。
定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。
如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。
如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。