第十周学习总结

用户头像
高兵
关注
发布于: 2020 年 11 月 29 日

中台架构



中台可定义为:中台是一套结合互联网技术和行业特性,将企业核心能力以共享服务中心进行沉淀,形成“大中台、小前台“的组织和业务机制,供企业快速低成本的进行业务创新的企业架构。

中台的目的是“提供企业快速低成本创新的能力”,核心是“构建企业共享服务中心”,过程是构建 “大中台、小前台“组织和业务机制。其中,前台作为一线业务,更敏捷更快速适应市场,中台将整个企业的数字运营能力、产品技术能力,对各业务前台形成强力支撑。



1. 共享中心以共享业务+数据能力为主,比如领域服务层+API接口

2. 共享中心的目的是沉淀传统行业业务和数据能力,并开放出去

3. 共享中心是中台的重要部分,目的是实现前端应用和后台的彻底解耦



没有中台前,企业的痛点体现在:

复杂:系统庞大、逻辑复杂 (学习理解成本高,每人了解系统全貌,最懂的是程序员,需要翻代码才能知道具体逻辑)

重复:系统差异性大、标准不一 (同样的需求在不同系统重复造轮子,对于一个通用功能,没人说清楚是否有,或知道但现有的够不够支持)

沟通成本高:团队多,跨部门的沟通多(无用的拉通对齐会太多,沟通需求和信息获取成本极高)



中台就是为了让企业进行核心能力的沉淀,更给予我们快速创新的机会,具体包括:

1、中台赋予业务快速创新和试错能力

企业可以聚焦核心共享服务的建设,提高服务的重用。

2、打造数字化运营能力

中台有助于业务通过共享核心能力的沉淀,进行数字化运营。通过对中心核心数据的分析,更加精确地对业务进行调整和优化,全方位动态调整资源利用。

3、改变组织阵型带来组织效能提升

中台的变化也是组织阵型的变化。一方面,对于公司,中台侧重的是跨部门跨团队的深入合作。另一方面,对于个人,中台推荐的是类微服务的小而精团队,员工从事多种岗位,对全局和整体有更深入的锻炼。



中心化类似烟囱式架构,一个中心解决整个技术堆栈。平台的目标为高内聚、低耦合、职责边界清晰,是单一团队、部门、系统的效率提升。

中台的目标是提升效能、数据化运营、更好支持业务发展和创新,是多领域、多BU、多系统的负责协同。

中台是平台的自然演进:这种演进带来“去中心化“的组织模式,突出对能力复用、协调控制的能力,以及业务创新的差异化构建能力。



中台是真正为前台而生的平台(可以是技术平台,业务能力甚至是组织机构),它存在的唯一目的就是更好的服务前台规模化创新,进而更好的响应服务引领用户,使企业真正做到自身能力与用户需求的持续对接。



有了“中台”这⼀新的Pace-Layered断层,我们即可以将早已臃肿不堪的前台系统中的稳定通用业务能力“沉降”到中台层,为前台减肥,恢复前台的响应⼒;又可以将后台系统中需要频繁变化或是需要被前台直接使用的业务能力“提取”到中台层,赋予这些业务能力更强的灵活度和更低的变更成本,从而为前台提供更强大的“能力炮火”⽀援。

电商经过十几年的发展,组织已经庞大而复杂,业务不断细化拆分,也导致野蛮发展的系统越来越不可维护,开发和改造效率极低,也有很多新业务不得不重复造轮子,所以中台的目标是为了解决效率问题,同时降低创新成本。

所谓的业务中台就是:通过制定标准和机制,把不确定的业务规则和流程通过工业化和市场化的手段确定下来,以减少人与人之间的沟通成本,同时还能最大程度地提升协作效率。

中台的目标:减少沟通成本,提升协作效率。中台的实现手段:制定标准和规范。原则:集中管控,分布式执行。那么应该如何建设中台,我们先看一下中台的定位。



业务中台需要收敛一些基础的业务服务,如会员、商品、交易、营销和结算等。这些基础的服务会被整个电商业务使用,所以统一管理是很有必要的。

那么业务中台还需要什么?因为中台的目标是要向上层业务提供这些基础的服务,那自然必须能够清楚地描述自己到底有哪些服务、数据和功能,我们可以把它统称为能力。所以还需要能够定义能力(标准和规范)、能力的发现、能力的注册、能力的列表以及能力的评价和更新机制等。

业务中台也不是什么都做,除了有基本的基础服务和服务能力外,还要定义中台的边界。下图描述了业务中台一些基本的工作范围,它需要能够对接能力,同时又服务好能力使用方,而自己并不负责实现具体的业务。



企业的后台系统,在创建之初的目标,并不是主要服务于前台系统创新,而更多的是为了实现后端资源的电子化管理,解决企业管理的效率问题。这类系统要不就是当年花大价钱外购,需要每年支付大量的服务费,并且版本老旧,定制化困难;要不就是花大价钱自建,年久失修,一身的补丁,同样变更困难,也是企业所谓的“遗留系统”的重灾区。



前台:由各类前台系统组成的前端平台。每个前台系统就是一个用户触点,即企业的最终用户直接使用或交互的系统,是企业与最终用户的交点。例如用户直接使用的网站,手机App,微信公众号等都属于前台范畴。

后台:由后台系统组成的后端平台。每个后台系统一般管理了企业的一类核心资源(数据+计算),例如财务系统,产品系统,客户管理系统,仓库物流管理系统等,这类系统构成了企业的后台。基础设施和计算平台作为企业的核心计算资源,也属于后台的一部分。



有了“中台”这⼀新的Pace-Layered断层,我们即可以将早已臃肿不堪的前台系统中的稳定通用业务能力“沉降”到中台层,为前台减肥,恢复前台的响应⼒;又可以将后台系统中需要频繁变化或是需要被前台直接使用的业务能力“提取”到中台层,赋予这些业务能力更强的灵活度和更低的变更成本,从而为前台提供更强大的“能力炮火”⽀援。



所以,企业在平台化的过程中,需要建设自己的中台层(同时包括技术中台,业务中台和组织中台)。

开发效能是构建前中台应用过程中必不可少的重要一环。这方面的实践和能力通过沉淀,结合快速开发框架平台,例如微服务开发平台,就形成了企业的研发中台



业务中台提供重用服务,例如用户中心,订单中心之类的开箱即用可重用能力,为战场提供了强大的后台炮火支援能力,随叫随到,威力强大;

数据中台提供了数据分析能力,帮助我们从数据中学习改进,调整方向,为战场提供了强大及时的雷达监测能力,帮助我们掌控战场;

移动及算法中台提供了战场一线火力支援能力,帮助我们提供更加个性化的服务,增强用户体验,为战场提供了陆军支援能力,随机应变,所向披靡;

技术中台提供了自建系统部分的技术支撑能力,帮助我们解决了基础设施,分布式数据库等底层技术问题,为前台特种兵提供了精良的武器装备;

研发中台提供了自建系统部分的管理和技术实践支撑能力,帮助我们快速搭建项目,管理进度,测试,持续集成,持续交付,是前台特种兵的训练基地及快速送达战场的机动运输部队;

组织中台为我们的项目提供投资管理,风险管理,资源调度等,是战场的指挥部,战争的大脑,指挥前线,调度后方。



前台想不想用,爱不爱用,好不好用,帮了前台多大的忙,从中台获得了多大的好处,愿意掏出多少利润来帮助建设中台,这才是甄别中台建设对错好坏的唯一标准。 对于中台来讲,前台就是用户,以用户为中心,在中台同样适用。



业务中台使得任何一条业务线都具备整个公司的核心能力。

所有业务共享单元的能力共同支撑了阿里巴巴整个业务集团的发展。业务中台能够赋予整个企业快速进行业务创新的能力。



业务中台是水平的,在这个平台上需要建立自己核心的业务中心,比如,在维护、建设前端业务线的时候不能有自己的会员中心,必须把所有的会员纳入到会员中心里,不仅可以快速创新和试触,也可以防止业务做大后会员之间的打通。所以,使用业务中台的一个好处是其数据中心本身就是打通的。



业务中台建设的方式不是一蹴而就的,它是随着企业本身信息化建设持续进展和业务不断创新最终沉淀下来的。有三个最基本的核心要求:开放,所有业务中台实现对内对外的开放,能够极大促进企业业务能力的发展;滋养,传统企业也需要业务创新,只有新的业务不断冒出来试触才能让业务中台做的更好;数据,对数据的应用。在此之上,有两个基本的能力保障:服务,能力是以服务来对外提供的,所以必须确保服务是足够核心的,在定义业务中台的时候会面临个性化诉求,个性化需求是会以特定方式做二次性开发的;稳定,专业、专注带来稳定。



业务场景-客户管理系统-解决方案

客户管理系统一般存在三个问题:客户数据不完备,其原因是大量线下、渠道用户数据未纳入,现有用户中心管理不到30%用户,用户基本信息字段缺失,姓名、电话、住址等,用户订单等业务数据缺失;客户数据不一致,相同用户,姓名/称呼不一致,基本信息登记混乱,订单等业务数据各系统不一致;客户数据缺乏共享,各渠道单独注册用户,数据大量重复、用户体验差,售后、客服的用户信息来源多样化,效率低、用户体验差,不同系统用户数据割裂,无法360度画像,用户分析和营销缺乏精准度。



所以需要:统一用户管理,为线上线下渠道提供统一用户管理,包括统一注册通道、账户管理、基本信息用户清洗去重机制等;统一用户登录,为多渠道提供统一单点登录管理,提升客户体验,支持账号密码、手机、微信、二维码等多种认证和安全管控手段,支持页面H5代理、api oauth等方式接入;数据共享,各个业务系统客户数据打通共享,对应的业务部门协同服务,让客户得到统一的消费体验。



中台架构目标

业务中台采用领域驱动设计(DDD),在其上构建业务能力SAAS,持续不断进行迭代演进。

平台化定位,进行了业务隔离设计,方便一套系统支撑不同玩法的业务类型和便于定制化扩展。

前后端分离,通过服务接入层进行路由适配转发。

天然的分库分表,消息解耦和分布式缓存设计,支持弹性扩容,以支持大数据高并发场景。

减少沟通成本,提升协作效率,制定标准和规范,集中管控,分布式执行。



领域驱动设计

领域驱动设计(简称 ddd)概念来源于2004年著名建模专家eric evans发表的他最具影响力的书籍:《domain-driven design –tackling complexity in the heart of software》(中文译名:领域驱动设计—软件核心复杂性应对之道)一书。,书中提出了“领域驱动设计(简称 ddd)”的概念。

         领域驱动设计一般分为两个阶段:

        1.   以一种领域专家、设计人员、开发人员都能理解的“通用语言”作为相互交流的工具,在不断交流的过程中发现和挖出一些主要的领域概念,然后将这些概念设计成一个领域模型;

         2.   由领域模型驱动软件设计,用代码来表现该领域模型。领域需求的最初细节,在功能层面通过领域专家的讨论得出。

 

       领域驱动设计告诉我们,在通过软件实现一个业务系统时,建立一个领域模型是非常重要和必要的,因为领域模型具有以下特点:

     1.   领域模型是对具有某个边界的领域的一个抽象,反映了领域内用户业务需求的本质;领域模型是有边界的,只反应了我们在领域内所关注的部分;

     2.   领域模型只反映业务,和任何技术实现无关;领域模型不仅能反映领域中的一些实体概念,如货物,书本,应聘记录,地址,等;还能反映领域中的一些过程概念,如资金转账,等;

     3.   领域模型确保了我们的软件的业务逻辑都在一个模型中,都在一个地方;这样对提高软件的可维护性,业务可理解性以及可重用性方面都有很好的帮助;

     4.   领域模型能够帮助开发人员相对平滑地将领域知识转化为软件构造;

     5.  领域模型贯穿软件分析、设计,以及开发的整个过程;领域专家、设计人员、开发人员通过领域模型进行交流,彼此共享知识与信息;因为大家面向的都是同一个模型,所以可以防止需求走样,可以让软件设计开发人员做出来的软件真正满足需求;

     6.  要建立正确的领域模型并不简单,需要领域专家、设计、开发人员积极沟通共同努力,然后才能使大家对领域的认识不断深入,从而不断细化和完善领域模型;

     7.  为了让领域模型看的见,我们需要用一些方法来表示它;图是表达领域模型最常用的方式,但不是唯一的表达方式,代码或文字描述也能表达领域模型;

     8.  领域模型是整个软件的核心,是软件中最有价值和最具竞争力的部分;设计足够精良且符合业务需求的领域模型能够更快速的响应需求变化;

 

领域驱动设计中的一些基本概念:

1.实体(entity):

        根据eric evans的定义,”一个由它的标识定义的对象叫做实体”。通常实体具备唯一id,能够被持久化,具有业务逻辑,对应现实世界业务对象。

         实体一般和主要的业务/领域对象有一个直接的关系。一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。

 

需要注意的是:

         一些开发人员将实体当成了orm意义上的实体,而不是业务所有和业务定义的领域对象。在一些实现中采用了transaction script风格的架构,使用贫血的领域模型。这种认识上的混乱,在领域驱动架构中,不愿意在领域对象中加入业务逻辑而导致贫血的领域模型,同时还可能使混乱的服务对象激增。

 

2.值对象(value object)

        值对象的定义是:描述事物的对象;更准确的说,一个没有概念上标识符描述一个领域方面的对象。这些对象是用来表示临时的事物,或者可以认为值对象是实体的属性,这些属性没有特性标识但同时表达了领域中某类含义的概念。

 

        通常值对象不具有唯一id,由对象的属性描述,可以用来传递参数或对实体进行补充描述。

        作为实体属性的描述时,值对象也会被存储。在uml的类图上显现为一对多或一对一的关系。在orm映射关系上需要采用较复杂的一对多或一对一关系映射。

 

        关于实体与值对象的一个例子:比如员工信息的属性,如住址,电话号码都可以改变;然而,同一个员工的实体的标识将保持不变。因此,一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。

 

实体与值对象的区别

         实体具有唯一标识,而值对象没有唯一标识,这是实体和值对象间的最大不同。

        实体就是领域中需要唯一标识的领域概念。有两个实体,如果唯一标识不一样,那么即便实体的其他所有属性都一样,也认为是两个不同的实体;一个实体的基本概念是一个持续抽象的生命,可以变化不同的状态和情形,但总是有相同的标识。

        不应该给实体定义太多的属性或行为,而应该寻找关联,发现其他一些实体或值对象,将属性或行为转移到其他关联的实体或值对象上。

 

        如果两个对象的所有的属性的值都相同,我们会认为它们是同一个对象的话,那么我们就可以把这种对象设计为值对象。值对象在判断是否是同一个对象时是通过它们的所有属性是否相同,如果相同则认为是同一个值对象;而实体是否为同一个实体的区分,只是看实体的唯一标识是否相同,而不管实体的属性是否相同。

        值对象另外一个明显的特征是不可变,即所有属性都是只读的。因为属性是只读的,所以可以被安全的共享;当共享值对象时,一般有复制和共享两种做法,具体采用哪种做法还要根据实际情况而定。

        箴言:如果值对象时可共享的,它们应该是不可变的。(值对象应该保持尽量的简单)

         值对象的设计应尽量简单,不要让它引用很多其他的对象,因为本质上讲值对象只是代表一个值。

 

3.聚合及聚合根(aggregate、aggregate root):

        聚合是用来定义领域对象所有权和边界的领域模式。聚合的作用是帮助简化模型对象间的关系。聚合,它通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的单元。

        划分aggregation是对领域模型的进一步深化,aggregation能阐释领域模型内部对象之间的深层关联.对aggregation的划分会直接映射到程序结构上.比如:ddd推荐按aggregation设计model的子包.每个aggregation配备一个repository.aggregation内部的非root对象是通过导航获得的.        

        一个聚合是一组相关的被视为整体的对象。每个聚合都有一个根对象(聚合根实体),从外部访问只能通过这个对象。根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体。

         只有聚合根才能使用仓储库直接查询,其它的只能通过相关的聚合访问。如果根实体被删除,聚合内部的其它对象也将被删除。

         通常,我们把聚合组织到一个文件夹或一个包中。每一个聚集对应一个包,并且每个聚集成员包括实体、值对象,domain事件,仓储接口和其它工厂对象。

 

聚合有以下一些特点:

  1. 每个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或值对象,根是聚合内的某个实体;

  2. 聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象,也就是说聚合根是外部可以保持对它的引用的唯一元素;

  3. 聚合内除根以外的其他实体的唯一标识都是本地标识,也就是只要在聚合内部保持唯一即可,因为它们总是从属于这个聚合的;

  4. 聚合根负责与外部其他对象打交道并维护自己内部的业务规则

  5. 基于聚合的以上概念,我们可以推论出从数据库查询时的单元也是以聚合为一个单元,也就是说我们不能直接查询聚合内部的某个非根的对象;

  6. 聚合内部的对象可以保持对其他聚合根的引用;

  7. 删除一个聚合根时必须同时删除该聚合内的所有相关对象,因为他们都同属于一个聚合,是一个完整的概念。

 

如何识别聚合?

        聚合中的对象关系是内聚的,即这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。

        当我们在修改一个聚合时,我们必须在事务级别确保整个聚合内的所有对象满足这个固定规则。

        作为一条建议,聚合尽量不要太大,否则即便能够做到在事务级别保持聚合的业务规则完整性,也可能会带来一定的性能问题。

        有分析报告显示,通常在大部分领域模型中,有70%的聚合通常只有一个实体,即聚合根,该实体内部没有包含其他实体,只包含一些值对象;另外30%的聚合中,基本上也只包含两到三个实体。这意味着大部分的聚合都只是一个实体,该实体同时也是聚合根。

 

如何识别聚合根?

  如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互。

       并不是所有的实体都是聚集根,但只有实体才能成为聚集根。

 

4.工厂(factories):

       工厂用来封装创建一个复杂对象尤其是聚合时所需的知识,作用是将创建对象的细节隐藏起来。客户传递给工厂一些简单的参数,然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。当创建 实体和值对象复杂时建议使用工厂模式。

       不意味着我们一定要使用工厂模式。如果创建对象很简单,使用构造器或者控制反转/依赖注入容器足够创建对象的依赖。此时,我们就不需要通用工厂模式来创建实体或值对象。

 

       良好工厂的要求:

       每个创建方法都是原子的。一个工厂应该只能生产透明状态的对象。对于实体,意味着创建整个聚合时满足所有的不变量。

      一个单独的工厂通常生产整个聚合,传出一个根实体的引用,确保聚合的不变量都有。如果对象的内部聚合需要工厂,通常工厂方法的逻辑放在在聚合根上。这样对外部隐藏了聚合内聚的实现,同时赋予了根确保聚合完整的职责。如果聚合根不是子实体工厂的合适的家,那么继续创建一个单独的工厂。

 

5.仓储(repositories):

        仓储是用来管理实体的集合。

         仓储里面存放的对象一定是聚合,原因是domain是以聚合的概念来划分边界的;聚合作为一个整体概念,要么一起被取出来,要么一起被删除。外部访问不会单独对某个聚合内的子对象进行单独操作。因此,我们只对聚合设计仓储。

         仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分,我们在领域模型中定义仓储的接口,而在基础设施层实现具体的仓储。也符合按照接口分离模式在领域层定义仓储库接口的原则。

        注意:repositories本身是一种领域组件,但repositories的实现却不是领域层中的。

 

respositories和dao:

         dao和repository在领域驱动设计中都很重要。dao是面向数据访问的,是关系型数据库和应用之间的契约。

        repository:位于领域层,面向aggregation root。repository是一个独立的抽象,使用领域的通用语言,它与dao进行交互,并使用领域理解的语言提供对领域模型的数据访问服务的“业务接口”。

  dao方法是细粒度的,更接近数据库,而repository方法的粒度粗一些,而且更接近领域。领域对象应该只依赖于repository接口。客户端应该始终调用领域对象,领域对象再调用dao将数据持久化到数据 存储中。

  处理领域对象之间的依赖关系(比如实体及其repository之间的依赖关系)是开发人员经常遇到的典型问题。解决这个问题通 常的设计方案是让服务类或外观类直接调用repository,在调用repository的时候返回实体对象给客户端。

 

6.服务(services):

         服务这个词在服务模式中是这么定义的:服务提供的操作是它提供给使用它的客户端,并突出领域对象的关系。

         所有的service只负责协调并委派业务逻辑给领域对象进行处理,其本身并真正实现业务逻辑,绝大部分的业务逻辑都由领域对象承载和实现了。

         service可与多种组件进行交互,这些组件包括:其他的service、领域对象和repository 或 dao。

         通常,应用中一般包括:domain模型服务和应用层服务:

        *  domain services encapsulate domain concepts that just are not naturally modeled as things.

        *  application services constitute the application, or service, layer.

 

        当一个领域操作被视为一个重要的领域概念,一般就应该作为领域服务。 服务应该是无状态的。

        设计实现领域服务来协调业务逻辑,只在领域服务中实现领域逻辑的调用。

        领域服务逻辑须以非常干净简洁的代码实现。因此,我们必须实现对领域低层组件的调用。通常应用的调用,例如仓储库的调用,创建事务等,不应该在这里实现。这些操作应该在应用层实现。

          通常服务对象名称中都应包含一个动词。 service接口的传入传出参数也都应该是dto,可能包含的工作有领域对象和dto的互转换以及事务。

 

      服务的3个特征:

  a. 服务执行的操作涉及一个领域概念,这个领域概念通常不属于一个实体或者值对象

  b. 被执行的操作涉及到领域中其它的对象

  c. 操作时无状态的

推荐:最好显式声明服务,因为它创建了领域中一个清晰的特性,封装了一个概念领域层服务和基础设施层服务:均建立在领域实体和值对象的上层,以便直接为这些相关的对象提供所需的服务;

 

领域服务与domain对象的区别

        一般的领域对象都是有状态和行为的,而领域服务没有状态只有行为。需要强调的是领域服务是无状态的,它存在的意义就是协调领域对象共同完成某个操作,所有的状态还是都保存在相应的领域对象中。

        通常,对开发人员来说创建不应该存在的服务相当容易;要么在服务中包含了本应存在于领域对象中的领域逻辑,要么扮演了缺失的领域对象角色,而这些领域对象并没有作为模型的一部分去创建。

 

7.domain事件

        domain event模式最初由udi dahan提出,发表在自己的博客上:http://www.udidahan.com/2009/06/14/domain-events-salvation/

        企业级应用程序事件大致可以分为三类:系统事件、应用事件和领域事件。领域事件的触发点在领域模型(domain model)中。它的作用是将领域对象从对repository或service的依赖中解脱出来,避免让领域对象对这些设施产生直接依赖。它的做法就是当领域对象的业务方法需要依赖到这些对象时就发出一个事件,这个事件会被相应的对象监听到并做出处理。

        通过使用领域事件,我们可以实现领域模型对象状态的异步更新、外部系统接口的委托调用,以及通过事件派发机制实现系统集成。另外,领域事件本身具有自描述性。它不仅能够表述系统发生了什么事情,而且还能够描述发生事件的动机。

         domain事件也用表进行存储。

 

8.DTO

       dto- datatransfer object(数据传输对象):dto在设计之初的主要考量是以粗粒度的数据结构减少网络通信并简化调用接口。

 

领域驱动架构与n层架构设计

领域驱动架构

        eric  evans的“领域驱动设计- 应对软件的复杂性“一书中描述和解释了建议的n层架构高层次的图:

 



user interface:

        该层包含与其他系统/客户进行交互的接口与通信设施,在多数应用里,该层可能提供包括web services、rmi或rest等在内的一种或多种通信接口。该层主要由facade、dto和assembler三类组件构成,三类组件均是典型的j2ee模式。

        dto的作用最初主要是以粗粒度的数据结构减少网络通信并简化调用接口。在领域驱动设计中,采用dto模型,可以起到隐藏领域细节,帮助实现独立封闭的领域模型的作用。

        dto与领域对象之间的相互转换工作多由assembler承担,也有一些系统使用反射机制自动实现dto与领域对象之间的相互转换,如apache common beanutils。

        facade的用意在于为远程客户端提供粗粒度的调用接口。facade本身不处理任何的业务逻辑,它的主要工作就是将一个用户请求委派给一个或多个service进行处理,同时借助assembler将service传入或传出的领域对象转化为dto进行传输。

 

application:

         application层中主要组件就是service。这里需要注意的是,service的组织粒度和接口设计依据与传统transaction script风格的service是一致的,但是两者的实现却有质的区别。

  transaction script(事务脚本)的核心是过程,通过过程的调用来组织业务逻辑,业务逻辑在服务(service)层进行处理。大部分业务应用都可以被看成一系列事务。

         transaction script的特点是简单容易理解,面向过程设计。  如果应用相对简单,在应用的生命周期里不会有基础设施技术的改变,尤其是业务逻辑很少会变动,采用transaction script风格简单自然,性能良好,容易理解。

        transaction script的缺点在于,对于复杂的业务逻辑难以保持良好的设计,事务之间的冗余代码不断增多。应用架构容易出现“胖服务层”和“贫血的领域模型”。同时,service层积聚越来越多的业务逻辑,导致可维护性和扩展性变差

  领域模型属于面向对象设计,领域模型具备自己的属性行为和状态,领域对象元素之间通过聚合配合解决实际业务应用。可复用,可维护,易扩展,可以采用合适的设计模型进行详细设计。缺点是相对复杂,要求设计人员有良好的抽象能力。

        transactionscript风格业务逻辑主要在service中实现,而在领域驱动设计的架构里,service只负责协调并委派业务逻辑给领域对象进行处理。因此,我们可以考察这一点来识别系统是transaction script架构还是domain model架构。在实践中,设计良好的领域设计架构在开发过程中也容易向transaction script架构演变。

 

domain:

        domain层是整个系统的核心层,该层维护一个使用面向对象技术实现的领域模型,几乎全部的业务逻辑会在该层实现。domain层包含entity(实体)、valueobject(值对象)、domain event(领域事件)和repository(仓储)等多种重要的领域组件。

 

infrastructure:

        infrastructure(基础设施层)为interfaces、application和domain三层提供支撑。所有与具体平台、框架相关的实现会在infrastructure中提供,避免三层特别是domain层掺杂进这些实现,从而“污染”领域模型。infrastructure中最常见的一类设施是对象持久化的具体实现。

 

n层架构设计

        层(layers)被视为构成应用或服务的水平堆叠的一组逻辑上的组件。它们帮助区分完成不同任务的组件,提供一个最大化复用和可维护性的设计。简言之,是关于在架构方面应用关注点分离的原则。         在传统的多层架构中,每个解决方案的组件必须分隔到不同的层。每层的组件必须内聚而且有大约相同的抽象级别。每个一级层应该和其他的一级层松耦合。

        从最底层的抽象级别看,例如第1层。这是系统的基础层。这些抽象的步骤是一步一步的最后到最顶层。

        多层应用的关键在于对依赖的管理。传统的多层架构,层内的组件只能和同级或者低级层的组件交互。这有利于减少不同层内组件的依赖。通常有两种多层架构的设计方法:严格和灵活的。“严格的层设计”限定层内的组件只能和同一层、或者下一层的组件通信。即第n层只能和第n-1层交互,n-1层只能和n-2层交互,等等。

     “灵活的层设计”允许层内的组件和任何低级别层交互。这种设计中,第n层可以和n-1,n-2层交互。

        这种设计由于不需要对其他层进行重复的调用,从而可以提高性能。然而,这种设计不提供层之间的同层隔离级别,使得它难以在不影响多个高级层的时候替换一个低级的层。

        由于层之间是通过定义明确的接口进行交互这一事实,很容易为各层添加替代的实现(例如 mock  and stubs)。

         因为高层的组件只能和底层的交互,在单独的组件上进行测试是很容易的。

使用层的好处  -  功能容易确定位置,解决方案也就容易维护。层内高内聚,层间松耦合使得维护/组合层更容易。 -  其他的解决方案可以重用由不同层暴露的功能。 -  当项目按逻辑分层时,分布式的部署更容易实现。 -  把层分布到不同的物理层可以提高可伸缩性;然后这一步应该进行仔细的评估,因为可能对性能带来负面影响。

 

面向领域架构的分层:

        在面向领域架构中,关键是要清楚界定和分离领域模型层和其余的层。

 

领域驱动与项目开发

         一般适合结合使用scrum(适用于项目管理)和xp(适用于软件开发目标)方法对处理ddd实施项目。敏捷方法注重于交付商业价值,而ddd侧重于结合软件系统和业务模型。此 外,就ddd迭代的特性来说,scrum或dsdm这样的敏捷方法对项目管理来说也是更好的框架。

       ddd迭代周期的项目管理模型如图所示。





本图根据《domain driven design and development in practice》一文中插图进行了部分修改。

  领域建模结束时可以开始领域驱动设计。关于如何开始实现领域对象模型,ramnivas laddad推荐如下的步骤。他强调要更侧重于领域模型中的领域对象,而不是服务。

       *   从领域实体和领域逻辑开始。

       *   不要一开始就从服务层开始,只添加那些逻辑不属于任何领域实体或值对象的服务。

       *   利用通用语言、契约式设计(dbc)、自动化测试、  ci和重构,使实现尽可能地与领域模型紧密结合。

 

设计领域模型的一般步骤:

       1.   根据需求建立一个初步的领域模型,识别出一些明显的领域概念以及它们的关联,关联可以暂时没有方向但需要有(1:1,1:n,m:n)这些关系;可以用文字精确的没有歧义的描述出每个领域概念的涵义以及包含的主要信息;

       2.   分析主要的软件应用程序功能,识别出主要的应用层的类;这样有助于及早发现哪些是应用层的职责,哪些是领域层的职责;

       3.   进一步分析领域模型,识别出哪些是实体,哪些是值对象,哪些是领域服务;

       4.   分析关联,通过对业务的更深入分析以及各种软件设计原则及性能方面的权衡,明确关联的方向或者去掉一些不需要的关联;

       5.   找出聚合边界及聚合根,这是一件很有难度的事情;因为你在分析的过程中往往会碰到很多模棱两可的难以清晰判断的选择问题,所以,需要我们平时一些分析经验的积累才能找出正确的聚合根;

       6.   为聚合根配备仓储,一般情况下是为一个聚合分配一个仓储,此时只要设计好仓储的接口即可;

       7.   走查场景,确定我们设计的领域模型能够有效地解决业务需求;

       8.   考虑如何创建领域实体或值对象,是通过工厂还是直接通过构造函数;

       9.   停下来重构模型。寻找模型中觉得有些疑问或者是蹩脚的地方,比如思考一些对象应该通过关联导航得到还是应该从仓储获取?聚合设计的是否正确?考虑模型的性能怎样,等等;

         领域建模是一个不断重构,持续完善模型的过程,大家会在讨论中将变化的部分反映到模型中,从而是模型不断细化并朝正确的方向走。

 

  从设计和实现的角度来看,典型的ddd框架应该支持以下特征。

       *   应该是一个以pojo为基础的架构。

       *   应该支持使用ddd概念的业务领域模型的设计和实现。

       *   应该支持像依赖注入(di)和面向方向编程(aop)这些概念的开箱即用。

       *   与单元测试框架整合。

       *   与其它java/java ee框架进行良好的集成,比如jpa、hibernate、toplink等。

 

一些反模式:

       *   贫血的领域对象

       *   重复的dao

       *   肥服务层:服务类在这里最终会包含所有的业务逻辑。

       *   依恋情结(feature envy):函数对某个类的兴趣高过对自己所处类的兴趣。

 

组件设计原则

随着软件代码规模的不断扩大,管理软件的复杂性,使软件容易扩展,确保业务和研发效率的敏捷性越来越重要。项目架构层面上,开闭原则告诉我们要将系统划分为一系列组件,组件之间的依赖关系按照层次结构进行组织,从而使得系统容易扩展。

大型软件系统中,一般将系统分层,比如基础组件层、业务逻辑层,数据持久层、服务层。横向上,一般也会根据业务、功能进行组件化。在组件化过程中,哪些类应该组合成一个组件?组件之间如何解耦?如何实现“高内聚,低耦合”?这些问题都是开发人员要谨慎考虑的问题。

SOLID设计原则主要关注类的设计,解决如何将数据和函数设计成类,以及如何将这些类链接起来成为程序的问题。

Robert C. Martin《架构整洁之道》 中,他提出了一些用于组件设计的原则,一共包括六个原则。

组件聚合指导我们应该将哪些类组合成一个组件,要考虑三个原则:复用/发布等同原则共同闭包原则共同复用原则

组件耦合帮助我们确定组件之间的相互依赖关系,要考虑三个原则:无依赖环原则稳定依赖原则稳定抽象原则



组件聚合

组件聚合是应该把哪些类应该被组合成一个组件?

复用/发布等同原则(The Reuse/Release Equivalence Principle, REP)

软件复用的最小粒度等同于其发布的最小粒度。

REP告诉我们,代码复用在组件这一级,可复用的组件中必须包含可复用的类,这些可复用的类以组件的方式发布给用户使用。从另外一个角度去考虑一个组件的内容,一个组件中的类要么都可以重用,要么都不是可重用的。

REP要求我们保持组件的重用粒度和组件的发布粒度一致。例如,两个组件A、B,如果其他组件总是一起使用组件A、B,而且这两个组件总是一起发布,那么这两个组件应该合为一个组件。组件中的类与模块必须是彼此紧密相关的

共同闭包原则(The Common Closure Principle, CCP)

应该将那些会同时修改、相同目的修改的类放在同一个组件中。另一方面,应该将不会同时修改,不会为了相同的目的而修改的类放在不同的组件中。

CCP原则告诉我们,如果一个应用中的代码需要同时、为统一目的发生修改,尽量让这种修改都集中在一个组件中,而不是分散在多个组件中。如果将这些更改分散在多个组件中,将增加软件发布、验证和维护工作量。

共同闭包原则不重视代码的复用性,例如,如果A和B组件共同依赖于C组件,而且A组件的变化经常伴随着C变更,按照CCP原则的要求,C组件应该要放入A组件中,同样C组件应该放入B组件中,这将会导致代码复用性降低。



对于大部分应用程序来说,可维护性的重要性要远远高于可复用性

共同复用原则(The Common Reuse Principle, CRP)

不要强迫一个组件依赖他们不需要的东西。

CRP原则是面向对象设计原则中接口分离原则的普适版本。CRP要求经常共同复用的类和模块放在同一个组件中,而不是紧密相连的类不应该放在同一个组件中。

一个组件中的类应该一起被复用,相反,如果你只用一个组件中的一部分类,那么应该把不用的类从组件中移除出去。

从另一个方面讲,在一个组件中不应该包含太多不同类型的类,不要把一些完全不相干的类放在一个组件中,这样会导致组件的职责过重,从而增加修改和发布的频率。



哪个原则更重要?

你可能已经意识到,上述三个原则是相互竞争的。REP和CCP是粘合性原则,告诉我们哪些类要放在一起,这会让组件变得更大。CRP是排除性的原则,不需要的类要从组件中移除出去,这会使组件变小。软件架构师的重要任务就是在这三个原则之间做出均衡



三大原则张力图

只关注两个方面,而忽略另一个方面会引发一些问题:

  1. 只关注REP和CRP将会导致一个功能会拆分为更多组件,每次需求变更会导致太多的组件变更。

  2. 只关注REP和CCP将会导致每个组件规模很庞大,改动一个小点,就会导致组件更新版本,从而使得组件频繁发布。

  3. 只关注CCP和CRP将会导致不关注组件的复用性,使得组件复用困难。

如何进行权衡?

Bob大叔建议我们,要在上述三角区域中定位一个最适合目前研发团队状态的位置,同时不停调整。

在项目早期,CCP原则会比REP原则更重要,因为这一阶段开发速度要比复用性更重要,这个时候可以牺牲复用性,换取开发速度。

随着项目成熟,其他组件之间产生产生更多依赖,项目重心就逐渐会向三角区域的左侧滑动,更加重视复用性。



也就是说,要根据项目的开发时间成熟度不断变动、不断演化的,与项目本身的功能关系很小。

组件耦合

如何管理组件之间的依赖关系?

无依赖环原则 (Acyclic Dependencies Principle,ADP)

组件依赖关系图中不应该出现环

环形依赖关系使得一个组件修改之后的影响范围将变得非常大,环中任何一个组件发生变更,会对环上每一个组件产生影响,进而导致对环上组件依赖的其他组件产生影响,导致系统难以升级和维护。

如何打破依赖循环? 有两种机制可以消除环形依赖关系:1)使用依赖反转原则 2)创建一个新组件

下面举例说明,在这个例子中三个组件Interactors、Authorizer、Entities三个组件形成环形依赖关系。





循环依赖



1)使用依赖反转原则





使用依赖反转原则



步骤:

  1. 分析发现,Entities组件中User使用到Authorizer组件中的认证Permission功能

  2. 把User调用Authorizer组件中的认证Permission抽象成为一个接口,User依赖这个接口,而不是依赖于Authorizer

  3. Authorizer实现了Permission接口(是Permission的具体类),于是Authorizer依赖于Permission接口

  4. Authorizer依赖于Entities组件,实现了依赖反转,打破了依赖循环。

2)创建新组件



创建新组件

1、把Entities组件中User使用到Authorizer组件中的认证Permission功能单独拆分出来,形成新的组件Permissions。

2、Entities和Authorizer共同依赖于新的组件,打破了依赖循环。



稳定依赖原则(The Stable Dependencies Principle,SDP)

依赖关系必须指向更稳定的方向

如何衡量一个组件的稳定性?

不稳定性指标:I = (对别的组件依赖个数)/ (对别的组件依赖个数 + 别的组件对自己依赖个数)



每一个组件的I指标必须大于其依赖组件的I指标。

稳定抽象原则(The Stable Abstractions Principle,SAP)

一个组件的抽象化程度应该与其稳定性保持一致。

组件的抽象化程度 A = 组件中抽象类和接口的数量/组件中具体类的数量。

  1. 稳定的组件应该是抽象的,应该由接口和抽象类组成。

  2. 一个不稳定的组件应该包含具体的实现代码。

  3. 依赖关系应该指向更加抽象的方向。

总结

本文介绍了组件设计要考虑的原则,首先,要考虑应该把哪些类应该被组合成一个组件,组件太大和太小都会有不同的问题,组件聚合原则告诉我们设计组件时要考虑的原则,以及如何根据项目的开发时间和成熟度对这些原则进行权衡。组件耦合考虑的是如何管理组件之间的依赖关系,减小组件之间的耦合,组件依赖要考虑的问题。从组件聚合和组件耦合全面分析,可以设计出"高内聚、低耦合"的组件。

用户头像

高兵

关注

还未添加个人签名 2018.09.21 加入

还未添加个人简介

评论

发布
暂无评论
第十周学习总结