教会你何时定义领域服务

作者:CQITer小编 时间:2018-05-22 16:10

字号
人工智能+区块链的发展趋势及应用调研报告

若遵循基于面向对象设计范式的领域驱动设计,并用以应对纷繁复杂的业务逻辑,则强调领域模型的充血设计模型已成为社区不争事实。我将Eric提及的战术设计要素如Entity、Value Object、Domain Service、Aggregate、Repository与Factory视为设计模型。这其中,只有Entity、Value Object和Domain Service才能表达领域逻辑。

领域驱动设计

为避免贫血模型,在封装领域逻辑时,考虑设计要素的顺序为:

Value Object -> Entity -> Domain Service 

切记,我们必须将Domain Service作为承担业务逻辑的最后的救命稻草。之所以把Domain Service放在最后,是因为我太清楚领域服务的强大“魔力”了。开发人员总会有一种惰性,很多时候不愿意仔细思考所谓“职责(封装领域逻辑的行为)”的正确履行者,而领域服务恰恰是最便捷的选择。

就我个人的理解,只有满足如下三个特征的领域行为才应该放到领域服务中:

领域行为需要多个领域实体参与协作

领域行为与状态无关

领域行为需要与外部资源(尤其是DB)协作

假设某系统的合同管理功能允许客户输入自编码,该自编码需要遵循一定的编码格式。在创建新合同时,客户输入自编码,系统需要检测该自编码是否在已有合同中已经存在。针对该需求,可以提炼出两个领域行为:

验证输入的自编码是否符合业务规则

检查自编码是否重复

在寻找职责的履行者时,我们应首先遵循“信息专家模式”,即“拥有信息的对象就是操作该信息的专家”,因此可以提出一个问题:领域行为要操作的数据由谁拥有?针对第一个领域行为,就是要确认谁拥有自编码格式的验证规则?有两个候选:

拥有自编码信息的“合同(Contract)”对象

体现自编码知识概念自身的“自编码(CustomizedNumber)”对象

我倾向于定义CustomizedNumber值对象,将该检测规则封装其内,并在构造函数中对其进行验证。在领域驱动设计中,值对象往往用于封装这些基础概念。由于自定义的类型可以封装领域行为,就可以有效地实现职责的“分治”,实现对象的协作。

若要检查自编码是否重复,则需要从数据库中查找,这就需要通过Repository与DB协作。基于前面总结的三个特征,则该职责应该分配给一个领域服务,例如DuplicatedNumberChecker。

从职责分配的角度看,实体Contract又或者值对象CustomizedNumber才应该是承担该职责的合理选择。为何我却定义了这么一条例外原则呢?究其原因,就是在领域驱动设计中,我们应尽量保证实体与值对象的纯粹性,尤其不应该依赖于Repository(资源库)。继续深挖根本原因,是因为实体与值对象的生命周期是由Repository管理的。倘若被管理的实体对象还依赖了Repository,就要求该实体对应的Repository在管理实体对象的生命周期的同时,还需要管理它与Repository的依赖,这并不合理。值对象在一个聚合(Aggregate)边界之内,道理相同。

举例来说,假设Contract是聚合根,如果将检查重复编码的职责分配给该实体对象(或值对象CustomizedNumber),内部就需要依赖ContractRepository。然而,Contract的获取也是通过Repository得到,在基础设施层对ContractRepository的实现时,其实并不知道该如何管理二者之间的依赖。如果Contract实体还要依赖其他Repository,就更不可能了。

public class ContractRepositoryImpl implements ContractRepository { 

    public Contract contractById(Identity contractId) { 

        //这里并不知道Contract对象需要注入ContractRepository对象自身 

    } 

若真要解决此依赖管理问题,较简单的做法是为Contract提供一个setContractRepository()的依赖注入方法。不过,当Contract是通过Repository来获得时,如Spring、Guice之类的DI框架都无法注入这一依赖,因而需要显式调用,这就会引入对Repository具体实现的耦合。这样的耦合放在领域层,会导致本来单纯的领域层内核依赖了外部资源。倘若将这种具体耦合往外推,例如推到应用层,又会加重调用者的负担。

领域服务则不存在此问题,因为它的生命周期不是由Repository管理。如下的领域服务定义是合情合理的:

public class DuplicatedNumberChecker {     

    @Repository 

    private ContractRepository repository;      

    public boolean isDuplicate(CustomizedNumber number) { 

        return repository.existsNumber(number); 

    } 

责任编辑:CQITer新闻报料:400-888-8888   本站原创,未经授权不得转载
继续阅读
热新闻
推荐
关于我们联系我们免责声明隐私政策 友情链接