领域是与某个特定问题相关的知识和行为。比如支付平台就属于特定的领域,只要是这个领域,都会有账户、会记、收款、付款、风控等核心环节。所以,同一个领域的系统都具有相同的核心业务,他们要解决的问题的本质是一致的。一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。
在日常开发中,我们通常会将一个大型的软件系统拆分成若干个子系统。这种划分有可能是基于架构方面的考虑,也有可能是基于基础设施的。在DDD中,我们对系统的划分是基于领域(基于业务)的。比如上文提到支付平台是一个领域,而账户、会记、收款、付款等则为子领域。一个领域由众多子领域聚集而形成。
DDD中的设计主要指领域模型的设计。DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个平台的核心价值。每一个领域都有一个对应的领域模型,领域模型能够很好的解决负责的业务问题。所以领域模型的设计和架构设计同等重要。
DDD中,总是以领域为边界,分析领域中的核心问题。然后设计对应的领域模型,通过领域模型驱动代码的实现。而数据库设计、持久化设计这些都不是DDD的核心,属于外围的东西。与数据库驱动开发的思路形成对比,驱动中需要记住两个原则:领域驱动领域模型设计,领域模型驱动代码实现。
回想日常的开发过程,日常建表,然后写CRUD,因此也有一句很真实的话“面试造火箭,工作拧螺丝”。其根本原因在于表驱动思想,而不是领域驱动设计。
前者是能增加数据库的表数量,而后者才能形成长期的、具有业务意义的模型,这样的系统生命力才更加长久。我们也才能用工程的方法来编码,从编码转身为业务领域的开发专家。有很多关于领域驱动设计的论述中都未明确我们如何得到“领域”,只有合理的领域模型才能有效驱动设计开发。所以建好领域模型是关键,对于领域模型的思考与技术框架升级同样重要。
【Spring Cloud 2】软件架构设计
三、如何DDD
领域中还同时存在问题空间(problem space)和解决方案空间(solution space)。在问题空间中,我们思考的是业务所面临的挑战,而在解决方案空间中,我们思考如何实现软件以解决这些业务挑战。
问题空间是领域的一部分,对问题空间的开发将产生一个新的核心域。对问题空间的评估应该同时考虑已有子域和额外所需子域。因此,问题空间是核心域和其他子域的组合。问题空间中的子域通常随着项目的不同而不同,他们各自关注于当前的业务问题,这使得子域对于问题空间的评估非常有用。子域允许我们快速地浏览领域中的各个方面,这些方面对于解决特定的问题是必要的。
解决方案空间包含一个或多个界限上下文,即一组特定的软件模型。这是因为界限上下文是一个特定的解决方案,用以解决问题。
通常,我们希望将子域一对一地对应到限界上下文。这种做法显式地将领域模型分离到不同的业务板块中,并将问题空间和解决方案空间融合在一起。
但是在实践中,这种做法并不总是可能的,想像一下,谁没有维护过“毛线团”系统,现在我们就要借助界限上下文来安全的、合理的、快速的理顺这堆交织不清的关系。
电子商务系统是个典型的“大线团”,我们按照经验将其在逻辑上拆解为:产品目录子域、订单子域、发票子域,当然你也可以拆解出更多的子域,甚至将产品目录子域继续向下分解为类目子域、商品子域(虚线是逻辑子域)。另外还有一个专门用于库存管理的库存系统、以及用于销售预测的预测系统。
电商系统里面也存在物流相关的业务逻辑,同时物流又不可避免的作用于库存逻辑之上。而往往最难以把握的就是这部分相交的地方,这才是实际的项目场景,我们通常做法是将其归并为一个新的履约系统,作为一个支撑子域去辅助主要的电商系统。
当然,随着业务不断发展,我们的履约模式(比如支持同城当日达、商家仓储发货、电商集货仓发货、退货等等)、库存类型(调拨库存、越库操作、临期库存、残次库存等等)越来越复杂,我们考虑将其再向下分解为履约系统2.0、库存系统2.0。
核心就是我们可以在概念上使用多个子域来分解较大的界限上下文,也可以将多个分散的界限上下文包含在同一个新的子域当中,最终做到“子域和界限上下文一一对应”。我个人觉得,这个过程是最考验内功心法的地方。
上面我们已经说了会拆解出来新的子域,目的使“整洁干净”的界限上下文能够一对一的解决这个子域对应的问题空间,但是随着拆解就必然导致“关联关系”。因为要解决问题空间,必须使用对应的子域,你可以把它拆解出去,但是它始终存在于依赖网中。
我们通用的做法是在相交的地方,定义接口。由支撑的界限上下文去实现,可以做到支撑上下文的插拔式切换。这里仍然是我们强调的“依赖抽象”“解耦”。
“对于每种需要进行全局访问的对象,我们都应该创建另一个对象来作为这些对象的提供方,就像是在内存中访问这些对象的集合一样。为这些对象创建一个全局接口以供客户端访问。为这些对象创建添加和删除方法……
此外,我们还应该提供能够按照某种指定条件来查询这些对象的方法……只为聚合创建资源库”引用自《领域驱动设计》。大家和我的疑问一样,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来实现,也可以用MongoDB,甚至是Redis。尤其是当我们在更换底层存储的时候,领域层以及相关的服务并无任何影响。
那么微服务和DDD是什么关系呢?其实在2015年的一次演讲中,DDD的提出者Eric Evans表达了对微服务技术的热爱与支持,认为微服务是让DDD落地的好工具。因为DDD和微服务其本质是降低软件项目的复杂性,而DDD是一种设计理念/设计方法,DDD需要有强制性的原则做保障,否则不同的领域对象终究会混在一起。而微服务本身的一些限制,以及大家都能理解微服务的实施前提和首要条件,会在实现上给DDD增加了一些原则限制。DDD和微服务的不一定要同时使用落地,但是如果将DDD和微服务结合一起,效果是非常不错的。
四、分布式架构的基本理论CAP、BASE以及其应用
CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。
一致性C:在分布式系统中的所有数据备份,在同一时刻是否同样的值。
可用性A:保证每个请求不管成功或失败都应该有响应。
分区容忍性P:系统中任意信息的丢失或失败不会影响系统的继续运作。
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足。
因此在进行分布式架构设计时,必须做出取舍。当前一般是通过分布式缓存中各节点的最终一致性来提高系统的性能,通过使用多节点之间的数据异步复制技术来实现集群化的数据一致性。通常使用类似memcached之类的MOSQL作为实现手段。虽然memcached也可以是分布式集群环境的,但是对于一份数据来说,它总是存储在某一台memcached 服务器上。如果发生网络故障或服务器死机,则存储在这台服务器上的所有数据都将不可访问。由于数据是存储在内存中,重启服务器,将导致数据全部丢失。当然也可以自己实现一套机制,用来在分布式memcached之间进行数据的同步和持久化,但是实现难度非常的。
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。由于网络硬件肯定会出现延迟丢包的问题,所以分区容错性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,没有NOSQL系统能同时保证这三点。对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地。
(1)、数据库事务一致性需求
很多web实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求并不高。允许实现最终一致性。
(2)、数据库的写实时性和读实时性需求
对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。
(3)、对复杂的SQL查询,特别是多表关联查询的需求
任何大数据量的web系统,都非常忌讳大表的关联查询,以及复杂的复杂数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角度就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。
传统的关系型数据库在功能支持上通常很宽泛,从简单的键值查询,到复杂的多表联合查询再到事务机制的支持。而与之不同的是,NoSQL系统通常注重性能和扩展性,而非事务机制(事务就是强一致性的体现)。
传统的SQL数据库的事务通常都是支持ACID的强事务机制。A代表原子性,即在事务中执行多个操作是原子性的,要么事务中的操作全部执行,要么一个都不执行;C代表一致性,即保证进行事务的过程中整个数据库的状态是一致的,不会出现数据花掉的情况;I代表隔离性,即两个事务不会相互影响,覆盖彼此数据等;D表示持久化,即事务一旦完成,那么数据应该是被写到安全的,持久化存储的设备上(比如磁盘)。
NoSQL系统仅提供对行级别的原子性保证,也就是说同时对同一个Key下的数据进行的两个操作,在实际执行的时候是会串行的执行,保证了每一个Key-Value对不会被破坏。
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写。
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。接下来我们着重对BASE中的三要素进行详细讲解。基本可用:指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
注意,这绝不等价于系统不可用,以下两个就是“基本可用”的典型例子:
响应时间上的损失:正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
功能上的损失:正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
弱状态:也称为软状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性:强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
五、分布式架构下的高可用设计、可伸缩设计
[【Spring Cloud 3】分布式架构下
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
的高可用设计与可伸缩设计]( )
六、构建高性能的分布式架构
【Spring Cloud 4】构建高性能的大型分布式网站
第三章 漫谈微服务架构
===========
一、SOA架构和微服务架构之间的区别和联系
【Spring Cloud 5】SOA架构和微服务架构之间的关系
二、如何设计微服务及其设计原则
1、AKF拆分原则
AKF扩展立方体(参考《The Art of Scalability》),是一个叫AKF的公司的技术专家抽象总结的应用扩展的三个维度。理论上按照这三个扩展模式,可以将一个单体系统,进行无限扩展。
X 轴 :指的是水平复制,很好理解,就是讲单体系统多运行几个实例,做个集群加负载均衡的模式。
Z 轴 :是基于类似的数据分区,比如一个互联网打车应用突然或了,用户量激增,集群模式撑不住了,那就按照用户请求的地区进行数据分区,北京、上海、四川等多建几个集群。
Y 轴 :就是我们所说的微服务的拆分模式,就是基于不同的业务拆分。
场景说明:比如打车应用,一个集群撑不住时,分了多个集群,后来用户激增还是不够用,经过分析发现是乘客和车主访问量很大,就将打车应用拆成了三个乘客服务、车主服务、支付服务。三个服务的业务特点各不相同,独立维护,各自都可以再次按需扩展。
也就是说Y轴代表了系统功能上的划分方向,X轴代表了功能或数据上的复制方向,Z轴代表了按照一定条件进行优先级划分:比如,同样是扩展系统能力,按照地域优先级来进行数据分区。
2、前后端分离
前后端分离,不仅要做到技术代码的分离,还要做到物理分离的部署方式,不要使用之前的服务端模板技术,如JSP。
分离模式下,前后端交互界面更清晰,就剩下接口和模型,后端的接口简洁明了,更容易维护。
前端多渠道应用场景更容易实现,后端无需变更,采用统一的数据和模型,即可支撑PC前端、移动APP等访问。
3、无状态服务
状态:如果一个数据需要被多个服务共享,则这个数据就是状态。
有状态服务: 依赖于状态数据的服务称为有状态服务。
无状态服务: 不依赖状态数据的服务成为无状态服务。
这里提到的无状态服务原则,并不是说在微服务中不允许存在状态,而是将有状态的业务服务改为无状态的计算服务,把“状态”数据迁移到对应的“有状态服务”中。
eg:本地内存中建立的数据缓存、Session缓存,到现在的微服务架构中就应该把这些数据迁移到分布式缓存中存储,让业务服务变成一个无状态的计算节点。
迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。
4、无状态通信原则(RESTful通信风格)
无状态通信:每次通信都是独立的,不存在共用的数据(状态),不相互依赖某一数据。
无状态通信的最佳实践就是RESTful通信风格,RESTful具有如下优势:
天生适合无状态的HTTP协议,具有很强的扩展能力;
JSON报文序列化,轻量简单,人机均可读,学习成本低,对搜索引擎友好;