极客大学架构师训练营 微服务网关 领域驱动设计 DDD OAuth 2.0 中台架构 第 20 课 听课总结
说明
讲师:李智慧
微服务网关
基于网关的微服务架构

网关作用

微服务网关

网关管道技术
网关本身没有什么业务,主要职责是各种校验与拦截,这些职责可以通过管道技术连接起来。

实现管道技术的责任链设计模式

Flower 异步网关与异步微服务框架

开源地址:
https://github.com/zhihuili/flower
利用Servlet3 实现异步网关

开放平台网关
API 接口:是开放平台暴露给合作者使用的一组API, 其形式可以是 Restful、WebService、RPC 等各种形式。
转义转换:将各种 API 输入转换成内部服务可以识别的形式,并将内部服务的返回 封装成 API 的格式。
安全:除了一般应用需要的身份识别、权限控制等安全手段,开放平台还需要分级的访问带宽限制,保证平台资源被第三方应用公平合理使用,也保护网站内部服务不会被外部应用拖垮。
审计:记录第三方应用的访问情况,并进行监控、计费等。
路由:将开放平台的各种访问路由映射到具体的内部服务。
流程:将一组离散的服务组织成一个上下文相关的新服务,隐藏服务细节,提供统一接口供开发者调用。

开放授权协议 OAuth 2.0


资源所有者:用户;
授权码授权
OAuth 2.0 一共有四种授权模式,分别是授权码、隐式授权、资源所有者密码凭据和客户端凭据。
目前互联网上使用最多也是最安全的一种方式是授权码方式。

Resource Owner: 用户;
Client:用户用的 App;
User-Agent: 比如微信、淘宝;
Authorization Server:授权中心;
领域驱动设计 Domain-Driven Design
为什么需要 DDD
很多项目的实际情况:
用户或者产品经理的需求零零散散,不断变更。
工程师在各处代码中寻找可以实现这些需求变更的代码,修修补补。
软件只有需求分析,并没有真正的设计,系统没有一个统一的领域模型维持其内在的逻辑一致性。
功能特性不是按照领域模型内在的逻辑设计,而是按照各色人等自己的主观想象设计。
项目时间一长,各种困难重重,需求不断延期,线上 bug 不断,管理者考虑是不是要推到重来,而程序员则考虑是不是要跑路。
例子:
早期的遥控器,有一堆密密麻麻的按钮,实际上很多都用不着的,现在就设计成只剩下几个就够用了。
iPhone之前的手机,密密麻麻的按钮,iPhone出来以后,前面就只有一个Home键,现在有FaceId, Home键也没了。
事务脚本(一直在用,不推荐)

问题在于:Service 是业务逻辑的核心。每次增加一种contractID合同类型, 就增加一个if else, 随着业务的变更,Service 类就变得特变臃肿,变得代码发臭。
领域模型(推荐)

每种contractID合同类型,都要增加一种contact、product、recognitionStrategy。
贫血模型 vs 充血模型
由于事务脚本模式中,Service、Dao这些对象只有方法,没有数值成员变量,而方法调用时传递的数值对象,比如 Contract,没有方法(或者只有一些 getter、setter 方法),因此事务脚本又被称作贫血模型。
领域模型的对象则包含了对象的数据和计算逻辑,比如合同对象,既包含合同数据,也包含合同相关的计算。因此从面相对象的角度看,领域模型才是真正的面向对象。收入确认和合同强相关的,是合同对象的一个职责,那么合同对象应该提供一个 calculateRecognition 方法计算收入。
领域模型是合并了行为和数据的领域的对象模型。通过领域模型对象的交互完成业务逻辑的实现,也就是说,设计好了领域模型对象,也就设计好了业务逻辑实现。和事务脚本被称作贫血模型相对应的,领域模型也被称作为充血模型。
DDD战略设计
领域是一个组织所做的事情以及其包含的一切。通俗地说,就是组织的业务范围和做事方式,也是软件开发的目标范围。
领域驱动设计就是从领域出发,分析领域内模型及其关系,进而设计软件系统的方法。

子域
领域是一个组织所做的事情以及其包含的一切。这个范围就太大了,不知道该如何下手。所以通常的做法是把整个领域拆分成多个子域,比如用户、商品、订单、库存、物流、发票等。
如何划分子域?
卖家提现功能是属于用户子域?订单子域?财务子域?还是直接设计一个提现子域?
卖家提现有可能是财务的子域。
子域就是微服务,微服务的边界就清楚了。
限界上下文
在一个子域中,会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便成为限界上下文。限界上下文和子域具有一对一的关系,用来控制子域的边界,保证子域内的概念统一性。
通常限界上下文对应一个组件或者一个模块,或者一个微服务,一个子系统。
上下文映射图
不同的限界上下文,也就是不同的子系统或者模块之间会有各种的交互合作。DDD 使用上下文映射图来设计这种关联和交互。

DDD战术设计
实体
领域模型对象也被称为实体,每个实体是唯一的,具有一个唯一标识,一个订单对象是一个实体,一个产品对象也是一个实体,订单ID或者产品ID是它们的唯一标识。实体可能会发生变化,比如订单的状态会变化,但是它们的唯一标识不会变化。
实体设计使 DDD 的核心所在,首先通过业务分析,识别出实体对象,然后通过相关的业务逻辑设计实体的属性和方法。这里最重要的是,是要把握住实体的特征是什么,实体应该承担什么职责,不应该承担什么职责,分析的时候要放在业务场景和界限上下文中,而不是想当然地认为这样的实体就应该承担这样的角色。
值对象
并不是领域内的对象都应该被设计为实体,DDD 推荐尽可能将对象设计为值对象。比如像住址这样的对象就是典型的值对象,也许建在住址上的房子可以被当做一个实体,但是住址仅仅是对房子的一个描述,像这样仅仅用来做度量或描述的对象应该被设计为值对象。
值对象的一个特点是不变性,一个值对象创建以后就不能再改变了。如果地址改变了,那就是一个新地址,而一个订单实体则可能会经历创建、待支付、已支付、待发货、已发货、待签收、待评价等各种变化。
聚合
聚合是一个关联对象的集合,我们将其作为一个单元来处理数据变更。每个集合都有一个根和一个边界。边界定义了聚合内部的内容。根是聚合中包含的单个特定实体。
聚合根:将多个实体和值对象聚合在一起的实体。

DDD 分层架构
领域实体的组合调用和事务控制在应用层。

用户支付 应用层会多个领域层:订单状态,支付等。
DDD 六边形架构
领域模型通过应用程序封装成一个相对比较独立的模块,而不同的外部系统则通过不同的适配器和领域模型交互,比如可以通过 HTTP 接口访问领域模型,也可以通过 Web Service 或者消息队列访问领域模型,只需要为这些不同的访问接口提供不同的适配器就可以了。

DDD 战略设计与战术设计
领域、子域、界限上下文、上下文映射图,这些是 DDD 的战略设计。
实体、值对象、聚合、CQRS、实践溯源,这些是 DDD 战术设计。
通过战略设计,划分模块和服务的边界及依赖关系,对微服务架构的设计至关重要。
李智慧老师经历的一个 DDD 重构实践过程
当前系统设计与问题汇总讨论:架构与代码混乱,需求迭代困难,部署麻烦,bug率逐渐升高。
针对问题分析具体原因:子系统 A 太庞大,模块 B 和 C 职责不清,业务理解不一致;
重新梳理业务规则和边界,明确业务术语:DDD 战略设计,领域建模。
技术框架选型与落地方案验证:DDD 战术设计,样例代码。
任务分解与持续重构:在不影响业务开发的前提下,按照战略与战术设计,将重构开发和业务迭代有机融合。
DDD 对个人成长的价值
如果一个工作多年的程序员,还是仅仅写一些跟他工作第一年差不多的 CRUD 代码。那么他迟早会遇到自己的职业危机。公司必然愿意用更年轻、更努力,当然也更低薪水的程序员来代替他。至于学习新技术的能力,其实多年工作经验也并没有太多帮助,有时候也许还是劣势。
资深程序员真正有优势的是TA在一个业务领域的多年积淀,对业务领域有更深刻的理解和认知。那么如何将这些业务沉淀和理解反映到工作中,体现在代码中呢? 实践 DDD 是一个不错的方式。
如果一个人有多年的领域经验,那么必然对领域模型设计有更深刻的认识,把握好领域模型在不断的需求变更中的演讲,使系统维持更好的活力,并因此体现自己的真正价值。
DDD战略建模,在重构业务系统时的实践 (逻辑思维)
主讲人:韩宇斌 (得到后端业务线 Leader)
目录:
用领域驱动来把握真正的业务需求;
领域驱动设计指导架构设计与建模;
用限界上下文来保护领域。
领域驱动设计帮助我解决了工作的难题
无路可退:入职第一个任务;
左右为难:实现技术重构的目标,满足不了业务需求!不去实现,又不知道该做什么?
得到 App 电商业务涉及的组织和系统

得到 App 目前如何确认收入

谁还没有个过去

没有订单时

财务:必须记录订单及交易状态
不靠谱的任务:增加一个订单中间层

实现了“订单化”后的调用关系

实现了“订单化”后,依赖于耦合加剧

原系统架构的问题很快暴露

财务:尽快把所有交付内容都接入“订单化”

接到的重构任务:订单代理(订单化)系统
实现 一个代理服务;
对接 交易平台组的订单系统和基础平台组的支付系统;
推动 若干个业务系统改造,改成调用新的代理服务。
订单代理系统如何隔离变化

“同时满足”了业务需求和技术目标
业务需求:所有的商品都实现“订单化”。
技术:不光都实现“订单化”,我们还实现个“订单化的代理系统”,应对外部系统的变化。
方案确定了!但这是业务的目标吗?

实现“订单化”并不是业务的真正需求

面临的挑战
无路可退:入职第一个任务;
左右为难:实现“订单代理系统”,满足不了业务需求!不去实现“订单代理系统”,那该做什么?
没有把握真正需求的原因

领域驱动设计的工作方式


问题域:电商的发货与算账;
业务期望:精确交付。
理解“订单化”在需求中作用和意义。

提炼和理解一些“统一语言”

领域驱动设计,找到真正的业务需求

二、领域驱动设计指导架构设计与建模
电商的基本业务模型

“个体户” 或 “小商贩”

订单代理系统架构的弊端

每个业务依然是个“”,相同功能的代码依然会重复;
改成调用订单代理系统,交付数据的准确性依然达不到财务要求。
需求:财务核算级别的精确交付
“小商贩”模式能解决技术问题,但不能满足业务需求。
交易领域中缺少一个专注交付的子领域。
重新理解和确定了领域问题

得到后端的核心子领域问题:是“履约”,是交付订单交付系统。
指导建模:把握领域并识别限界上下文。
目标:让业务方从“小商贩”入驻“超市”。

订单交付系统接管业务的交易行为

订单交付系统满足了业务和技术的目标

业务方不再是“小商贩”,入驻“超市”称为“卖家”;
交付的数据达到财务精准核算的要求。
版权声明: 本文为 InfoQ 作者【John(易筋)】的原创文章。
原文链接:【http://xie.infoq.cn/article/5f76e0d79df10ced238846520】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论 (1 条评论)