一、微服务
1.1 什么是微服务
微服务就是一些协同工作的小而自治的服务。
关键词: 小而自治
-- 小
“小”这个概念,一方面体现在微服务的内聚性上。
- 内聚性也可以称之为单一职责原则:“把因相同原因而变化的东西聚合到一起,而把因不同原因而变化的东西分离开来。”
- 也就是说,微服务应该专注于做好一件事情。
- 由业务边界来确定服务的边界
-- 自治
“自治”这个概念,强调的是,一个微服务就是一个独立的实体。体现在服务之间的松耦合上。黄金法则:你是否能够修改一个服务并对其进行部署,而不影响其他任何服务?
1.2 微服务的好处
- 技术异构性
- 不同服务根据业务场景、性能要求、功能需求采用不同的技术架构
- 新技术的快速实践,技术团队的快速成长
- 弹性/反脆弱性(anti-fragility)
- 服务降级
- 服务容灾、服务熔断
- 扩展
- 传统单体系统,无法做到对局部功能进行扩展
- 根据具体的业务需求,对特定的微服务进行扩展
- 通过架构的手段,节省成本
- 简化部署
- 传统单体应用,即使是一行代码修改,也需要整体重新部署。风险太大。
- 微服务架构,各个服务部署相互独立
- 灵活的发版方式
- 快速回滚
- 风险小
- 架构与组织结构相匹配
- 关联知识点:康威定律
- 服务的可重用、可组合
- 服务的可替代性,快速重写
1. 3微服务的弊端
对运维能力要求高;运行效率会降低;技术要求高,需要处理事务最终一致性等问题。微服务不该是这样的:
二、好服务的特征
2.1松耦合
松耦合指的是,修改一个服务,而另一个相关的服务,不需要修改。微服务的重要特点就是:能够独立修改和部署单个服务,而不会影响、甚至修改系统中的其它服务。
一个服务应该尽可能少地了解与之协作的服务信息。因为过度了解,会导致紧耦合。
2.2高内聚
高内聚指的是,把相关的行为聚集在一起。这样,需要改变行为时,只需要在一处修改,然后独立发布。
我们需要做的是,找出业务领域的边界,然后实现高内聚
三、微服务拆分原则
还是要重申两个重要概念,高内聚和松耦合,对应前面的关键词:小而自治。
3.1限界上下文
微服务一定要有清晰的功能边界。一个微服务对应了一个功能集合,这些功能一定是有一些共性的。比如,订单服务,那么创建订单、修改订单、查询订单列表,一般是订单服务的功能集合。
3.2逐步划分
一开始,我们会首先识别出一个粒度比较粗的服务模型。领域拆分并不是一步到位的,应当根据实际情况逐步展开。从单体应用到微服务体系的拆分过程能很好的说明这个问题。所以如果一开始不知道应该划分多细,完全可以先粗粒度划分,然后随着需要,初步拆分。比如一个电商一开始索性可以拆分为商品服务和交易服务,一个负责展示商品,一个负责购买支付。随后随着交易服务越来越复杂,就可以逐步的拆分成订单服务和支付服务。
四、DDD领域驱动设计
DDD 是一种架构设计方法,微服务是一种架构风格,DDD可以看作是微服务的方法论。两者从本质上都是为了追求高响应力,而从业务视角去分离应用系统建设复杂度的手段。两者都强调从业务出发,其核心要义是强调根据业务发展,合理划分领域边界,持续调整现有架构,优化现有代码,以保持架构和代码的生命力,也就是我们常说的演进式架构。
- DDD是一个很好的应用于微服务架构的方法论;
- 在项目的全生命周期内,所有岗位的人员都基于对业务的相同的理解来开展工作。所有人员站在用户的角度、业务的角度去思考问题,而不是站在技术的角度去思考问题;
- 诞生于2004年,兴起于2014年(微服务元年);
- DDD晦涩难懂,难以落地。因为DDD是方法论,不是行动指南
4.1 领域和子域
领域可以是多个子领域的一个虚拟集合,换句话说多个微服务也可以形成一个大域,不必纠结于领域和微服务之间的数量对应关系。例如在做架构设计的时候可能就把订单域作为一个领域,代表了这个域就是关于订单的,具体该有几个微服务,这需要更细的详细设计来提供。子域可以根据自身重要性和功能属性划分为三类子域,它们分别是:核心域、通用域和支撑域
4.2 界限上下文
任何领域模型都必须是在其特定的上下文环境中,如果DDD超出了其界定的上下文,则会产生歧义或者错误。例如社交媒体中的连接和网络应用中的连接的含义是完全不同的。对于任何上下文,我们都必须要能够建立明确的上下文的关联关系和边界。模型中的角色和边界的定义,在项目中非常重要。
4.3 库
库(Repositories)库指的是所有拥有共同实体和值接口的服务,且这些实体和值都在同一个聚合组中。库通常应该有增加,删除,修改和查找组中对象的方法。可以直接通过应用业务逻辑查询的引用来进行简洁的查询。
4.4 通用语言
通用语言就是能够简单、清晰、准确描述业务涵义和规则的语言。通用语言是团队统一的语言,不管你在团队中承担什么角色,在同一个领域的软件生命周期里都使用统一的语言进行交流。那么,通用语言的价值也就很明了,它可以解决交流障碍这个问题
4.5 聚合和聚合根
聚合(Aggregate):用来定义领域对象所有权和边界的领域模式,是用来帮助简化模型对象间的关系。通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。
聚合根(Aggregate Root):每个聚合都有一个根对象(聚合根实体),从外部访问只能通过这个对象。根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体。只有聚合根才能使用仓储库直接查询,如果根实体被删除,聚合内部的其它对象也将被删除。
4.6 实体和值对象
实体(Entity):一个由它的标识定义的对象叫做实体。通常实体具备唯一ID,能够被持久化,具有业务逻辑,对应现实世界业务对象。
值对象(Value Object):一个没有概念上标识符描述一个领域方面的对象,这些对象是用来表示临时的事物,或者可以认为值对象是实体的属性,这些属性没有特性标识但同时表达了领域中某类含义的概念。通常值对象不具有唯一ID,由对象的属性描述,可以用来传递参数或对实体进行补充描述。
4.7 领域事件
领域事件是对领域内发生的活动进行的建模。一个消息监听可以当成一个领域事件,表示发生了什么事情,然后需要做什么。领域事件对象用于记录模型产生的离散事件,我们必须谨慎选择和记录领域事件,仅仅对于那些有意义的事件进行记录。
4.8 领域和数据库
一般按垂直业务拆分,一个服务对应独立的库(也可能出现多个服务公用一个库,从设计上通常没有公用表),绝大多数情况下,一个服务和一个数据库搭配,对外提供接口。数据库应该被视为每个领域服务层微服务的私有数据库。
4.9 领域内拆分
领域拆分并不是一成不变的,应当具体情况具体分析。比如大众点评,其订单服务就拆分为了order-service和order-query-service,一来为了读写分离,二来order-query-service作为单独应用可以按需水平扩容。
4.10 核心子域、支撑子域、通用子域
-- 核心子域:
决定业务成功和公司核心竞争力的子域,整个系统最重要部分。 Eric Evans 曾提出如下问题助识别核心域:
- 为何这系统值得写?
- 为何不直接买个?
- 为何不让外包写?
若你对这几个问题的回答能帮你找到这个系统非写不可的理由,则它就是核心域。如电商系统的订单、商品服务。
-- 支撑子域(Supporting Subdomain)
不是你的核心竞争力,但又得做,市场无现成方案的子域。既不包含决定产品和公司核心竞争力的功能,也不包含通用功能的子域,但又必需。
具有企业特性,但不具通用性,如:
- 数据代码类的数据字典等系统
- 排行榜,可能根据各种信息排名,这种东西没人会按你需要做个,但对你自己,又是扩展自己系统的重要举措
- 调用银联、支付宝等第三方支付,即下游
- 报表系统
- 监控系统
-- 通用域(Generic Subdomain)
无太多个性化需求,同时会被多个子域使用的通用功能子域。如认证、权限等,无企业特点限制,无需太多定制化。行业里都这么做,即便不自己做,也不影响业务运行。
如很多 App 要给用户发通知,这种功能完全可买个服务,丝毫不影响业务运行。
五、DDD领域驱动经典四层设计
脚本式编程(dao+service)与DDD领域驱动模式区别如下:
5.1展现(表现层/用户接口层)(Presentation Layer):
负责以Restful的格式接受Web请求,然后将请求路由给Application层执行,并返回视图模型(View Model),其载体通常是DTO(Data Transfer Object);
5.2应用层(Application Layer):
主要负责获取输入,组装上下文,做输入校验,调用领域层做业务处理,如果需要的话,发送消息通知。当然,层次是开放的,若有需要,应用层也可以直接访问基础实施层;应用层连接用户接口层和领域层,不应该实现领域模型的核心领域逻辑。主要职能:协调领域层多个聚合完成服务的组合和编排。应用服务主要负责服务组合、编排和转发,处理业务用例的执行顺序以及结果的拼装。在应用服务中还可以进行安全认证、权限校验、事务控制、领域事件发布或订阅等。
5.3领域层(Domain Layer):
领域层是领域模型的核心,主要实现领域模型的核心业务逻辑。领域模型的业务逻辑主要由实体和领域服务来实现。其中实体会采用充血模型来实现业务功能,如单一实体(或值对象)不能实现时,就交由领域服务进行组合和协调聚合内多个实体(或值对象),实现复杂的业务逻辑。
主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(Entities)的函数对外部提供业务逻辑的计算和处理;
5.4基础实施层(Infrastructure Layer):
主要包含Tunnel(数据通道)、防腐层,Config和Common。这里我们使用Tunnel这个概念来对所有的数据来源进行抽象,这些数据来源可以是数据库(MySQL,NoSql)、搜索引擎、文件系统、也可以是SOA服务等;Config负责应用的配置;Common是通用的工具类。
基础层贯穿DDD所有层。其职能:提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。
六、如何进行DDD领域驱动设计
DDD 包括战略设计和战术设计两部分,它们分别从不同的视角出发,完成领域建模和微服务的拆分和设计。事件风暴(Event Storming)是落实领域驱动的一种常用方法,使用事件风暴能够通过领域事件来识别出聚合根,组合的聚合根则又组成限界上下文,限界上下文则正是我们寻找的“微服务”的概念。
6.1战略设计
是从业务视角出发,划分业务的领域边界,建立基于通用语言和业务上下文语义边界的限界上下文,构建领域模型。而限界上下文就可以作为微服务拆分和设计的边界。以一种领域专家、设计人员、开发人员都能理解的“通用语言”作为相互交流的工具,在不断交流的过程中发现和挖出一些主要的领域概念,然后将这些概念设计成一个领域模型;
6.2战术设计
则是从技术视角出发,侧重于对领域模型的技术实现,按照领域模型完成微服务的开发和落地。在战术设计中会有聚合、聚合根、实体、值对象、领域服务、领域事件、应用服务和仓储等领域对象,这些领域对象会以代码的形式映射到微服务中完成设计和系统落地。 由领域模型驱动软件设计,用代码来表现该领域模型。领域需求的最初细节,在功能层面通过领域专家的讨论得出。
七、领域模型建模
UML建模示例:
@startuml
hide circle
note "知识库领域建模" as knowledge_uml #yellow
entity 知识库 {
kie_knowledge
==
知识来源:工单、设备
类型: 知识、案例
知识状态: 待提交、已发布、已撤销
查看次数:调用详情次数
}
entity 知识操作日志 {
kie_knowledge_operation_log
==
操作名称
知识ID
操作人ID
操作人名字
是否成功(success)
操作类型: 存入草稿,撤销,发布
}
entity 知识关联对象 {
kie_knowledge_linked_object
==
知识ID
对象上下文快照: jsonb
}
entity 知识点赞记录 {
kie_knowledge_like_record
==
用户ID
知识ID
点赞标识:bool
}
知识库 ||--|{ 知识操作日志
知识库 ||--o{ 知识关联对象
知识库 ||--o{ 知识点赞记录
@enduml