IT教程 ·

范畴驱动设想(DDD)实践之路(一)

授权认证(IdentityServer4)

范畴驱动设想(Domain Driven Design,DDD)实在并不是新理论,人人可以看看 Eric Evans 编著的《范畴驱动设想》原稿首版是2003年,距今已十余年时刻。与如今的分布式、微效劳比拟,相对是行将步入中年的“老家伙”了。

直到近年微效劳理论被提出、被互联网行业普遍应用,人们好像又从新发明了范畴驱动设想的代价。所以看起来也确切是因为微效劳,范畴驱动设想才迎来了第二春。

不过我发明人人对DDD也存有一些误区,使其逐步成了一门“深邃的玄学”,随之又被人人置之不理。我本人在过去两年多的时刻里,研读过量本DDD相干的典范论著、也请教过一些资深DDDer,并在项目中实践过。

不过在开端进修、实践今后我又带着疑问与自身的思索从新读了一遍相干的著作理论。逐步意会到DDD作为一种头脑,实在离我们很近。

我把自身的进修历程、思索编写成系列文章,与人人一同议论进修,愿望人人可以有所收成,固然个中不正确的处所也迎接人人批评指正。

同时,在文章中我也会援用相干的论著或许一些我认为不错的案例素材,权当是我们对这些学问的细致解释,在这里一并对这些DDD先辈的不倦探究表示感谢。

范畴驱动设想(DDD)实践之路(一) IT教程 第1张

(DDD相干的典范论著)

一、关于DDD的误区

  1. DDD是处理大型庞杂项目标,我们当前营业比较简朴,不适合DDD。
  2. DDD要有一个完全的、相符DDD准绳的代码构造,这大概增添代码的庞杂度,有大概致使项目进度失控。
  3. DDD是一种框架,应当包含聚合根、实体、范畴事宜、仓储定义、限界上下文等统统DDD所提倡的元素;不然你就不是DDDer。
  4. DDD须要人人严厉遵照各自模块的边境,且存在着过量因为解耦带来的看似冗余没用的代码,会下降编码效力,构成“类膨胀”。

二、DDD离我们很近

DDD是什么?众里寻她千百度,蓦然回首,“DDD是一种可以自创的头脑,而非严厉遵照的要领论”。

1、范畴驱动设想中的范畴模子

当我们面向营业开发的历程当中,应当起首思索范畴模子而不是怎样建表。

我听过太多营业开发的声响,“口试造航母、事变拧螺丝”,一样平常事变就是建表写增编削查。为何会有如许的认知,其泉源在于表驱动设想头脑而非范畴驱动设想。

前者只能增添数据库的表数目,而后者才会构成历久的、具有营业意义的模子,如许的体系生命力才越发久长。我们也才用工程的要领来编码,从编码转身为营业域的开发专家。

范畴驱动设想(DDD)实践之路(一) IT教程 第2张

有很多关于范畴驱动设想的叙述中都并未明白我们怎样获得“范畴”,只需合理的范畴模子才有效驱动设想开发。所以建好范畴模子是症结,关于范畴模子的思索与手艺框架升级一样重要。我曾经在互联网部门分享过怎样举行范畴建模,也迎接人人与我交换沟通,有兴致的读者也可以重点阅读一下《UML和情势应用》相干章节。

范畴驱动设想(DDD)实践之路(一) IT教程 第3张

 

2、架构与解耦

在议论DDD之前我们先来议论一下“解耦”,这个词是我们在一样平常编码时刻常常说起的词语。一个具有工匠精神的程序员肯定会在代码检察阶段对一些巨无霸函数或许类举行拆分,使各部分的功用越发聚焦、下降耦合。

另一方面,在架构方面我们也会注重“解耦”,因为一个模块之间随便耦合的体系将是一切人的恶梦之源。因而,除了整齐的代码我们还须要关注整齐的架构。

架构的三要素:职责明白的模块或许组件组件间明白的关联关联束缚和指点准绳。内聚的组件肯定有明白的边境,而这个明白的边境必定作为相干的束缚指点今后的生长。

范畴驱动设想(DDD)实践之路(一) IT教程 第4张

3、从分层架构到六边形架构

3.1 分层架构

分层架构是应用最为普遍的架构情势,险些每一个软件体系都须要经由过程层来断绝差别的关注点,以此应对差别需求的变化,使得这类变化可以自力举行;各个层、以至统一层中的各个组件都邑以差别速度发生变化。

这里所谓的“以差别速度发生变化”,实在就是引发变化的缘由各有差别,这正好是单一职责准绳(Single-Responsibility Principle,SRP)的表现。即“一个类应当只需一个引发它变化的缘由”,换言之,假如有两个引发类变化的缘由,就须要星散。

单一职责准绳可以明白为架构准绳,这时候要斟酌的就不是类,而是条理。比方收集七层协定是一个定义的异常好的、典范的分层架构,简朴、易于进修明白,终究被普遍应用进而大大推动了收集通信的生长。

范畴驱动设想(DDD)实践之路(一) IT教程 第5张

一般情况下,我们会把软件体系分为这几个层:UI界面(或许接入层)、应用独占的营业逻辑、范畴普适的营业逻辑、数据库等。

接下来,另有什么差别缘由的变动呢?答案恰是这些营业逻辑自身!在每一层内部,差别的营业场景发生变化的缘由、频次也都差别,差别的场景我们离别定义为营业用例。由此,我们可以总结出一个情势:在将体系程度切分红多个分层的同时,按用例将其切分红多个垂直切片。如许做的优点就是对单个用例的修正并不会影响其他用例。

假如我们同时对支持这些用例的UI和数据库也举行了分组,那末每一个用例应用各自的UI表现与数据库,如许就做到了自上而下的解耦。另一方面,有条理就有依靠。在OSI协定中,上层通明的依靠基层。然则在软件架构中,我们更强调“依靠笼统”。即组件A依靠B的功用,我们的做法是在A中定义其须要用到的接口,由B去完成对应接口才能,如许就做到了可插拔,未来我们可以把B替换为一样完成了接口才能的组件C而对体系不会构成影响。

范畴驱动设想(DDD)实践之路(一) IT教程 第6张

3.2 整齐架构

分层架构中给人的以为是每一层都一样重要,但假如我们把关注的重点放在范畴层,同时把依靠关联依据营业由重到轻构成一个以范畴层为中间的环,即演变为一种整齐的架构作风。这里不是说其他层不重要,仅仅是为了凸显承载了营业中心的范畴才能。

范畴驱动设想(DDD)实践之路(一) IT教程 第7张

 

整齐架构最重要准绳是依靠准绳,它定义了各层的依靠关联,越往里,依靠越低,代码级别越高。外圆代码依靠只能指向内圆,内圆不晓得外圆的任何事变。平常来讲,外圆的声明(包含要领、类、变量)不能被内圆援用。一样的,外圆应用的数据花样也不能被内圆应用。

整齐架构各层重要职能以下:

  • Entities:完成范畴内中心营业逻辑,它封装了企业级的营业划定规矩。一个 Entity 可所以一个带要领的对象,也可所以一个数据构造和要领鸠合。平常我们发起建立充血模子。
  • Use Cases:完成与用户操纵相干的效劳组合与编排,它包含了应用特有的营业划定规矩,封装和完成了体系的一切效例。
  • Interface Adapters:它把实用于 Use Cases 和 entities 的数据转换为实用于外部效劳的花样,或把外部的数据花样转换为实用于 Use Casess 和 entities 的花样。
  • Frameworks and Drivers:这是完成一切前端营业细节的处所,UI,Tools,Frameworks 等以及数据库等基础设施。

3.3 六边形架构

我们把整齐架构的外部依靠依据其输入输出功用、资源范例举行整合。将存储、中间件、与其他体系的集成、http挪用离别暴露一个端口。则会演变成下面的架构图。

范畴驱动设想(DDD)实践之路(一) IT教程 第8张

“Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.”“体系能平等地被用户、其他程序、自动化测试或剧本驱动,也可以自力于其终究的运行时装备和数据库举行开发和测试”这是六边形的精华。

该架构由端口和适配器构成,所谓端口是应用的进口和出口,在很多言语中,它以接口的情势存在。比方以作废定单为例,“发送定单作废关照”可以被认为是一个出口端口,定单作废的营业逻辑决议了什么时候挪用该端口,定单信息决议了端口的输入,而端口为上游的定单相干营业屏障了实在现细节。

而适配器分为两种,主适配器(别号Driving Adapter)代表用户怎样应用应用,从手艺上来讲,它们吸收用户输入,挪用端口并返回输出。Rest API是现在最常见的应用应用体式格局,以作废定单为例,该适配器完成Rest API的Endpoint,并挪用进口端口OrderService,固然service内部大概发送OrderCancelled事宜。统一个端口大概被多种适配器挪用,本场景的作废定单也大概会被完成音讯协定的Driving Adapter挪用以便异步作废定单。

次适配器(别号Driven Adapter)完成应用的出口端口,向外部东西实行操纵,比方向MySQL实行SQL,存储定单;应用Elasticsearch的API搜刮产物;应用邮件/短信发送定单作废关照。有别于传统的分层抽象,构成一个六边形,因而也会称作六边形架构。

4、DDD是一种头脑

我愚蠢的认为,DDD即营业+解耦。大道至简、何等熟习的场景,因为这就是我们在做的事变,只不过我们大概过于关注应用了什么手艺框架、用了哪些中间件、写了哪些通用的class。

现实上DDD犹如辩证唯物主义头脑一样,哪怕我们在软件项目标某一个环节用到了,只需这个头脑为我们处理了现实问题就够了。我们没有必要为了DDD而去DDD,我们肯定是从问题中来再回到问题中去。

三、DDD有什么用

借助DDD可以转变开发者对营业范畴的思索体式格局,请求开发者消费大批的时刻和精神来细致思索营业范畴,研讨观点和术语,而且和范畴专家交换以发明,捕获和革新通用言语,以至发明模子以致体系架构层面的不合理的地方。固然有大概你的团队中并没有相干营业的专家,那末此时你自身必需成为营业专家。

一般来讲我们可以将DDD的营业代价总结为以下几点:

  1. 你获得了一个异常有效的范畴模子;
  2. 你的营业获得了更正确的定义和明白;
  3. 范畴专家可认为软件设想做出孝敬;
  4. 更好的用户体验;
  5. 清晰的模子边境;
  6. 更好的企业架构;
  7. 迅速、迭代式和延续建模;
  8. 应用计谋和战术新东西;

四、怎样DDD

经由过程前面的叙述,你脑海内里肯定闪灼几个词语“范畴模子”“解耦”“依靠笼统”“边境”。这些通用的剖析要领肯定是放之四海而皆有效的。所以我认为当你依据这几个准绳举行思索的时刻就已在DDD的路上向前迈进了一步,接下来我们连系界线上下文、Repository这两个最轻易被人人所疏忽的处所来进一步论述。

在这些步骤都做完今后,你再决议接下来怎样去编码开发。不过我敢肯定,你在这个历程当中已获得了很多高营业代价的东西。

接下来怎样去完成,你可以依据现实情况。我以为计谋DDD比战术DDD更重要,我想这就是DDD作为一种头脑的奇异地点。犹如金庸笔下的少林绝学易筋经一样,一套并没有明白招式的内功心法却能打遍武林。

1、界线上下文

范畴中还同时存在问题空间(problem space)和处理方案空间(solution space)。在问题空间中,我们思索的是营业所面对的应战,而在处理方案空间中,我们思索怎样完成软件以处理这些营业应战。

  • 问题空间是范畴的一部分,对问题空间的开发将发生一个新的中心域。对问题空间的评价应当同时斟酌已有子域和分外所需子域。因而,问题空间是中心域和其他子域的组合。问题空间中的子域一般跟着项目标差别而差别,他们各自关注于当前的营业问题,这使得子域关于问题空间的评价异常有效。子域许可我们疾速地阅读范畴中的各个方面,这些方面关于处理特定的问题是必要的。
  • 处理方案空间包含一个或多个界线上下文,即一组特定的软件模子。这是因为界线上下文是一个特定的处理方案,用以处理问题。

一般,我们愿望将子域一对一地对应到限界上下文。这类做法显式地将范畴模子星散到差别的营业板块中,并将问题空间和处理方案空间融会在一同。

然则在实践中,这类做法并不老是大概的,想像一下,谁没有保护过“毛线团”体系,如今我们就要借助界线上下文来平安的、合理的、疾速的理顺这堆交错不清的关联。

很多书本或许文章解说DDD,老是说凸起应当怎样构建代码包构造,应用什么手艺框架。我认为这是不完全实用的,所以我会花较多时刻来论述一下怎样借助界线上下文来理顺这堆“毛线团”。

范畴驱动设想(DDD)实践之路(一) IT教程 第9张

我直接应用了《完成范畴驱动设想》的相干章节的配图,权当是我对这个图的解释吧。

遗留的电子商务体系是个典范的“大线团”,我们依据履历将其在逻辑上拆解为:产物目录子域、定票据域、fa票子域,固然你也可以拆解出更多的子域,以至将产物目录子域继承向下剖析为类目子域、商品子域(虚线是逻辑子域)。别的另有一个特地用于库存治理的库存体系、以及用于贩卖展望的展望体系。

因为汗青缘由电商体系内里也存在物流相干的营业逻辑,同时物流又不可避免的作用于库存逻辑之上。而每每最难以把握的就是这部分订交的处所,这才是现实的项目场景,我们一般做法是将其归并为一个新的履约体系,作为一个支持子域去辅佐重要的电商体系。

固然,跟着营业不断生长,我们的履约情势(比方支持同城当日达、商家仓储发货、电商集堆栈发货、退货等等)、库存范例(挑唆库存、越库操纵、临期库存、残次库存等等)愈来愈庞杂,我们斟酌将其再向下剖析为履约体系2.0、库存体系2.0。

中心就是我们可以在观点上应用多个子域来剖析较大的界线上下文,也可以将多个疏散的界线上下文包含在统一个新的子域当中,终究做到“子域和界线上下文一一对应”。我个人以为,这个历程是最磨练内功心法的处所。

范畴驱动设想(DDD)实践之路(一) IT教程 第10张

上面我们已说了会拆解出来新的子域,目标使“整齐清洁”的界线上下文可以一对一的处理这个子域对应的问题空间,然则跟着拆解就必定致使“关联关联”。因为要处理问题空间,必需应用对应的子域,你可以把它拆解出去,然则它一直存在于依靠网中。

我们通用的做法是在订交的处所,定义接口。由支持的界线上下文去完成,可以做到支持上下文的插拔式切换。这里仍然是我们强调的“依靠笼统”“解耦”。

2、Repository

“关于每种须要举行全局接见的对象,我们都应当建立另一个对象来作为这些对象的供应方,就像是在内存中接见这些对象的鸠合一样。为这些对象建立一个全局接口以供客户端接见。为这些对象建立增加和删除要领……

另外,我们还应当供应可以依据某种指定前提来查询这些对象的要领……只为聚合建立资源库”援用自《范畴驱动设想》。人人和我的疑问一样,Repository是什么?DAO与Repository什么区别?为何须要Repository?

起首,Repository 是一个自力的层,介于范畴层与数据映照层(数据接见层)之间。

它的存在让范畴层以为不到数据接见层的存在,它供应一个类似鸠合的接口供应给范畴层举行范畴对象的接见。Repository 是仓库治理员,范畴层须要什么东西只需通知仓库治理员,由仓库治理员把东西拿给它,并不须要晓得东西现实放在哪。其中心照样“解耦”,所以我们应当明白范畴层只应当应用Repository猎取对象。

接下来,看看DAO与Repository什么区别。

我的明白是如许,你可以将Repository看成 DAO 来对待,然则请注意一点,在设想Repository时,我们应当采纳面向鸠合的体式格局,而不是面向数据接见的体式格局。这有助于你将自身的范畴看成模子来对待,而不是 CRUD 操纵;Repository是面向范畴的,Repository定义的目标不是DB驱动的,Repository治理的数据的最小粒度是聚合根,这两点和DAO有很大差别。

一般我们发起把Repository定义为一个鸠合而且只供应类似鸠合的接口,比方Add,Remove,Get这类操纵。一言以蔽之,我们要用鸠合的头脑来操纵聚合根,而不是传统的面向DB的CRUD要领。

厥后看看为何须要Repository,我明白照样“解耦”。当我们把Repository设想成一个资源库,也不关心背地的耐久化,这些也不是DDD该思索的东西,我们可以用mysql来完成,也可以用mongo,以至redis。尤其是当我们在替换底层存储时刻,范畴层以及相干的效劳并没有任何影响。

以下是代码示例:

package zwb.ddd.repository.sample.domain;
 
import zwb.ddd.repository.sample.domain.model.BaseAggregateRoot;
 
import java.util.List;
 
/**
 * BaseAggregateRoot范畴模子的基类,BaseSpecification实用于较为庞杂的查询场景。
 * @author wenbo.zhang
 * @date 2019-11-20
 */
public interface IRepository<T extends BaseAggregateRoot, Q extends BaseSpecification> {
 
    T ofId(String id);
 
    void add(T t);
 
    void remove(String id);
 
    List<T> querySpecification(Q q);
}

范畴驱动设想(DDD)实践之路(一) IT教程 第11张
范畴驱动设想(DDD)实践之路(一) IT教程 第11张

完成类:

package zwb.ddd.repository.sample.infrastructure;
 
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import zwb.ddd.repository.sample.domain.IRepository;
import zwb.ddd.repository.sample.domain.BaseSpecification;
import zwb.ddd.repository.sample.domain.model.BaseAggregateRoot;
import zwb.ddd.repository.sample.domain.model.Customer;
import zwb.ddd.repository.sample.domain.model.CustomerSpecification;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * @author wenbo.zhang
 * @date 2019-11-20
 */
@Component
public class CustomerRepository implements IRepository {
    /**
     * Repository其详细完成上层是无感知的,假如今后我们要切换为redis、mysql只须要修正这一层即可。
     */
    Map<String, Customer> customerMap = new ConcurrentHashMap<>();
 
    @Override
    public Customer ofId(String id) {
        return customerMap.get(id);
    }
 
    @Override
    public void add(BaseAggregateRoot aggregateRoot) {
        if (!(aggregateRoot instanceof Customer)) {
            return;
        }
        Customer customer = (Customer) aggregateRoot;
        customerMap.put(customer.getId(), customer);
    }
 
    @Override
    public void remove(String id) {
        customerMap.remove(id);
    }
 
    /**
     * 我们在Specification内里定义越发庞杂的查询前提
     *
     * @param specification 此处举例:基于id批量查询
     * @return
     */
    @Override
    public List<Customer> querySpecification(BaseSpecification specification) {
 
        List<Customer> customers = new ArrayList<>();
        if (!(specification instanceof CustomerSpecification)) {
            return customers;
        }
        if (CollectionUtils.isEmpty(specification.getIds())) {
            return customers;
        }
        specification.getIds().forEach(id -> {
            if (ofId(id) != null) {
                customers.add(ofId(id));
            }
        });
        return customers;
    }
}

范畴驱动设想(DDD)实践之路(一) IT教程 第11张
范畴驱动设想(DDD)实践之路(一) IT教程 第11张

在一样平常项目中我们应用mybatis,所以在Repository中会应用mybatis的DAO来举行操纵,下图是一个触及到订购的庞杂场景。

范畴驱动设想(DDD)实践之路(一) IT教程 第15张

 

五、实践:某加盟营业的计谋DDD重构

我们举一个加盟营业来形貌一下界线上下文的分别,以下图营业流程应当比较清晰,然则触及一些术语,因而先把重要的术语定义清晰、下降人人的认知差别。

范畴驱动设想(DDD)实践之路(一) IT教程 第16张

通用术语:

  • 进件:金融范畴术语,进件是指把材料准备好后提交给贷款公司或银行的体系内里,叫做进件,进件后银行或贷款公司就会入手下手考核这个贷款了。
  • 特约商户:金融术语,指银行、其他金融机构和财务公司刊行的信用卡作为一种付出手腕在流畅中被接收并愿意为其供应效劳的种种单元。简而言之,指与银行签订受理卡营业协定并赞同用银行卡举行商务结算的商户。

范畴驱动设想(DDD)实践之路(一) IT教程 第17张

 

上图的1.0版本,银行卡、进件、结算划定规矩都逾越了问题域,因而我们对其笼统“付出”“特约商户”上下文,以下图。

这里有人会有疑问,“特约商户”“商家”什么关联,是不是应当把“特约商户”归属为“商家域”,这只是字面意义的类似,“特约商户”是进件审批今后构成的付出相干的营业。固然“商家域”会应用到“特约商户”的才能。

因为进件逻辑庞杂因而我们以进件为中间来画出了如许的上下文。另一方面从状况流转来讲,“银行进件”是一个重要节点,代表平台、商家的一些权益行将见效,因而以此为中心也是有必要的。

范畴驱动设想(DDD)实践之路(一) IT教程 第18张

跟着商号外卖团购营业的生长,我们须要一个范畴才能更雄厚的履约装置域,可以举行社区配送、售后维修等。不可避免地将与定单、fa票、库存、售后等营业都有关联,因而以定单为中间构建了下面的上下文。

范畴驱动设想(DDD)实践之路(一) IT教程 第19张

六、结语

斟酌到篇幅以及内容繁多,范畴层相干的内容会在背面的文章中继承解说。

本文重要报告了计谋层面的DDD准绳,相对来讲较为笼统,但这是最磨练内功、最不可无视的环节。

再次强调一点,实践DDD绝不是参照一套网上的代码构造,依葫芦画瓢去重写自身的体系,这肯定是失利的。发起人人依据本文所报告的准绳、要领去思索自身的体系,当你意会其精华今后肯定可以“笑傲代码”,控制处理软件中心庞杂性的内功心法。

范畴驱动设想(DDD)实践之路(一) IT教程 第20张

 

redis系列-14点的灵异事件

参与评论