领域驱动设计概论入门

著名建模专家Eric Evans在2004年发表了他最具影响力的著名书籍:Domain-Driven Design –Tackling Complexity in the Heart of Software。(领域驱动设计-软件核心复杂性应对之道)

处理复杂软件的方式大致分为三个手段,抽象、分治和知识。

  • 抽象:将复杂的问题抽象为简单的问题
  • 分治:将复杂的问题分为多个小问题,单独处理。
  • 知识:如何抽象和分治更为合理。DDD

DDD核心

  • 统一语言:软件的开发人员/使用人员都使用同一套语言,即对某个概念,名词的认知是统一的。
  • 面向领域:以领域去思考问题,而不是模块。
  • 为了实现这两个核心,需要一个关键的角色,领域专家。他负责问题域,和问题解决域,他应该通晓研发的这个产品需要解决哪些问题,专业术语,关联关系,一般情况下最接近这个角色的就是产品经理。

如何实现统一语言

  • 通用语言

通过团队交流达成共识的,能够简单、清晰、准确描述业务涵义和规则的语言就是通用语言。

通用语言是团队统一的语言,不管你在团队中承担什么角色,在同一个领域的软件生命周期里都使用统一的语言进行交流。

通用语言中的名词可以给领域对象命名,如用户/订单等。而动词则表示一个动作或事件,如用户已登录/订单已付款等。

基于它,就能够开发出可读性更好的代码,业务需求也能更准确转化为代码设计。

  • 限界上下文(Bound Context)

用来确定语义所在的领域边界。在统一的领域边界内用统一的语言进行交流。

用来封装通用语言和领域对象,提供上下文环境,保证在领域之内的一些术语、业务相关对象等(通用语言)有一个确切的含义,防止一语双关的现象。

理论上限界上下文就是微服务的边界。限界上下文是微服务设计和拆分的主要依据。

领域可以拆分为多个子领域。一个领域相当于一个问题域,领域拆分为子域的过程就是大问题拆分为小问题的过程。

  • 通用语言定义上下文含义,限界上下文则定义领域边界
  • 实现过程过程:事件风暴>领域(用户)故事分析>提取领域对象>领域对象与代码模型映射>代码实现

什么是面项领域

编程思想

  1. 面向过程POP:从解决问题的步骤考虑,实现解决问题的步骤。至上而下处理问题。
  2. 面向对象OOP:将问题事务分解成多个对象,由对象去处理各自的事务。面向领域-从内部考虑,发现共性,定义聚合根对象,对外提供服务。抽象化处理问题。
  3. 面向服务SOA:从用户业务出发,将业务功能做为松耦合的服务。微服务是面向服务的延伸
  4. 面向切面AOP:从类的角度出发,抽象出通用的功能。
  5. 面向组件COP:EJB组件

好处

  1. 因为大家都使用统一的一套通用语言,所以沟通成本会大大减小,不会在讨论A的时候以为是B。
  2. 对使用产品的用户有好处,他能在产品不断更新过程中,有一套统一流畅的体验。用户不用在每次软件更新时都要抱怨为什么之前的一个数据保存后没有用到了。
  3. 每个团队在它的上下文中能够更加明确自己领域内的概念,因为上下文是领域的解系统;
  4. 当限界上下文之间发生交互时,团队与上下文的一致性,能够保证我们明确对接的团队和依赖的上下游。
  5. 面向领域去开发产品有助于我们深入分析产品的内在逻辑,专注于解决当前产品的核心问题,而不是冗余的做很多功能模块,(避免根据用户/运营反馈的问题就去更改产品逻辑,上线后用户不用,吐槽用户乱提需求。)

难处

  1. 落地实践少,最佳实践太少意味着,我们可以参考的资料就少,承担的项目失败的风险就大。
  2. 出现了很多的新的概念和术语,比如聚合根,值对象,六边形架构,CQRS(命令和查询职责分离),事件驱动等等概念。
  3. 需要我们在领域建模花费很多的时间和精力,而且还可能导致付出和收益不成正比的情况。因为在界限上下文的划分上是非常考验架构师的业务水平。如果没有将业务模型很好的识别出来,那么模型就会在迭代的过程中腐败掉了。
  4. DDD是一套思想,一套领域建模设计,一套在特定上下文环境中使用的。所有在不同的团队中需要设计不同的方案,他需要结合团队的实际情况去实行,才能达到效果。

现代互联网产研团队的构成一般是市场/运营、产品、UI交互、前端、后端、测试。这些角色的分工是将一个产品开发上线的各个过程拆分出来,然后每个过程专人负责,可以有效提高生产效率,这一套流程是标准的流水线作业。

这样做的好处毋庸置疑,坏处也很明显,每个人只盯着自己那一块,而忽略整体了。

常用工具

  1. 流程图
  2. 时序图
  3. 实体关系图(ER模型)
  4. 用例图
  5. UML图(领域建模)

领域模型

软件开发一般分为瀑布式,敏捷式。

  • 瀑布式:项目经理业务分析生产整体需求,开发人员进行开发。
  • 敏捷式:也需要大量的分析,精细的业务模块,是小步迭代,周期性交付,敏捷是拥抱变化,可能产生大量的需求或者业务模型变更,将带来不小的维护成本。
  • DDD:更小粒度的迭代设计,领域模型就是能够精确反映领域中某一知识元素的载体,将专业知识转化为领域模型,在此之上进行代码设计。

失血模型

class User{

String id;

String name;

Integer age;

Getter/Setter..

}

只有一个领域对象,Service负责所有操作,包括入库。

Service>Domain entity

贫血模型

class UserDao {

    @Autowired

    JdbcTemplate jdbcTemplate;

    public void updateName(String name,String id){

        jdbcTemplate.excute("update user u set u.name = ? where id=?",name,id);

    }

}

class UserService{

    @Autowired

    UserDao userDao;

    void updateName(String name,String id){

        userDao.updateName(name,id);

    }

}

Dao中实现了持久化逻辑,Service负责业务。

Service>DAO>Domain entity

充血模型

interface UserRepository extends JpaRepository<User,String>{

    //springdata-jpa自动扩展出save findOne findAll方法

}

class UserService{

    @Autowoird

    UserRepository userRepository;

    void updateName(String name,String id){

        User user = userRepository.findOne(id);

        user.setName(name);

        userRepository.save(user);

    }

}

在贫血模型基础上多加了Domain层,将Dao放入到了Domain里面,Service不需要关心数据库操作,只需要关注领域对象本身。Repository模式就是在于此,屏蔽了数据库的实现。

Service> Domain > DAO

胀血模型

void updateName(String name,String id){

    User user = new User(id);

    user.setName(name);

    user.save();

}

在充血模型的基础上,将Service和Domain近合并。

Domain > Dao

设计模式

传统模式

  • MVC 

User interface>service>dao>model

已数据ER图驱动开发,面向数据编程,当service业务增多,dao失去了明确意义,称为贫血症引起的失忆症

  • DDD模型

User interface>application>domain>infrastucture

用户界面:用户展示和用户指令

应用层:协调应用活动,不包含业务逻辑,不保留业务对象,只保存任务进度。

领域层:领域信息/业务对象状态,将业务状态和信息委托给基础层

基础层:支撑库,对业务数据持久化,和用户展示库等。

 

概念定义

  • 领域:包括问题域和解系统,限界上下文
  • 实体:当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。
  • 值对象:当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。
  • 聚合根:Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。
  • 领域服务Services:一些重要的领域行为或操作,可以归类为领域服务。
  • 领域事件DE:领域事件是对领域内发生的活动进行的建模。
  • 资源库(Repositories):springdata中的xxxRepository就是基于ddd的充血模型
  • 模块(Moudles):一种控制限界上下文的手段,一般尽用一个模块来表示一个领域量的限界上下文。在工程中开源用包的方式,如com.公司名.组织架构.业务.上下文.*

设计领域模型的一般步骤如下:

  1. 根据需求划分出初步的领域和限界上下文,以及上下文之间的关系;
  2. 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象;
  3. 对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根;
  4. 为聚合根设计仓储,并思考实体或值对象的创建方式;
  5. 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构。

扩展

CQRS模式

CQRS — Command Query Responsibility Segregation,命令查询的责任分离模式,将系统中的操作分为两类,即「命令」(Command) 与「查询」(Query)。

  • 命令:是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。
  • 查询:即不会对数据产生变化的操作,只是按照某些条件查找数据。

核心思想

是将这两类不同的操作进行分离,然后在两个独立的「服务」中实现。这里的「服务」一般是指两个独立部署的应用。在某些特殊情况下,也可以部署在同一个应用内的不同接口上。

Command 与 Query 对应的数据源也应该是互相独立的,即更新操作在一个数据源,而查询操作在另一个数据源上。看到这里,你可能想到一个问题,既然数据源进行了分离,如何做到数据之间的同步呢?让我们接着往下看。

CQRS 的架构图

 

有点类似「读写分离」

六边形架构

传统MVC架构分为表现层、业务逻辑层、数据访问层,各层相互依赖,无法单独调整某一层。

六边形架构又称为端口-适配器。六边形架构将系统分为内部(内部六边形)和外部,内部代表了应用的业务逻辑,外部代表应用的驱动逻辑、基础设施或其他应用。内部通过端口和外部系统通信,端口代表了一定协议,以API呈现。一个端口可能对应多个外部系统,不同的外部系统需要使用不同的适配器,适配器负责对协议进行转换。这样就使得应用程序能够以一致的方式被用户、程序、自动化测试、批处理脚本所驱动,并且,可以在与实际运行的设备和数据库相隔离的情况下开发和测试。

 

六边形体系结构基于三个原则和技术:

  1. 明确区分应用程序,域和基础设施。
  • 应用程序:与外部交互,路由、api、序列化等。
  • 域:纯粹的业务逻辑代码。
  • 基础设施:需要实现业务逻辑的支持环节,比如数据入库、第三方功能等。
  1. 依赖关系从应用程序和基础结构到域:域不依赖外部。
  2. 使用端口和适配器隔离边界,
  • 端口:域定义端口
  • 适配器:链接域的端口与外部交互适配处理。

总结

框架易学,思想难学

如果单从研发角度考虑DDD,开发进行领域建模,然后遵从康威定律,将软件架构设计映射到业务模型中。

康威定律:任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。

参考:

  1. 领域驱动设计:软件核心复杂性应对之道,Eric Evans
  2. 浅析DDD(领域驱动设计),https://www.jianshu.com/p/b6ec06d6b594
  3. 领域驱动设计在互联网业务开发中的实践,https://tech.meituan.com/2017/12/22/ddd-in-practice.html
  4. DDD 中的那些模式 — CQRS,https://zhuanlan.zhihu.com/p/115685384
  5. DDD术语-通用语言、限界上下文,https://www.cnblogs.com/junzi2099/p/13682028.html
  6. 领域驱动模型(DDD)在美团外卖活动管理业务的应用,https://www.jianshu.com/p/fb319d7674ff
  7. 「首席架构师看应用架构」六边形架构:三个原则和一个实现示例,https://baijiahao.baidu.com/s?id=1662240769613294810&wfr=spider&for=pc

 

 

猜你喜欢

转载自blog.csdn.net/lizz861109/article/details/112952918
今日推荐