领域驱动开发和面向数据开发的思考

本文探讨了面向数据开发与领域驱动设计两种方法的差异。面向数据开发通常从数据库表出发,易受数据变动影响,而领域驱动设计强调业务场景和实体行为,确保业务逻辑集中且易于维护。通过比较两者在处理‘物’与‘事’的顺序,以及实际开发过程中的实现方式,指出领域驱动设计能更好地封装业务逻辑,提高代码可读性和可维护性。文章通过示例展示了两种方法的不同实现,并强调了领域驱动设计中对象行为的重要性。
摘要由CSDN通过智能技术生成

1.先“物”后“事”还是先“事”后“物”?

通常在面对一项业务需求时,如果是面向数据的开发方式,程序员们首先想到的是会有哪些信息?要存储哪些字段?如何设计一个数据库表。然后再针对数据库表进行增删查改,完善业务逻辑,操作这些信息,最后完成需求。

我总结为,这是一个先形成“物”的概念,然后再完成“事”的概念,是自下而上的开发(从数据库开始编码到控制界面编码)。
这看起来很自由,毕竟有了数据,随便事情怎么变动,都可以完成逻辑。

然而,这是基于数据库表不再变动的情况下的,如果之前考虑不周,在数据库表上要删除或增加几个字段,那改起来就麻烦了。

在领域驱动设计中,思考方式就有所不同,我们想的是先有一件“事”,比如某人购物,付款等这些场景并不怎么变动,变动的是其实现方式。所以我们先限定其场景范围(限界上下文),然后再针对事情中的“物”进行建模。如果场景变了,就说明我们需要再另外新建立一套模型,而不是像面对数据库表开发那样,继续改动字段,改动逻辑然后完成需求变动。这就像一个大泥球一样麻烦。
以前我时常面临这些麻烦,自从看了DDD之后,我就发现领域驱动设计其实可以解决这样的麻烦。

2.领域驱动设计到底是什么?

我认为java作为一个面向对象的语言,应该以面向对象的方式去解决问题。对象有自己的行为逻辑,或者说业务逻辑。例如,一个订单有若干订单明细项,订单总表的金额是订单明细各项金额之和。这是订单这个对象的业务规则,也必须保持这样的规则。我们称订单是个聚合,它有自己的业务逻辑,并且包含订单明细项的对象集合。

若以面向数据的方式开发,订单包含订单总表和订单明细表两张数据库表以及它们的各字段。通常在service层中实现程序要求的各种业务逻辑,那么在修改订单的数据时,在代码的各处都要注意保证类似“订单总表的金额是订单明细各项金额之和”这样的业务逻辑。程序员需要准备修改2张数据库表的对象,同时要额外注意准备修改的数据不会导致bug。保证业务逻辑的重复代码散落在程序各处,并且该逻辑还不是实现意图的核心逻辑,这增加了巨量的复杂度,过段时间或者新接手的程序员根本看不懂代码,导致出现程序能正常运行就不要去改它的现象,因为也不知从何改起。

而以领域驱动设计的方式开发就不同了,业务逻辑都集中在对象中,我想改任何数据,我都得去重建对象,调用对象的方法,保证修改的任何数据都符合它自身的业务规则,防止因为意外情况篡改了数据。即使有人跳过应用程序,直接修改数据库,若数据不符合规范,那么对象就无法重建,就会直接报错提醒管理员。这样开发人员就可以将关注点分离,可以放心地调用领域对象中的行为方法实现逻辑,再根据领域对象中的数据转化为持久化对象存储到数据库中。同时,当核心业务规则发生变化时,也可以集中了解业务知识,然后只用修改领域对象中的业务逻辑。

领域驱动认为内存中的数据,才是本体,而数据库中的数据只是其备份而已。由于内存昂贵,对象必须尽量小最好,所以一个作为聚合的对象应该只包含其具有约束的对象。例如,订单的总金额等于各项之和,订单明细项不能脱离订单总表而独立存在。

3.面向数据开发和领域驱动开发的实现区别

经过一段时间摸索,开发人员面临一个陌生业务时,也就是我们首先思考:

什么样的对象完成什么样的事情?

例如,“将产品提交到店铺售卖”这一业务。
1.在面向数据库表开发时,先有属性,然后完成事件,开发的时候可能会这样做。

public class Product extends Entity{
    //店铺ID,一般作为产品表的外键关联店铺表的主键
    private ShopId shopId;
    //产品类型,作为产品表的一个字段
    private ProductType type;
    ...
    public void setShopId(ShopId shopId){
        this.shopId = shopId;
    }
    public void setProductType(setProductType productType){
        this.productType = productType;
    }
    ...
}

然后调用的客户端代码,比如service层,完成业务事件的实际流程。

public class ProductService{
    ...
    product.setShopId(shopId);
    product.setProductType(productType);
    ...
}

2.而在领域驱动开发中,是这样做的。

public class Product extends Entity{
    private ShopId shopId;
    private ProductType type;
    ...
    //行为逻辑由实体自身完成
    public void commitTo(Shop aShop){
        //自我验证
        if(!this.isEmptyForShop){
            throw new IllegalStateException("货架已满,请腾出空位!");
        }
        //完成业务逻辑
        this.setShopId(aShop.id());
        this.setProductType(productType);
        //发布领域事件
        DomainEventPublisher
        .instance()
        .publish(new ProductedCommitted);
    }
    ...
}

客户端代码调用

public class ProductService{
    ...
    product.commitTo(shopId);
    ...
}

由此,看出如果面向数据开发,以数据为中心,客户端代码必须懂得数据核心,虽然看起来操作自由,但容易设置错误。
而面向领域驱动开发,行为由领域实体自身完成,不会向客户端暴露业务逻辑。关注点在事件,而不是数据。领域驱动开发若出错,容易找到bug,封装性更好,所以方便进行迭代开发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值