1. DDD为什么难学
我们需要合理的架构设计来避免微服务的滥用,而DDD是一种很好的指导微服务架构设计的范式,就像面对对象设计模式是一种很好的指导面向对象编程的范式一样。
DDD是由埃里克·埃文斯在2004年提出来的,但是一直停留在理论层次,多年来的实际应用并不广泛。
直到2014年,马丁·福勒与詹姆斯·刘易斯共同提出了微服务的概念,人们才发现DDD是一种很好的指导微服务架构设计的模式。
DDD的诞生早于微服务的诞生,DDD并不是为微服务而生的,DDD也可以用于单体结构项目的设计,但是在微服务架构中DDD能发挥出更大的作用。
DDD并不是一个技术,而是一种架构设计的指导原则;
DDD不是一种强制性的规范,各个项目可以根据自己的情况进行个性化的设计。
DDD就像烹饪中餐时”盐少许、油少许”一样让人难以捉摸,而且DDD中的概念非常多,表述非常晦涩,因此很多人都对DDD望而生畏。
不同项目的行业情况、公司情况、团队情况、业务情况等不同,因此DDD不能给我们一个拿来就能照着用的操作手册。
每个人、每个团队对DDD的理解不同,如果说”一千个人心中就有一千个哈姆雷特”的话,那么也可以说”一千个人心中就有两千个DDD”,因为同一个人对DDD也可能在不同时期有着不同的理解。
很多开发人员把DDD当成一个技术,这是非常打的误区。
DDD是一种设计思想,它分为战略设计和战术设计两个层次;
DDD的战略设计可以帮助公司的领导人进行团队的划分、人员的组织、产品线的规划等,也可以帮助产品经理对产品功能进行规划,还可以帮助架构师进行项目架构的规划、技术栈的选择等;
DDD的战术设计则是对公司全员进行DDD具体实施过程的指导。
DDD的英文全称是domain driven design,翻译成中文就是”领域驱动设计”。
这里的主干词是”设计”,也就是说DDD是一种设计思想。
这里的形容词是”领域驱动”,那么什么是”领域”呢?
领域其实指的就是业务,因此DDD其实就是一种用业务驱动的设计。
传统的软件设计把业务和实现技术割裂,在系统的需求设计完成后,技术人员对系统的理解并不完全匹配。
随着系统的升级,技术人员对代码进行修改,业务人员和技术人员对系统的理解偏差越来越大,从而造成系统的扩展性、可维护性越来越差。
而DDD则是指在项目的全生命周期内,管理、产品、技术、测试、实施、运维等所有岗位的人员都基于对业务的相同理解来开展工作。
技术人员在把业务落地为设计、代码的时候,也直接把业务映射到代码中,而不是用代码去实现业务。
DDD的核心理念就是所有人员站在用户的角度、业务的角度去思考问题,而不是站在技术的角度去思考问题。
2. 领域与领域模型
“领域”(domain)是一个比较宽泛的概念,主要指的是一个组织做的所有事情,比如一家银行做的所有事情就是银行的领域。
为了缩小讨论问题的范围,我们通常会把领域细分为多个”子领域”(简称”子域”),比如银行的领域就可以划分为”对公业务子域”、”对私业务子域”、”内部管理子域”等。
子域还可以继续划分为更细粒度的子域,比如”对私业务子域”可以划分为”柜台业务子域”、”AMT业务子域”、”网银业务子域”等。
划分出子域之后,我们就能专注于子域内部的领域相关业务的处理。
领域(包含子域)可用按照功能划分为核心域、支撑域、通用域。
核心域指的是解决项目的核心问题的领域,支撑域指的是解决项目的非核心问题的领域,而通用域指的是解决通用问题的领域。
核心域是和组织业务紧密相关的、个性化的领域,支撑域则具有组织特性,但不具有通用性,而通用域则是可用被很多其他领域复用的领域。
领域的划分可以不限于技术相关的问题。
举个例子:
对于一家手机公司来讲,手机的研发、制造、销售业务就属于核心域,售后业务、财务业务就属于支撑域,而保洁、保安则属于通用域。
领域划分为不同类别后,我们就可以为不同的领域投入不同的资源:
对于核心域我们要投入重点资源,对于通用域我们可以采购外部服务,比如很多公司的保洁人员都是外包的第三方服务公司提供的。
一个公司对于领域的不同分类也决定了公司业务方向的不同。
一家注重销售的手机公司,可能手机都是从第三方采购的,只是把手机贴上自己的商标而已,对于这样的公司来讲,研发、制造业务就是通用域。
从软件开发技术这个层面来讲,领域的不同分类页决定了公司的研发重点。
对于一家普通软件公司来讲,业务逻辑代码属于核心域,权限管理、日志模块等属于支撑域,而报表工具、工作流引擎等属于可以从外部采购的通用域。
但是对于一家提供云计算基础服务的公司来讲,服务器资源管理、安全监控等属于核心域,云服务器SDK、技术文档、沙箱环境、计费模块等则属于支撑域,而操作系统、数据库等属于通用域。
对于一家想要通过研发自己的操作系统、数据库系统从而最大化地利用服务器资源的云计算公司来讲,操作系统、数据库等就属于支撑域甚至核心域了。
确定一个领域之后,我们就要对领域内的对象进行建模,从而抽象出模型的概念,这些领域中的模型就叫做领域模型(domain model)。
比如银行的柜台业务领域中,就有储户、柜员、账户等领域模型。
建模是DDD中非常核心的事情,一旦定义出了领域模型,我们就可以用领域模型驱动项目的开发。
使用DDD,我们在分析完产品需求后,就应该创建领域模型,而不是考虑如何设计数据库和编写代码。
使用领域模型,我们可以一直用业务语言去描述和构建系统,而不是使用技术人员的语言。
与领域模型对应的概念上”事务脚本”(transaction script),事务脚本是指使用技术人员的语言去描述和实现业务事务,说通俗一点就是没有太多设计,没有考虑可扩展性、可维护性,通过使用if、for等语句用流水账的形式编写代码。
如以下代码所示,”柜员取款”业务的伪代码就是一个典型的事务脚本:
public string Withdraw(string accout,double amount)
{
if (!this.User.HasPermission("Withdraw"))return "当前柜员没有取款权限";
double balance = _context.Balance.Where(q=>q.Balance < amount).amount;
if (balance == null) return "账号不存在";
if (balance<amount) return "账户余额不足";
return "OK";
}
在上面代码中,我们检查当前柜员是否拥有操作取款业务的权限,然后检查账户的余额,最后完成扣款。
包括本人在内的很多开发人员的职业生涯中都写过这样流水账式的代码。
这样的代码可以满足业务需求,而且编写简单、自然,非常符合开发人员的思维方式。
事务脚本代码的问题在于,本应该属于支撑域中的权限的概念出现在了核心域的代码中,我们应该通过AOP等方式把权限校验的代码放在单独的权限校验支撑域中。