DDD(Domain-Driven Design)领域驱动设计入门说明及示例

作者: admin 分类: 领域驱动设计 发布时间: 2019-11-21 15:36  阅读: 45 views

DDD(领域驱动设计)作为一种软件开发方法,它可以帮助我们设计高质量的软件模型。

如果你

有开发卓越软件的激情和毅力;渴望学习和进步;有能力理解软件模式,并懂得如何应用这些模式;有发掘不同设计方法的能力和耐性;勇于改变现状;着重细节,希望亲自试验;希望编写更好的代码。

可以学习并实施DDD

领域模型是什么?

领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同事包含了数据和行为,并且表达了准确的业务含义。

为什么我们需要DDD

  • 使领域专家和开发者一起工作,这样开发出来的软件能够准确地传达业务规则。
  • “准确传达业务规则”的意思是说,软件就像领域专家开发出来的一样。
  • 可以帮助业务人员自我提高。
  • 可以确保软件知识并不是掌握在少数人手中。
  • 在领域专业、开发者和软件本身间不存在‘翻译’。大家都是用相同的语言进行交流。
  • 设计就是代码、代码就是设计。
  • DDD同时提供了战略设计和战术设计两种方式。战略设计帮助我们理解哪些投入是最重要的;战术设计则帮助我们创建DDD模型中各个部件。

通常来说,领域专家将关注点放在交付业务价值上,而开发者则将注意力放在技术实现上。并不是说开发者的动机是错误的,而是说开发者的眼光被自然而然地吸引到了实现层面上。即便让领域专家和开发者一同工作,他们之间的协作也只是表面的,这时在所开发的软件中便产生了一种映射:将业务人员所想的映射到开发者所理解的。这样一来,软件便不能完全反映出领域专家的思维模型。随着时间的推移,这种鸿沟将增加软件的开发成本。随着开发者转到其他项目或离职,本应该驻留在软件中的领域知识也就丢失了。

DDD作为一种软件开发方法,主要关注以下三个方面:
1. DDD将领域专家和开发人员聚集到一起,这样所开发的软件能够反映出领域专家的思维模型。
2. DDD关注业务战略。实现面向服务架构或业务驱动架构
3. 通过使用战术设计建模工具,DDD满足了软件真正的技术需求。这些战术设计工具能使开发人员按照领域专家的思维模型开发软件。

DDD的作用是简化,而不是复杂化。

贫血领域对象实例代码

通过一个代码的改造理解DDD的意图。

第一版本

/**----version1.0---------------------------------------------**/
    //不太明确本来意图
    //保存客户对象
    public void saveCustomer(
            String customerId,
            String customerFirstName,String customerLastName,
            String streetAddress1,String streetAddress2,
            String city, String stateOrProvince,
            String postalCode,String country,
            String homePhone,String mobilePhone,
            String primaryEmailAddress,String secondaryEmailAddress
            ) {
        //判断对象是否存在
        Customer customer = customerDao.readCustomer(customerId);
        if(customer == null) {
            customer = new Customer();
            customer.setCustomerId(customerId);
        }

        customer.setCustomerFirstName(customerFirstName);
        customer.setCustomerLastName(customerLastName);
        customer.setStreetAddress1(streetAddress1);
        customer.setStreetAddress2(streetAddress2);
        customer.setCity(city);
        customer.setStateOrProvince(stateOrProvince);
        customer.setPostalCode(postalCode);
        customer.setCountry(country);
        customer.setHomePhone(homePhone);
        customer.setMobilePhone(mobilePhone);
        customer.setPrimaryEmailAddress(secondaryEmailAddress);
        customer.setSecondaryEmailAddress(secondaryEmailAddress);
        //保存对象
        customerDao.saveCustomer(customer);
    }

第二版本

/**----version2.0---------------------------------------------**/
    /**
     * "由贫血导致的失忆症"
     * 缺点:
     * 1. saveCustomer()业务意图不明显
     * 2. 方法的实现本身增加了潜在的复杂性
     * 3. Customer领域对象根本就不是对象,而只是一个数据持有器。
     */
    //保存客户对象
    public void saveCustomer1(
            String customerId,
            String customerFirstName,String customerLastName,
            String streetAddress1,String streetAddress2,
            String city, String stateOrProvince,
            String postalCode,String country,
            String homePhone,String mobilePhone,
            String primaryEmailAddress,String secondaryEmailAddress
            ) {
        //判断对象是否存在
        Customer customer = customerDao.readCustomer(customerId);
        if(customer == null) {
            customer = new Customer();
            customer.setCustomerId(customerId);
        }

        if(customerFirstName != null) {
            customer.setCustomerFirstName(customerFirstName);
        }
        if(customerLastName != null) {
            customer.setCustomerLastName(customerLastName);
        }
        if(streetAddress1 != null) {
            customer.setStreetAddress1(streetAddress1);
        }
        if(streetAddress2 != null) {
            customer.setStreetAddress2(streetAddress2);
        }
        if(city != null) {
            customer.setCity(city);
        }
        if(stateOrProvince != null) {
            customer.setStateOrProvince(stateOrProvince);
        }
        if(postalCode != null) {
            customer.setPostalCode(postalCode);
        }
        if(country != null) {
            customer.setCountry(country);
        }
        if(homePhone != null) {
            customer.setHomePhone(homePhone);
        }
        if(mobilePhone != null) {
            customer.setMobilePhone(mobilePhone);
        }
        if(secondaryEmailAddress != null) {
            customer.setPrimaryEmailAddress(secondaryEmailAddress);
        }
        if(secondaryEmailAddress != null) {
            customer.setSecondaryEmailAddress(secondaryEmailAddress);
        }
        //保存对象
        customerDao.saveCustomer(customer);
    }

第三版本

通过阅读代码你便能够理解它的业务意图。还可以通过测试来保证它功能。

/**----version3.0---------------------------------------------**/

/**
 * 对Customer模型进行了细分,每一个应用层的方法都对应着一个单一的用例流。
 * 这意味着用户界面所反映的用户操作也变得更加狭窄。
 * (对具体对象操作按业务进行细分,实际数据操作时,如mybatis的generator生成类可以通用)
 */
public interface Customer{

    public void changePersonalName(String firstName,String lastName);
    public void postalAddress(PostalAddress postalAddress);
    public void relocateTo(PostalAddress changedPostalAddress);
    public void changeHomeTelephone(Telephone telephone);
    public void disconnectHomeTelephone();
    public void changeMobileTelephone(Telephone telephone);
    public void disconnectMobileTelephone();
    public void primaryEmailAddress(EmailAddress emailAddress);
    public void secondaryEmailAddress(EmailAddress emailAddress);

}

public void changeCustomerPersonalName(
        String customerId,
        String customerFirstName,
        String customerLastName) {

    Customer customer = customerRespository.customerOfId(customerId);

    if(customer == null) {
        throw new IllegalStateException("Customer does not exist.");
    }

    customer.changePersonalName(customerFirstName,customerLastName);
}

使用通用语言来捕捉特核心业务领域中的概念和属于,它是一种团队模式。软件模型包含名词、形容词、动词和一些富有含义的语句等。团队成员通过这些语言进行交流。

关于通用语言的说明

  1. 这里的‘通用’意思是‘普遍的’,或‘导出都存在的’;通用语言在团队范围内使用,只表达一个单一的领域模型。
  2. ‘通用语言’并不表示全企业、全公司或全球性的万能的领域语言。
  3. 限界上下文和通用语言间存在一对一的关系。
  4. 界限上下文是一个相对较小的概念。刚好能够容纳下一个独立的业务领域所使用的通用语言。
  5. 只有当团队在一个独立的限界上下文中时,通用语言才通用。
  6. 虽然只工作在一个限界上下文中,但通常我们还需要和其他限界上下文打交道。这时可以通过上下文映射图进行集成。

DDD所带来的的业务价值

  1. 获得一个有用的领域模型
  2. 你的业务得到了更准确的定义和理解
  3. 领域专业可以为软件设计做贡献
  4. 更好的用户体验
  5. 清晰的模型边界
  6. 更好的企业架构
  7. 敏捷、迭代式和持续建模
  8. 使用战略和战术新工具

纯数据模型的优劣对比

示例1: 采用的是以数据为中心的方式,此时调用方必须知道如何正确的将一个待定项提交到BacklogItem对象中。这样的模型是不能成为领域模型的。

public class BacklogItem extends Entity{

    private SprintId sprintId;
    private BacklogItemStatusType status;
    //其他属性

    public void setSprintId(String sprintId) {
        this.sprintId = sprintId;
    }

    public void setStatus(BacklogItemStatusType status) {
        this.status = status;
    }
    //其他get \set
}

//调用代码
BacklogItem bl = new BacklogItem();
bl.setSprintId(xxx);
bl.setStatus(xxxx);

示例2:将行为暴露给客户,行为方法的名字清楚地表名了业务含义。

public class BacklogItem extends Entity{

    private SprintId sprintId;
    private BacklogItemStatusType status;
    //其他属性

    public void  commitTo(Sprint aSprint) {
        if(!this.isScheduledForRelease()) {
            throw new IllegalStateException("Must be scheduled for xxx");
        }

        //回收事件
        if(this.isCommiteedToSprint()) {
            if(!aSprint.sprintId().equals(this.sprintId())) {
                this.uncommitFromSprint();
            }
        }

        this.elevateStatusWith(BacklogItemStatus.COMMITTED);
        this.setSprintId(aSprint.sprintId());

        //事件形式通知调用方
        DomainEventPublisher.instance().publish(
                new BacklogItemCommitted(
                    this.tenant(),
                    this.backlogItemId(),
                    this.sprintId()
                    )
                );
    }

    //其他处理
}

//调用代码
BacklogItem bl = new BacklogItem();
bl.commitTo(sprint);

整理自《实现领域驱动设计》


   原创文章,转载请标明本文链接: DDD(Domain-Driven Design)领域驱动设计入门说明及示例

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

更多阅读