领域驱动设计DDD之概览

在从事开发多年以后,你是否会感受本身只是一个业务CRUD Boy,并认为业务没有多少技术含量。你是否会陷入业务的泥潭中,各类复杂交错的业务规则使得代码开始腐烂开始失控,项目开始变得难以维护,迭代举步维艰。若是你开始意识到这个问题的话,那么我十分推荐你开始学习领域驱动设计面向领域建模的设计方式。前端

DDD是什么呢?

DDD即Domain Driven Design,翻译成中文的话就是领域驱动设计,首先咱们应该先理解这里的领域是什么意思?假设公司内部正在开发一套电商平台,而电商平台中包含了库存、订单、商品等核心业务。这些核心业务逻辑其实呈现的就是电商平台领域。通俗的理解就是一整套体系的业务知识即表明了一个领域。比如在线教育平台,它须要有一套体系的业务,包括招生、线上教学、课程等内容。咱们将这些业务抽象出领域模型,而这些领域模型表达了产品经理所阐述的业务需求,咱们反复地用这些领域模型与产品经理进行讨论沟通最终肯定初步的领域模型。再使用初步的领域模型指导代码设计开发。数据库

简而言之就是:编程

业务知识 --> 领域模型 --> 项目设计与代码开发bash

不一样的电商平台的核心业务逻辑大都是类似的,这部分领域知识是能够进行复用,区别在于不一样公司使用的不一样的编程语言,不一样的前端控制框架,数据库框架。采用领域驱动设计的好处在于项目以领域模型为核心,而Spring MVC、Struts等前端控制框架或者Hibernate、Mybatis对象数据库框架属于外围技术基础,领域模型其实并不与这些基础技术产生耦合,因此在领域模型不变的状况下,咱们是很容易对咱们的基础设施进行更换的。markdown

那咱们以前的开发也是有这些业务逻辑支撑?那咱们以前传统的开发模式为何不能称之为面向领域驱动设计呢?回想一下咱们以前的代码开发,好比一个实现一个购物车下订单的功能,咱们根据订单表的字段,依次用商品字段、金额字段、用户字段等拼凑出订单表中的一条记录,而后写入到订单表当中去。其实咱们是面向数据库在进行开发,以数据库为重心,而业务逻辑零散地分布在各个Service当中去,采用的是面向过程的编程方式而不是面向对象的方式,也就没有造成一套有机的业务逻辑。框架

而面向领域驱动设计则不同,它以领域为重心,以刚才的购物车下订单功能为例子。在DDD当中,会将购物车相关的业务逻辑封装到一个ShoppingCart对象中,并直接调用shoppingCart.takeOrder()下订单的方法,代码的重心从生成订单表中的记录转移到购物车对象自己,而具体数据库中如何生成这条记录并不属于咱们的核心业务逻辑,它被下放到基础设施层,由Repository或者Dao等数据交互对象负责去持久化咱们对领域模型下达的指令所产生的数据库变化。编程语言

项目中的代码经过不一样领域模型之间的配合体现了领域专家的业务意图,使得系统内部造成一套自运行的有机总体。在以往面向过程式的开发方式,咱们很难让领域专家或者产品经理直接看懂咱们的业务逻辑,而DDD的优点在于shoppingCart.takeOrder()这种相似白话的方式直接体现出业务含义。而咱们代码中的领域模型和领域专家口中的领域模型是一致的。甚至咱们能够在没有基础设施技术支持的状况下,直接建模领域模型开始编写代码并测试验证业务逻辑。微服务

领域模型总结

  • 抽象了领域内的核心概念,并创建概念之间的关系;
  • 领域模型维护了领域内的数据之间一致性的,也即咱们的业务规则;

为何咱们须要DDD

  1. 经过统一语言使得咱们开发人员与领域专家(产品经理)可以更好的沟通。更准确的表达咱们的业务,而这些业务代码按照正如领域专家所想的那样工做。
  2. 经过战术设计将业务知识进行集中,浓缩在领域模型当中。
  3. 以往的业务代码中,咱们总须要嵌入许多注释,由于过程式的代码自解释的能力不好,而最好的设计就是代码自己。经过代码自己将领域中的知识呈现出来。
  4. 经过战略设计解构复杂的业务系统,并使其简单化。

战术设计和战略设计是DDD针对局部和总体的设计指导。oop

贫血症和失忆症

传统的开发模式中,咱们常用的是一个JavaBean,其中只有映射到数据库的字段,并无业务行为。经过填充这个JavaBean,并在对象外部进行业务逻辑的编写,如计算订单的最终金额填充到JavaBean中再交由数据库映射框架进行持久化。学习

而其实这就是Evans所说的贫血症,由于数据和业务行为隔离开来,造成一种无机的代码组成。其实这并不是面向对象的编码方式,当咱们看到Order对象时,咱们根本不知道它含有计算最终金额的业务逻辑,而这其实就是一种所谓的贫血症引发的失忆症。数据和行为并无紧密的联系到一块儿。

public static void buyProduct(Long orderId, Double price, Long productId, Float discount) {

        Order order = new Order();

        order.setOrderId(orderId);
        order.setProductId(productId);
        order.setDiscount(discount);
        order.setPrice(price);

        // 计算最终付款金额
        if(discount == 0) {
            throw new RuntimeException("折扣不可为0!");
        }

        Double paid = price * discount;
        order.setPaid(paid);

        OrderDao.save(order);

}
复制代码

而更好的方式咱们应该经过将数据和业务行为整合到一个对象中去,让两者造成一种有机的代码组成。在GRASP对象职责中有一个原则就是当一个对象拥有某个方法所需的属性时,那么更应该将这个方法放置到这个对象中去,而不是放在其余地方。

public static void buyProduct_(Long orderId, Double price, Long productId, Float discount) {

        Order order = new Order(orderId, price, productId, discount);

        order.calculatePaid();

        OrderDao.save(order);

}
复制代码

上面的例子更加符合咱们对于业务的描述,但仅仅强调将业务逻辑封装到数据对象中去还不够,咱们还须要经过这些对象之间的协做来进行业务的表达。一个很显而易见的例子就是关于转帐的例子。

public static void main(String[] args) {

        Account a = new Account(5);

        Account b = new Account(5);

        double transferMoney = 4;

        if (a.getMoney() < transferMoney) {
            throw new RuntimeException("余额不足!");
        }

        a.setMoney(a.getMoney() - transferMoney);

        b.setMoney(b.getMoney() + transferMoney);

    }
复制代码

更好的作法咱们应该借鉴面向对象的建模方式,将领域知识封装到帐户Account模型中去。

public void transfer(Account another, double transferMoney) {

        if (money < transferMoney) {
            throw new RuntimeException("余额不足!");
        }

        money = money - transferMoney;
        another.setMoney(another.getMoney() + transferMoney);

    }
复制代码

A帐户向B帐户进行转帐。

public static void main(String[] args) {

        Account a = new Account(5);
        Account b = new Account(5);

        double transferMoney = 4;

        a.transfer(b, transferMoney);

    }
复制代码

咱们经过领域模型之间的协做,呈现出来的代码就像白话同样。有主语谓语宾语。主语是A帐户,谓语是transfer()方法,宾语是B帐户。这样一来,代码的自解释能力也就很是强了。就算产品经理不懂编程语言的话,看到咱们的代码的话也能理解其中的业务目的。

DDD的适用场景

那什么场景才是适合DDD的场景呢?
在可预见的将来中,项目的业务复杂度会愈来愈高,那就很是适合使用DDD的设计方式。而若是项目所有都是一些很是简单的增删查改而不多包含业务知识的话,那真是想D也D不起来,由于DDD的思想就是为了经过模型来表达领域知识,而领域知识自己就很匮乏的话,表达也就无从谈起。

如何DDD

咱们能够经过和领域专家(产品经理)使用一致的通用语言,利用通用语言抽象出领域模型,在根据这些领域模型进行代码的落地开发,这样一来便能更好的在代码中去体现业务领域知识。咱们须要转变咱们以往的思惟惯性,少从技术层面考虑,而更应该从业务层面去考虑。个人理解是,DDD是一套基于领域为核心的面向对象编程的方法论。它主要经过两种设计来实现DDD。一种是战术设计,你能够理解为在单个微服务中的设计。一种是战略设计,你能够理解为多个微服务之间如何进行协做。

战术设计

战术设计侧重点在于局部的设计,主要有如下几个概念:

  • 实体:有惟一标识有生命周期,能够理解为经过实体能够对应到数据库的记录。
  • 值对象:用来描述实体的属性。
  • 聚合:包含实体和值对象,并维护了事务一致性。
  • 资源库:用于获取或保存聚合。
  • 领域服务:放置一些不适合在聚合中的业务逻辑。

咱们简单的讲解一下这几个概念是如何在单个限界上下文(即单个微服务)中进行工做的。聚合由实体、值对象进行组成,它维护了事务的一致性。而聚合又由资源库进行持久化以及查找或者获取。而当一些业务规则并不能很好的放入实体或者值对象上时,咱们可使用领域服务。

战略建模

战略设计侧重点在于总体内的不一样局部如何协做的设计,主要有如下几个概念:

  • 限界上下文:一类领域模型运做的环境。好比电商中的商品模块即一个限界上下文。
  • 上下文映射图:不一样类的领域模型如何交互。好比在电商平台中商品模块是如何与库存模块进行交互的。

限界上下文是一种概念上的边界,领域模型便工做于其中,也即一个限界上下文对应了咱们设计的一个微服务。而不一样上下文如何进行沟通的话,则利用上下文映射图的概念来进行指导开发。


关于DDD的讨论很是之多,每一个人的看法都不同,这也是DDD为何难以流行起来的缘由之一。可是DDD的思想仍是很是值得借鉴的。关于DDD的学习我的很是推荐必定要阅读原著DDD以及IDDD。

如下是笔者上传的关于DDD和IDDD的高清PDF书籍,但愿能够帮到想学习DDD的朋友们。

《领域驱动设计:软件核心复杂性应对之道》 提取码:6290

《实现领域驱动设计》 提取码:xl3t

关于DDD的理解各有不一样,欢迎网友评论一块儿探讨。

转自个人我的博客 vc2x.com

相关文章
相关标签/搜索