写点什么

极客大学 - 架构师训练营 第十周总结

用户头像
9527
关注
发布于: 2020 年 11 月 26 日

第十周 模块分解

如果一个工作多年的程序员,还是仅仅写一些跟他工作第一年差不多的 CRUD 代码。那么他迟早会遇到自己的职业危机。公司必然愿意用更年轻、更努力,当然也更低薪水的程序员来代替他。至于学习新技术的能力,其实多年工作经验也并没有太多帮助,有时候也许还是劣势。


资深程序员真正有优势的是他在一个业务领域的多年积淀,对业务领域有更深刻的理解和认知。那么如何将这些业务沉淀和理解反映到工作中,体现在代码中呢?实践 DDD 是一个不错的方式。


如果一个人有多年的领域经验,那么必然对领域模型设计有更深刻的认识,把握好领域模型在不断的需求变更中的演进,使系统维持更好的活力,并因此体现自己真正的价值。


微服务:服务本身的设计、维护以及治理

巨无霸应用系统带来的问题

  • 编译、部署困难。

  • 代码分支管理困难,merge 冲突,发布的时候问题互相纠缠,顾此失彼,每次发布都要半夜三更。

  • 数据库连接耗尽。

  • 新增业务困难。都是雷区,老人忙得要死,新人一帮忙就容易添乱。


大系统的拆分方式

  • 纵向拆分:将一个大应用拆分成多个小应用,如果新增业务较为独立,那么就直接将其设计部署为一个独立的 Web 应用系统。

  • 横向拆分:将复用的业务拆分出来,独立部署成微服务,新增业务只需要调用这些微服务即可快速搭建一个应用系统。


Dubbo 微服务框架架构


微服务:落地实践的策略与思路


Service Mesh 服务网格


Service Mesh 是一个基础设施层,用于处理服务间的通信,通常表现为一组轻量级网络代理,它们与应用程序部署在一起,而对应用程序透明。

Service Mesh 的 sidecar 模式 (1)


Service Mesh 的 sidecar 模式(2)


微服务架构落地

  • 业务先行,先理顺业务边界和依赖关系(最重要),技术是手段而不是目的。

  • 先有独立模块,后又分布式服务。

  • 业务耦合严重,逻辑复杂多变的系统进行微服务重构要谨慎。

  • 要搞清楚实施微服务的目的是什么?业务复用?开发边界清晰?分布式集群提升性能?



微服务最佳实践

  • CQRS:读命令与写命令隔离。

  • 事件溯源:将用户请求过程中每次状态变化都记录到事件日志中。

  • 利用事件溯源,可以精确复现任何用户状态,进行复核审计

  • 利用事件溯源,可以有效监控用户状态变化,并在此基础上实现分布式事务

  • 断路器:三种状态是关闭、打开、半开。

  • 服务重试与调用超时:上游调用者超时时间要大于下游调用者超时时间之和。

  • 最重要的是需求


倒三角模型

其实所有技术的落地都应该遵循以下这个倒三角模型的指导思想


  • 先有需求,我们到底应该达到一个什么样的目标

  • 然后去分析这个目标可以给自己,团队或者公司带来什么样的价值

  • 为了达到这些价值,我们应该遵循什么样的原则

  • 基于这些原则,有哪些最佳实践可以去参考

  • 最后才是去讨论和思考实现这些最佳实践的技术手段和工具有哪些

  • 最后才是工具和技术手段的实现和落地


很多时候,在工作中我们使用一个技术,往往是因为这个技术很牛逼,很新,才去使用,而往往忽略了以上的原则,本末倒置


微服务网关的技术架构

在使用微服务的时候,对于微服务的调用变化到了利用一个网关来进行调用。其架构如下图

所有客户端的请求都被发送到网管服务器,然后再进行相应微服务的调用。


网关作用
  • 统一接入:高性能、高并发、高可用、负载均衡

  • 安全防护:防刷控制、黑白名单、统一鉴权认证

  • 流量管控和容错:限流、降级、熔断

  • 协议适配:http、dubbo 等


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


异步网关

异步调用微服务,无需等待。具有更高的并发能力。

开放平台网关
  • API 接口:是开放平台暴露给合作者使用的一组 API,其形式可以是 RESTful、 WebService、RPC 等各种形式

  • 协议转换:将各种 API 输入转换成内部服务可以识别的形式,并将内部服务的返回 封装成 API 的格式。

  • 安全:除了一般应用需要的身份识别、权限控制等安全手段,开放平台还需要分级 的访问带宽限制,保证平台资源被第三方应用公平合理使用,也保护网站内部服务不会 被外部应用拖垮。

  • 审计:记录第三方应用的访问情况,并进行监控、计费等。

  • 路由:将开放平台的各种访问路由映射到具体的内部服务。

  • 流程:将一组离散的服务组织成一个上下文相关的新服务,隐藏服务细节,提供统 一接口供开发者调用。

开放平台的重要技术 - OAuth2.0

假设我们的项目有一个微信公众号服务。当用户进入我们的业务页面时,我们想获得用户的微信账号基本信息,从而对用户的一些行为进行记录。若要获取用户的微信账号信息,就需要用户的同意,即获得用户授权。只有用户同意了,微信才能允许我们读取用户的基本信息。那么要如何获得用户授权呢?


如果没有 OAuth 2.0,传统的做法是用户把微信账号信息给我们,然后我们登录微信号来获取用户的基本信息。这听起来就比较傻,也存在许多安全隐患。但是采用 OAuth 的话就可以解决这个问题。


OAuth 核心思想

  • 在“客户端”与“服务提供商”之间设置了一个授权层(authorization layer)

  • “客户端”无法直接登录“服务提供商”,但可以登录此“授权层”

  • 登录“授权层”时使用令牌 Token,该令牌与账号密码不同,拥有特定的权限范围与授权时间

运行流程

+--------+                               +---------------+|        |--(A)- Authorization Request ->|   Resource    ||        |                               |     Owner     ||        |<-(B)-- Authorization Grant ---|               ||        |                               +---------------+|        ||        |                               +---------------+|        |--(C)-- Authorization Grant -->| Authorization || Client |                               |     Server    ||        |<-(D)----- Access Token -------|               ||        |                               +---------------+|        ||        |                               +---------------+|        |--(E)----- Access Token ------>|    Resource   ||        |                               |     Server    ||        |<-(F)--- Protected Resource ---|               |+--------+                               +---------------+
复制代码


  • (A)用户打开客户端以后,客户端要求用户给予授权

  • (B)用户同意给予客户端授权。(核心步骤)

  • (C)客户端使用上一步获得的授权,向认证服务器申请令牌

  • (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌

  • (E)客户端使用令牌,向资源服务器申请获取资源

  • (F)资源服务器确认令牌无误,同意向客户端开放资源


授权模式

OAuth 2.0 定义了以下 4 种授权模式:

  1. 授权码模式(authorization code)

  2. 简化模式(implicit)

  3. 密码模式(resource owner password credentials)

  4. 客户端模式(client credentials)


微信使用的是授权码模式(authorization code)


领域驱动设计 DDD

为什么需要 DDD?

很多项目的实际情况:

  • 产品经理需求零零散散,不断变更。

  • 工程师在各处代码中寻找可以支持需求变更的代码,反复修修补补,对每块代码的职责缺乏思考。

  • 软件只有需求分析,缺乏真正的设计,系统没有一个统一的领域模型维持其内在逻辑一致性。(产品堆功能,但是对于这个功能是否真的是这个领域所必要的,缺乏思考)

  • 功能特性并不是按照领域模型内在的逻辑设计,而是按照人自己的主观想象拍脑袋设计。



项目时间一长,各种困难重重,需求延期,线上 bug 不断,管理者考虑要不要推倒重来,程序员考虑要不要跑路。



贫血模型 VS 充血模型

贫血模型:类似 Controller、Service、Dao 这种这种只有方法,没有成员变量,而方法调用时传递的数值对象,比如 Contract,没有方法(或者只有构造方法、getter、setter),典型的面向过程。

充血模型:合并了行为和数据的领域对象模型。通过领域模型对象的交互完成了业务逻辑。也就是说,设计好了领域模型对象,也就设计好了业务逻辑实现。


什么是领域?

领域是一个组织所做的事情以及其包含的一切,通俗地说,就是组织的业务范围和做事方式,也是软件开发的目标范围。领域驱动设计就是从领域出发,分析领域内模型及其关系,进而设计软件系统的方法。


什么是子域?

领域是一个组织所做的事情及其包含的一切。这个范围就太大了,不知道该如何下手。所以通常的做法是把一个领域拆分成多个子域。比如:用户、商品、订单、库存、物流、发票等。(一个微服务很可能就是一个子域)如何划分子域是一个关键。(卖家提现功能属于用户子域,还是财务子域?)



限界上下文

在一个子域中,会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。限界上下文和子域具有一对一的关系,用来控制子域的边界,保证子域内的概念统一性。通常限界上下文对应一个组件或者一个模块,或者一个微服务,一个子系统。



上下文映射图

不同的限界上下文,也就是不同的子系统或者模块之间会有各种交互动作。DDD 使用上下文映射图来设计这种关联和交互。


DDD 的战略设计和战术设计

领域、子域、限界上下文、上下文映射图,这些是 DDD 的战略设计。

实体(领域模型对象,每个实体是唯一的,有唯一标识。比如一个订单对象是一个实体。实体会发生变化,但是实体的唯一标识不会变化)、值对象(并不是领域内的对象都应该被设计为实体,比如住址的对象就是值对象,值对象不会变化,也没有行为)、聚合(是一个关联对象的集合,我们将其作为一个单元来处理数据更改。聚合根:将多个实体和值对象聚合在一起的实体)、CQRS、事件溯源,这些是 DDD 战术设计。

通过战略设计,划分模块和服务的边界及依赖关系,对微服务架构的设计至关重要。


软件组件设计原则

软件复杂度 vs 软件规模

一个复杂度为 100 的软件系统,如果能拆分成两个互不相关、同等规模的子系统,那么每个子系统的复杂度应该是 25,而不是 50。软件开发这个行业很久之前就形成了一个共识,应该将复杂的软件系统进行拆分,拆成多个更低复杂度的子系统,子系统还可以继续拆分成更小粒度的组件。也就是说,软件需要进行模块化、组件化设计。

组件内聚原则

组件内聚原则主要讨论哪些类应该聚合在同一个组件中,以便组件既能提供相对完整的功能,又不至于太过庞大。

  • 复用发布等同原则

  • 复用发布等同原则是说,软件复用的最小粒度应该等同于其发布的最小粒度。也就是说,如果你希望别人以怎样的粒度复用你的软件,你就应该以怎样的粒度发布你的软件。这其实就是组件的定义了,组件是软件复用和发布的最小粒度软件单元。这个粒度既是复用的粒度,也是发布的粒度。

  • 共同封闭原则

  • 共同封闭原则是说,我们应该将那些会同时修改,并且为了相同目的而修改的类放到同一个组件中。而将不会同时修改,并且不会为了相同目的而修改的类放到不同的组件中。

  • 共同复用原则

  • 共同复用原则是说,不要强迫一个组件的用户依赖他们不需要的东西。我们应该将互相依赖,共同复用的类放在一个组件中。比如说,一个数据结构容器组件,提供数组、Hash 表等各种数据结构容器,那么对数据结构遍历的类、排序的类也应该放在这个组件中,以使这个组件中的类共同对外提供服务。另一方面,这个原则也说明,如果不是被共同依赖的类,就不应该放在同一个组件中。如果不被依赖的类发生变更,就会引起组件变更,进而引起使用组件的程序发生变更。这样就会导致组件的使用者产生不必要的困扰,甚至讨厌使用这样的组件,也造成了组件复用的困难。


组件耦合原则

组件内聚原则讨论的是组件应该包含哪些功能和类,而组件耦合原则讨论组件之间的耦合关系应该如何设计。

  • 无循环依赖原则

  • 无循环依赖原则说,组件依赖关系中不应该出现环。如果组件 A 依赖组件 B,组件 B 依赖组件 C,组件 C 又依赖组件 A,就形成了循环依赖。

  • 稳定依赖原则

  • 稳定依赖原则说,组件依赖关系必须指向更稳定的方向。较少变更的组件是稳定的,也就是说,经常变更的组件是不稳定的。根据稳定依赖原则,不稳定的组件应该依赖稳定的组件,而不是反过来。组件不应该依赖一个比自己还不稳定的组件。

  • 稳定抽象原则

  • 稳定抽象原则说,一个组件的抽象化程度应该与其稳定性程度一致。也就是说,一个稳定的组件应该是抽象的,而不稳定的组件应该是具体的。这个原则对具体开发的指导意义就是:如果你设计的组件是具体的、不稳定的,那么可以为这个组件对外提供服务的类设计一组接口,并把这组接口封装在一个专门的组件中,那么这个组件相对就比较抽象、稳定。


组件的边界与依赖关系划分,不仅需要考虑技术问题,也要考虑业务场景问题。易变与稳定,依赖与被依赖,都需要放在业务场景中去考察。有的时候,甚至不只是技术和业务的问题,还需要考虑人的问题,在一个复杂的组织中,组件的依赖与设计需要考虑人的因素,如果组件的功能划分涉及到部门的职责边界,甚至会和公司内的政治关联起来。


发布于: 2020 年 11 月 26 日阅读数: 37
用户头像

9527

关注

还未添加个人签名 2020.04.22 加入

还未添加个人简介

评论

发布
暂无评论
极客大学 - 架构师训练营 第十周总结