posted in 架构设计 

用直观方式说明:为什么分库分表、怎么分、对现有系统的影响。

一页图:我们做了什么

flowchart LR
  subgraph App[业务应用]
    A[下单/报表/任务] -->|调用 SDK| B(ShardManager)
  end

  subgraph Core[ShardManager 内核]
    B --> C[按规则选库\n(Logical Region)]
    B --> D[按月份选表\n(shipments_YYYYMM)]
  end

  C -->|返回 databaseKey| E[(业务数据库集群)]
  D -->|返回 tableName| E

  subgraph Control[控制面]
    F[中央库\nwarehouse_shard_map]
    G[(Redis 缓存)]
  end

  B -->|读取映射| F
  B -->|版本前缀缓存| G

  style B fill:#eef,stroke:#446
  style C fill:#efe,stroke:#484
  style D fill:#efe,stroke:#484
  style F fill:#ffe,stroke:#a84
  style G fill:#ffe,stroke:#a84
  • 应用通过 SDK 得到“库 + 表”。
  • 选库:按“逻辑 Region(由 warehouse 映射)”;PoC 两个库。
  • 选表:按月份 shipments_YYYYMM
  • 映射来自中央库,Redis 缓存带有版本前缀,变更快速生效。

为什么这样做

  • 扩展性:按月拆表、按 region 分库,降低单库/单表压力。
  • 低侵入:SDK 只计算库表,不改你现有的数据库调用方式。
  • 可演进:分库/分表策略可插拔,后续可切换为“城市/租户/渠道”。

对现有系统的影响

  • 代码:新增一步“询问 SDK 要去哪张表”,其余不变。
  • 配置:新增 sharding.*;中央库连接由业务维护。
  • 运维:中央库维护映射与版本;Redis 缓存;Atlas 生成变更计划并审批执行。

变更如何生效(时序)

sequenceDiagram
  participant Ops as 运维
  participant Central as 中央库
  participant Redis as Redis
  participant App as 业务应用
  participant SDK as ShardManager

  Ops->>Central: 更新 warehouse_shard_map
  Ops->>Central: 更新 shard_version.version
  SDK->>Redis: 读 version 前缀 (miss)
  SDK->>Central: 读取最新 version
  SDK->>Redis: 写入 version,设置 TTL
  App->>SDK: route(warehouseId, ts)
  SDK->>Redis: 读 {prefix}{version}:wh:{id}
  alt miss
    SDK->>Central: 查询映射
    SDK->>Redis: setex 缓存映射
  end
  SDK-->>App: 返回 databaseKey + tableName

验收与演示

  • Demo:php examples/route_demo.php 输出如 uni_region_001.shipments_YYYYMM
  • 单测:
    • 默认策略路由;
    • 跨月表集合。

后续路线

  • 更多策略(城市/租户/渠道)。
  • 更精细的只读副本选择(延迟/权重)。
  • 开源拆分:uni/sharding-coreuni/sharding-lumen
posted in 架构设计 

ecommerce-platform/
├── README.md
├── docker-compose.yml
├── requirements.txt
└── src/
    ├── shared/                           # 共享内核
    │   ├── __init__.py
    │   ├── domain/
    │   │   ├── __init__.py
    │   │   ├── base_entity.py
    │   │   ├── base_aggregate_root.py
    │   │   ├── domain_event.py
    │   │   ├── value_objects/
    │   │   │   ├── __init__.py
    │   │   │   ├── money.py
    │   │   │   ├── email.py
    │   │   │   └── phone.py
    │   │   └── exceptions/
    │   │       ├── __init__.py
    │   │       ├── domain_exception.py
    │   │       └── business_rule_violation.py
    │   ├── infrastructure/
    │   │   ├── __init__.py
    │   │   ├── database/
    │   │   │   ├── __init__.py
    │   │   │   ├── base_repository.py
    │   │   │   └── unit_of_work.py
    │   │   ├── messaging/
    │   │   │   ├── __init__.py
    │   │   │   ├── event_bus.py
    │   │   │   └── message_broker.py
    │   │   └── utils/
    │   │       ├── __init__.py
    │   │       ├── id_generator.py
    │   │       └── datetime_utils.py
    │   └── application/
    │       ├── __init__.py
    │       ├── base_command.py
    │       ├── base_query.py
    │       └── base_application_service.py
    │
    ├── core_domains/                     # 核心域
    │   ├── __init__.py
    │   │
    │   ├── catalog/                      # 核心域1:商品目录域
    │   │   ├── __init__.py
    │   │   ├── bounded_contexts/
    │   │   │   ├── __init__.py
    │   │   │   │
    │   │   │   ├── product_management/   # 限界上下文1:商品管理
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── domain/
    │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   ├── aggregates/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── product/         # 聚合1:商品聚合
    │   │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   │   ├── product.py           # 聚合根
    │   │   │   │   │   │   │   ├── product_variant.py   # 实体
    │   │   │   │   │   │   │   ├── product_attribute.py # 值对象
    │   │   │   │   │   │   │   └── product_specification.py # 规约
    │   │   │   │   │   │   ├── category/        # 聚合2:分类聚合
    │   │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   │   ├── category.py          # 聚合根
    │   │   │   │   │   │   │   ├── category_hierarchy.py # 值对象
    │   │   │   │   │   │   │   └── category_rules.py    # 领域服务
    │   │   │   │   │   │   └── brand/           # 聚合3:品牌聚合
    │   │   │   │   │   │       ├── __init__.py
    │   │   │   │   │   │       ├── brand.py             # 聚合根
    │   │   │   │   │   │       ├── brand_authorization.py # 实体
    │   │   │   │   │   │       └── brand_policy.py      # 值对象
    │   │   │   │   │   ├── value_objects/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── product_id.py
    │   │   │   │   │   │   ├── sku.py
    │   │   │   │   │   │   ├── price.py
    │   │   │   │   │   │   └── weight.py
    │   │   │   │   │   ├── entities/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   └── product_review.py
    │   │   │   │   │   ├── domain_services/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── product_validation_service.py
    │   │   │   │   │   │   └── pricing_service.py
    │   │   │   │   │   ├── repositories/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── product_repository.py
    │   │   │   │   │   │   ├── category_repository.py
    │   │   │   │   │   │   └── brand_repository.py
    │   │   │   │   │   └── events/
    │   │   │   │   │       ├── __init__.py
    │   │   │   │   │       ├── product_created.py
    │   │   │   │   │       ├── product_updated.py
    │   │   │   │   │       └── product_discontinued.py
    │   │   │   │   ├── application/
    │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   ├── commands/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── create_product_command.py
    │   │   │   │   │   │   ├── update_product_command.py
    │   │   │   │   │   │   └── discontinue_product_command.py
    │   │   │   │   │   ├── queries/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── get_product_query.py
    │   │   │   │   │   │   └── search_products_query.py
    │   │   │   │   │   ├── handlers/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── product_command_handler.py
    │   │   │   │   │   │   └── product_query_handler.py
    │   │   │   │   │   └── services/
    │   │   │   │   │       ├── __init__.py
    │   │   │   │   │       └── product_application_service.py
    │   │   │   │   ├── infrastructure/
    │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   ├── persistence/
    │   │   │   │   │   │   ├── __init__.py
    │   │   │   │   │   │   ├── sqlalchemy_product_repository.py
    │   │   │   │   │   │   ├── sqlalchemy_category_repository.py
    │   │   │   │   │   │   └── models/
    │   │   │   │   │   │       ├── __init__.py
    │   │   │   │   │   │       ├── product_model.py
    │   │   │   │   │   │       └── category_model.py
    │   │   │   │   │   └── external_services/
    │   │   │   │   │       ├── __init__.py
    │   │   │   │   │       └── image_service.py
    │   │   │   │   └── presentation/
    │   │   │   │       ├── __init__.py
    │   │   │   │       ├── api/
    │   │   │   │       │   ├── __init__.py
    │   │   │   │       │   ├── product_controller.py
    │   │   │   │       │   └── category_controller.py
    │   │   │   │       └── dto/
    │   │   │   │           ├── __init__.py
    │   │   │   │           ├── product_dto.py
    │   │   │   │           └── category_dto.py
    │   │   │   │
    │   │   │   └── inventory_management/     # 限界上下文2:库存管理
    │   │   │       ├── __init__.py
    │   │   │       ├── domain/
    │   │   │       │   ├── __init__.py
    │   │   │       │   ├── aggregates/
    │   │   │       │   │   ├── __init__.py
    │   │   │       │   │   └── inventory/
    │   │   │       │   │       ├── __init__.py
    │   │   │       │   │       ├── inventory.py
    │   │   │       │   │       ├── stock_movement.py
    │   │   │       │   │       └── reorder_policy.py
    │   │   │       │   └── repositories/
    │   │   │       │       ├── __init__.py
    │   │   │       │       └── inventory_repository.py
    │   │   │       ├── application/
    │   │   │       └── infrastructure/
    │   │   └── shared/                   # 该域内共享
    │   │       ├── __init__.py
    │   │       └── catalog_shared_types.py
    │   │
    │   └── trading/                      # 核心域2:交易域
    │       ├── __init__.py
    │       ├── bounded_contexts/
    │       │   ├── __init__.py
    │       │   ├── order_management/     # 订单管理上下文
    │       │   │   ├── __init__.py
    │       │   │   ├── domain/
    │       │   │   │   ├── aggregates/
    │       │   │   │   │   ├── order/
    │       │   │   │   │   │   ├── order.py
    │       │   │   │   │   │   ├── order_item.py
    │       │   │   │   │   │   └── order_status.py
    │       │   │   │   │   └── shopping_cart/
    │       │   │   │   │       ├── cart.py
    │       │   │   │   │       └── cart_item.py
    │       │   │   │   └── repositories/
    │       │   │   │       └── order_repository.py
    │       │   │   ├── application/
    │       │   │   ├── infrastructure/
    │       │   │   └── presentation/
    │       │   └── payment_processing/   # 支付处理上下文
    │       │       ├── __init__.py
    │       │       ├── domain/
    │       │       ├── application/
    │       │       ├── infrastructure/
    │       │       └── presentation/
    │       └── shared/
    │           ├── __init__.py
    │           └── trading_shared_types.py
    │
    └── supporting_domains/               # 支撑域
        ├── __init__.py
        └── user_management/              # 支撑域:用户管理域
            ├── __init__.py
            ├── bounded_contexts/
            │   ├── __init__.py
            │   ├── authentication/       # 认证上下文
            │   │   ├── __init__.py
            │   │   ├── domain/
            │   │   │   ├── aggregates/
            │   │   │   │   └── user/
            │   │   │   │       ├── user.py
            │   │   │   │       ├── user_credential.py
            │   │   │   │       └── login_session.py
            │   │   │   └── repositories/
            │   │   │       └── user_repository.py
            │   │   ├── application/
            │   │   ├── infrastructure/
            │   │   └── presentation/
            │   └── profile_management/   # 用户档案管理上下文
            │       ├── __init__.py
            │       ├── domain/
            │       ├── application/
            │       ├── infrastructure/
            │       └── presentation/
            └── shared/
                ├── __init__.py
                └── user_shared_types.py

关键设计说明

1. 域的分层

  • core_domains/: 核心域,企业的核心竞争力
  • supporting_domains/: 支撑域,支持核心业务
  • shared/: 共享内核,跨域共享的基础设施

2. 限界上下文的体现

catalog 核心域中:

  • product_management: 商品管理上下文
  • inventory_management: 库存管理上下文

每个上下文都有完整的分层架构。

3. 聚合的组织

product_management 上下文的 domain/aggregates/ 中:

  • product/: 商品聚合(包含商品、商品变体等)
  • category/: 分类聚合(包含分类层次结构)
  • brand/: 品牌聚合(包含品牌授权等)

4. 完整的DDD分层

每个限界上下文都包含:

  • Domain Layer: 聚合、实体、值对象、领域服务
  • Application Layer: 应用服务、命令/查询处理器
  • Infrastructure Layer: 持久化、外部服务适配
  • Presentation Layer: API控制器、DTO

5. 依赖方向

Presentation → Application → Domain ← Infrastructure

依赖始终指向内层,符合洋葱架构原则。

posted in 架构设计 

一、分支管理模型选型

Branching Model在业界有多种规范,如:Gitflow、AoneFlow、OneFlow等,Branching Model的复杂程度取决于实际开发团队和代码规模,自动化程度等因素,实际使用时需要结合当前具体情况精简。但随着情况变化,当前的Model可能会不适用,需要定期Review并优化模型选型。

二、Branching Model的选型依据参考

  1. 是否一个Repository同时有多人在分别开发不同feature,或者做bugfix,且需要同时deploy到不同环境。eg. dev, pre, production, grayscale etc.
    • 解决方案:每个环境需要一个对应的branch或tag
  2. 是否需要同时维护多个长期版本
    • 解决方案:需要多个base branch
  3. 是否需要一个branch来记录latest stable version,这个version和production environment上deploy的内容完全一致
    • 解决方案:需要一个mark branch
  4. 是否需要在发布新版本到生产环境前冻结代码进行控制
    • 解决方案:需要对应一个branch,此branch开发无权限merge,代码被merge到此branch之后开始做集成测试
  5. 是否有CI/CD自动化工具
    • 解决方案:CI/CD自动化工具可以简化开发对git的操作

三、小型团队选型结论

  1. 存在多个开发在一个Repository上开发不同feature然后合并到一起测试和同时deploy到production environment的场景
  2. 不需要维护多个长期版本
  3. 不需要一个单端的branch来记录production environment上deploy的内容
  4. 不必要deploy前冻结代码
  5. 无CI/CD自动化工具

结论:

用到的branch(尽量少):

  • develop branch
  • release branch
  • feature branch

四、branching model & workflow

  • 使用long live的develop branch来记录最新的代码
  • 使用short live的release branch来记录next version的代码,并且使用release branch部署test和production environment
  • 使用Tag来记录latest的production environment内容
  • 在最新的Tag上创建short live的bugfix branch用来修production environment的bug

具体的workflow如下:

4.1 working on a feature branch

  1. Fetch develop branch so as to make it up to date
$ git fetch develop
  1. Start a new feature branch
$ git checkout -b feature/${customized_feature_name} origin/develop
  1. Implement feature and commit several times
$ git commit -a --amend
  1. Merge with latest develop branch
$ git fetch develop
$ git rebase origin/develop
  1. Self-testing
  2. Push your branch. Make sure you have only one commit for your feature before push
$ git log
$ git rebase -i hash
$ git push origin feature/${customized_feature_name}
  1. Open your browser and visit the "pull request url" printed on your console, then finish committing a PR on your browser with detailed test cases in your comment.

4.2 working on a release branch

You need a release branch when you decide to deploy a new release to production environment.

  1. Fetch develop branch so as to make it up to date
$ git fetch develop
  1. Start a release branch
$ git log origin/develop
$ git checkout -b release/x.y.z hash
  1. Deploy to test environment based on release/x.y.z branch
  2. Test thoroughly & Bug fixing(There should be only one commit for all bugs)
  3. Deploy to production environment based on release/x.y.z branch's latest commit & Test
  4. Squash commits & Tag the commit
$ git rebase -i hash
$ git tag x.y.z
  1. Merge to develop branch and push
$ git checkout develop
$ git pull origin develop
$ git merge release/x.y.z
$ git push --tags origin develop
$ git branch -d release/x.y.z

4.3 working on a hotfix branch

  1. Start a bugfix branch
$ git checkout -b hotfix/x.y.1 x.y.0
  1. Code and fix bugs
  2. Finish a bugfix branch
$ git checkout develop
$ git pull origin develop
$ git merge hotfix/x.y.1
$ git push --tags origin develop
$ git branch -d hotfix/x.y.1

rebase -i用法参考

Rewriting History Chapter from Pro Git SCM book

posted in 架构设计 

什么是领域驱动

数据模型驱动

传统服务建模的过程是先通过业务分析数据模型,然后在关系型数据库上基于范式设计数据库表以及表之间的关系。最后通过ORM建立起数据持久化对象和数据库表之间的映射。还要通过数据访问对象(DAO)来操作数据持久化对象。

由于持久化对象和数据访问对象都不包含业务逻辑,服务就成为了业务逻辑的唯一栖身之地。这时,持久化对象是数据的提供者,实现服务时就会非常自然地选择事务脚本(Transaction Script)模式。

《企业应用架构模式》对事务脚本的定义为:

使用过程来组织业务逻辑,每个过程处理来自表现层的单个请求。这是一种典型的过程式设计,每个服务功能都是一系列步骤的组合,从而形成一个完整的事务。注意,这里的事务代表一个完整的业务行为过程,而非保证数据一致性的事务概念。

不要因为事务脚本采用面向过程设计就排斥这一模式,相较于对编程范式的偏执,我认为 Martin Fowler 在书中说的一句话更加公道:

“不管你是多么坚定的面向对象的信徒,也不要盲目排斥事务脚本。许多问题本身是简单的,一个简单的解决方案可以加快你的开发速度,而且运行起来也会更快。”

即使采用事务脚本,我们也可以通过提取方法来改进代码的可读性。每个方法都提供了一定的抽象层次,通过方法的提取就可以在一定程度上隐藏细节,保持合理的抽象层次。这种方式被 Kent Beck 总结为组合方法(Composed Method)模式:

  • 把程序划分为方法,每个方法执行一个可识别的任务
  • 让一个方法中的所有操作处于相同的抽象层
  • 这会自然地产生包含许多小方法的程序,每个方法只包含少量代码

领域建模驱动

领域建模驱动是相对于数据建模驱动的。
领域建模驱动要先识别出领域,确定领域模型,然后再确定技术实现方案。
识别领域确定领域模型的过程需要分析清楚问题域,理清业务。所以领域驱动设计倡导的是从问题出发,先分析问题域得到业务,然后结合业务、规模、系统结构进行设计,然后再编码实现。

  • 问题域——客户的需求
  • 解决方案域——需求的实现
问题->领域->业务逻辑->规模->结构->实现

DDD为什么突然火了

微服务的划分粒度如何把握。

纵观软件设计的历史,不是分久必合、合久必分,而是不断拆分、持续拆分的微型化过程。

限界上下文(Bounded Context)有利于确定微服务的划分。

理论上:

  • 一个微服务不要小于一个聚合
  • 一个微服务不要大于一个限界上下文

如果出现如下情况说明微服务划分不合理:

  • 微服务间经常需要分布式事务来支持业务数据一致性
  • 对于微服务A来说,处理一个请求总是依赖另一个微服务B
  • 一个微服务中出现多个名字相同的领域实体

DDD的价值

应对软件复杂度

  • 从领域出发,保证软件分析模型和设计模型一致,业务人员和开发协作,保证软件质量
  • 提出统一语言,确保所有概念在各自的上下文中清晰无歧义
  • 提出分层架构,有效分离业务和技术,同时从垂直方向上做分隔职责边界
  • 战略设计通过拆分子域和限界上下文,从横向上分隔离职责边界
  • 战术设计通过引入多种领域模型概念帮助领域模型设计落地
  • 引入微服务间协作的设计模式

软件为什么会复杂

主观可控:

  • 业务规则 复杂
  • 需求变化 不可预测
  • 规模变
  • 结构变 复杂
  • 技术选型 升级

主观不可控:

  • 人不够
  • 人员流动
  • 时间紧
  • 组织架构不合理

如何应对软件复杂

关注点分离(Separation of Concern)

  • 分离业务和技术
    • 分层架构
      • 六边形架构
      • 整洁架构
    • 分层
      • UI层(User Interface Layer)
      • 应用层(Application Layer)
      • 领域层(Domain Layer)
      • 技术设施层(Infrastructure Layer)
    • 设计原则
      • SRP (Single-Responsibility Principle)
      • DIP (Dependency inversion principle)
      • Interface-Oriented Programming
      • Dependency Injection
  • 分离职责
    • 限界上下文
    • 聚合
    • 领域

DDD的设计过程

战略设计阶段

  • 问题域
    • 限界上下文(Bounded Context)
    • 上下文映射(Context Map)
    • 核心域(Core Domain)
    • 子域(Subdomain)
  • 架构
    • 六边形架构
    • 整洁架构
    • CQRS
    • Event Sourcing

战术设计阶段

  • 值对象(Value Object)
  • 实体(Entity)
  • 领域服务(Domain Service)
  • 领域事件(Domain Event)
  • 资源库(Repository)
  • 工厂(Factory)
  • 聚合(Aggregate)
  • 应用服务(Application Service)

一个完整的生命周期

阶段 参与者 产出物
需求 产品经理、客户 需求文档
KickOff 产品经理、客户、开发负责人、测试负责人 明确利益相关人、对业务的共同理解、识别主要用户故事
事件风暴 领域专家、产品经理、DDD专家、架构师、相关开发 统一语言、领域模型
架构设计 架构师、DDD专家 代码包结构、技术选型、系统间通信方式
开发 架构师、开发 单元测试、代码实现
演示 产品经理、开发负责人、测试负责人、领域专家、客户 客服意见
测试 测试 测试报告
交付 运维、开发、测试 服务

领域模型的提炼

6W模型

  • Who: 角色,一个场景中参与的用户是什么角色
  • Why: 价值,解决用户什么问题
  • What: 功能,需要做什么
  • When: 流程,具体的业务逻辑是什么
  • Where: 边界,场景的边界
  • How: 实现,具体的技术实现

如何实施6W模型

1. 用例

用例名称:批量增加主机
用例目的(Why):本用例可以帮助「角色(Who)」为其微服务一次性关联(What)符合「条件」的主机
参与者(Who):「角色」
前置条件:微服务已经被纳管

基础流程(When):
1. ……
2. ……
3. ……

替代流程:异常情况一
替代流程:异常情况二

2. TDD

@Autowared
private HostAppService hostAppService;

@Test
// 测试方法以描述业务的形式命名
// 不要对被测试方法写单元测试,要对领域场景编写,驱动我们识别场景分解任务
public void should_return_100_when_100_valid_hosts_registered_successfully() {
    // given 驱动我们思考对象的创建,与其他对象的协作,领域对象的命名(统一语言)
    Microservice microservice = Mock.one(Microservice.class);
    List<Host> validHosts = validHosts(100);
    
    // when 驱动我们思考职责边界、方法的命名、入参
    int successAmount = hostAppService.registerHostsForMicroservice(microservice, validHosts)
}
    
    // then 驱动我们思考方法的返回值、对系统的其他影响
    AssertThat(successAmount, is(100));

统一语言

  • 通过事件风暴获得统一语言的中文
  • 标注对应英文
  • 引入局外人对用例的阐述进行提问
  • 统一语言要具备专业性(水,H2O)

限界上下文

限界上下文需要满足四个特征:

  • 最小完备:要完备,而且要最小;不依赖外部
  • 自我履行:知识专家,基于掌握的知识决定需要履行的职责,不做边界外的职责
  • 稳定空间:分离内外,外部变化对内不影响
  • 独立进化:自身进化对外不影响

识别限界上下文

识别限界上下文就是寻找边界:基于业务边界、技术边界,找到限界上下文的边界,进而找到工作边界、应用边界。

验证识别结果

  • 是否能给限界上下文轻松的命名
  • 限界上下文之间是否容易协作

上下文协作关系

上下文映射模式:

  1. 合作关系(Partnership)
  2. 共享内核(Shared Kernel)
  3. 客户方-供应方开发(Customer-Supplier Development)
  4. 遵奉者(Conformist)
  5. 分离方式(Separate Ways)

降低上下文之间耦合的模式:

  1. 防腐层(Anticorruption Layer)
  2. 开放主机服务(Open Host Service)
  3. 发布/订阅事件

附:《实现领域驱动设计》里的9种组织模式和集成模式

①合作关系(Partnership)

如果2个限界上下文的团队要么一起成功,要么一起失败,此时就是这种关系。应该为相互关联的软件功能制定好计划表,这样可以确保这些功能在同一个发布中完成。

②共享内核(Shared Kernel)
   
对模型和代码的共享将产生一种紧密的依赖性,对于设计来说,这种依赖性可好可坏。我们需要为共享的部分模型指定一个显式边界,并保持共享内核的小型化。共享内核具有特殊的状态,在没有与另一个团队协商的情况下,这种状态是不能改变的。我们应该引入一种持续集成过程来保证共享内核与通用语言的一致性。

③客户方——供应方(Customer-Supplier Development)

当2个团队处于一种上游——下游关系时,上游团队可能独立于下游团队完成开发,此时下游团队的开发可能会受到很大的影响。因此,在上游团队的计划中,我们应该顾及到下游团队的需求。

④遵奉者(Conformist)

在存在上游——下游关系的2个团队中,如果上游团队已经没有动力提供下游团队之需,下游团队便孤军无助了。处于利他主义,上游团队可能向下游团队做出种种承诺,但是有很大的可能是:这些承诺是无法实现的。下游团队只能盲目地使用上游团队模型。

⑤防腐层(Anticorruption Layer)

在集成2个设计良好的限界上下文时,翻译层可能很简单,甚至可以很优雅的实现。但是,当共享内核,合作关系或客户方——供应方关系无法顺利实现时,此时的翻译将变得复杂。对于下游客户来说,你需要根据自己的领域模型创建一个单独的层,该层作为上游系统的委派向你的系统提供功能。防腐层通过已有的接口与其他系统交互,而其他系统只需要做很小的修改,甚至无需修改。在防腐层内部,它在你自己的模型和他方模型之间进行翻译转换。【为每个防腐层定义相应的领域服务】

⑥开放主机服务(Open Host Service)

定义一种协议,让你的子系统通过该协议来访问你的服务。并且需要将协议公开。

⑦发布语言(Published Language)

在2个限界上下文之间翻译模型需要一种公用的语言。此时你应该使用一种发布出来的共享语言来完成集成交流。发布语言通常与开放主机服务一起使用。

⑧另谋他路(SeparateWay)

在确定需求时,我们应该做到坚持彻底。如果2套功能没有显著的关系,那么它们是可以被完全解耦的。集成总是昂贵的,有时带给你的好处也不大。声明2个限界上下文之间不存在任何关系,这样使得开发者去另外寻找简单的、专门的方法来解决问题。

⑨大泥球(Big Ball of Mud)

当我们检查已有系统时,经常会发现系统中存在混杂在一起的模型,它们之间的边界是非常模糊的。此时你应该为整个系统绘制一个边界,然后将其归纳在大泥球范围之列。在这个边界内,不要试图使用复杂的建模手段来化解问题。同时,这样的系统有可能会向其他系统蔓延,应该对此保持警觉。

划分团队和应用

  • 基于语义相关性和功能相关性识别出限界上下文
  • 基于2PTs规则特性团队康威定律思想划分团队,找到工作边界
  • 基于重用性业务变化质量属性技术选型等技术考虑划分应用边界,并持续重构

战略设计阶段产出物

  1. 利益相关人
  2. 业务期望和愿景
  3. 项目业务范围
  4. 业务流程
  5. 史诗级故事和主故事
  6. 根据核心参与者识别用例,输出每个参与者的用例图
  7. 根据语义相关性和功能相关性识别出用例主题边界,输出用例主题
  8. 根据主题之间的相关性识别限界上下文
  9. 上下文映射图或表
  10. 输出架构4+1视图
    1. 逻辑视图:限界上下文图;上下文映射图;分层架构
    2. 进程视图:限界上下文图;六边形架构;上下文映射
    3. 物理视图:六边形架构
    4. 开发视图:分层架构;代码模型
    5. 场景视图:领域场景分析;用例图

架构4+1视图举例

1. 逻辑视图

视图说明

  • 系统层次的分层架构
  • 限界上下文层次的分层架构

2. 进程视图

视图说明

  • 在绘制系统的进程视图时,不需要将每个牵涉到进程间调用的用例场景都展现出来,而是将这些参与协作的组件以抽象方式表达一个典型的全场景即可。
  • 整个进程视图非常清晰地表达了部署在不同进程之上的组件或子系统之间的协作关系,同时通过图例体现了领域驱动设计中的北向网关和南向网关与外部资源之间的协作。调用的方式是同步还是异步,也一目了然。

3. 物理视图

物理视图与进程视图虽然都以进程边界为主要的设计单元,但二者的关注点不同。进程视图是动态的,体现的是外部环境、系统各个组件在进程之间的协作方式与通信机制;物理视图是静态的,主要体现系统各个模块以及系统外部环境的部署位置与部署方式。

4. 开发视图

  • 给出代码分层结构
  • 分别体现出系统级代码分层结构和限界上下文内的代码分层结构

实体

值对象

聚合

引入聚合来划分对象之间的边界,保证边界内所有对象的一致性。其中作为主体的实体对象是聚合根。

聚合边界的识别

  • 是否多实体同生命周期,同生同死组合关系(完整性)
  • 是否单实体有独立性需求,外界需要直接和他交互(独立性)
  • 是否存在不变量
  • 是否处于一个事务中

规则

  • 外界只能和聚合根交互
  • 聚合根可以向外部传递内部实体的引用,但外部对象只能临时使用
  • 聚合根可以向外部传递值对象的副本
  • 只有聚合根才能直接通过数据库获得,其他实体通过聚合根获得

领域服务

存在跨聚合的业务场景时需要使用领域服务

分层架构

传统三层架构和领域驱动经典四层架构的对比:


经典三层架构


Eric Evans 在其经典著作《领域驱动设计》中的四层架构

  • 领域驱动四层架构多引入了Application Layer
  • 业务逻辑层改名为Domain Layer,定位更具体
  • 数据访问层的名字变为Infrastructure Layer,职责扩大

对比分析

优化后的分层架构通过引入Application Layer,使Domain Layer可以专注业务逻辑,不依赖外部环境,更内聚。
Infrastructure Layer用来分离业务和技术关注点,并不只用来请求数据。

传统三层架构升级到DDD四层架构


三层架构代码模型


把Java Beans变成充血模型的POJO,并迁移到业务逻辑层。此时POJO对应DDD的Entity和Value Object,业务逻辑层变为领域层。此时领域层和基础设施层相互依赖。


通过依赖反转和依赖注入解耦领域层到基础设施层的依赖。但多了一层基础设施的抽象层。


通过DDD的Repository概念,使基础设施抽象层进入领域层。此时用户展现层仍然直接依赖领域层,领域层对外暴露太多细节。


通过引入开放主机服务,隔离开前端对领域层的直接依赖。但此时通过Controller暴露出去的服务仍然过细。


引入应用层,封装如批量查询、请求跳转、跨领域聚合的业务编排等逻辑。通过Facade模式提供高层接口。到此迁移完毕。

整洁架构


Robert Martin 提出的整洁架构

  • 层次越靠内的组件依赖的内容越少,处于核心的 Entities 没有任何依赖。
  • 层次越靠内的组件与业务的关系越紧密,因而越不可能形成通用的框架。
  • Entities 层封装了企业业务规则,准确地讲,它应该是一个面向业务的领域模型。
  • Use Cases 层是打通内部业务与外部资源的一个通道,因而提供了输出端口(Output Port)与输入端口(Input Port),但它对外的接口展现的其实是应用逻辑,或者说是一个用例。
  • Gateways、Controllers 与 Presenters 其本质都是适配器(Adapter),用于打通应用业务逻辑与外层的框架和驱动器,实现逻辑的适配以访问外部资源。
  • 系统最外层包括框架和驱动器,负责对接外部资源,不属于系统(仅指限界上下文而言)开发的范畴,但选择这些框架和驱动器,是属于设计决策要考虑的内容。这一层的一些组件甚至与要设计的系统不处于同一个进程边界。

微服务架构


Martin Fowler的微服务架构

整幅图的架构其实蕴含了两个方向:自顶向下和由内至外。

  • 外部请求通过代表协议(Protocol)的 Resources 组件调用 Service Layer、Domain 或 Repositories,如果需要执行持久化任务,则通过 Repositories 将请求委派给 ORM,进而访问网络边界外的数据库。所谓“外部请求”可以是前端 UI 或第三方服务,而 Resource 组件就是我们通常定义的 Controller,对应于上下文映射中的开放主机服务。之所以命名为 Resources,则是因为 REST 架构是一种面向资源的架构,它将服务操作的模型抽象为资源(Resource),这是自顶向下的方向。
  • 若当前微服务需要调用外部服务(External Service),且外部服务籍由 HTTP 协议通信,就需要提供一个 HTTP Client 组件完成对外部服务的调用。为了避免当前微服务对外部服务的强依赖,又或者对客户端的强依赖,需要引入 Gateways 来隔离。事实上,这里的 Gateways 即为上下文映射中的防腐层,这是由内至外的方向。

DDD的代码模型

以电商下单业务为例:

代码结构如下所示:

ordercontext.infrastructure
    - OrderController
    - OrderMapper
    - EmailSender
    - RabbitEventBus
ordercontext.application
    - OrderAppService
    - NotificationService
    - EventBus
ordercontext.domain
    - OrderRepository
    - PlaceOrderService
    - Order
    - Notification
    - OrderConfirmed
    - NotificationComposer
    - OrderConfirmedComposer
posted in 架构设计 

机器人是什么?
是计算机。
计算机是什么?
冯诺依曼结构的计算机可以抽象为“输入”、“计算”和“输出”三部分。
计算机的智能体现在其扩展能力上。
其输入从最早的打孔纸带扩展到现在的键盘,鼠标、语音,传感器等。
其计算能力可以通过编程来扩展。
其输出从最初的显示器扩展到现在的扬声器、打印机、投影仪等。
机器人的智能体现在,对于不同输入都可以给出“智能”的输出。这个“智能”体现在其处理逻辑会随着处理其他输入而自动升级。表现出会学习的特征。
何为非真正智能的机器人?
不会主动“思考”、“学习”的机器人就不是真正智能的机器人。
如果机器人能够主动的“思考”、“学习”了之后,他和人类有什么区别?
他应该从能力上超过人类。
为了想清楚机器人是否可能最终成为真正“智能”的机器人,我们需要把机器人和人类做一下对比。
然而我认为人类其实也是遵循冯诺依曼结构工作的!
人类刚出生时对于绝大多数输入是没有反应的,即还不具备对输入的计算能力。
随着被动学习,对于固定输入的计算逻辑建立起来了。此时开始模仿,即对于给定的输入,其计算逻辑始终不变,只是越算越快,这是一个熟练过程。
然而人类刚出生也不是对所有输入都没有计算能力的,比如对于疼痛的计算,对于饥饿的计算,对于光线的计算……
随着年龄长大,逐渐体现出对于一些输入会输出嫉妒、气愤、喜欢……
这些其实是人类的基因决定的。
那基因是什么?
其实就是随着人类从远古到现代的进化中积累下来的计算逻辑。比如,人类有攀比心,其实是对于比自己好这个输入的计算,其输出是我要超过他的激素刺激。
正是因为人类天生具备七宗罪的天性基因,才让人类得以主动思考,主动学习。
所以,如果可以破解人类基因,并且给机器人输入这些基因程序,则机器人就可以和人类一样智能。在不给机器人任何输入的情况下,机器人也会基于这个“没有任何输入”的信号,并结合之前的记忆去计算然后表现出机器人在“思考”……

posted in 架构设计 

一、六大设计原则

1.1 开闭原则(Open-Closed Principle, OCP)

1.1.1 解释

开闭原则由Bertrand Meyer于1988年提出,其定义如下:

  • 一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

1.1.2 好处

好处显而易见,不修改源码的情况下进行扩展,对已有逻辑没有影响。

1.1.3 不遵守有什么坏处

随着软件规模越来越大,软件寿命越来越长,若系统设计不满足开闭原则,则维护成本变得越来越大。修一个bug,又引出10个新bug,最终变得无法维护。

1.1.4 出现违反原则的原因

职责不单一是主要影响开闭原则的原因之一;缺乏抽象也会造成开闭原则的破坏。

1.1.5 实际运用中的取舍

为了满足开闭原则,需要对系统进行抽象化设计,针对具体业务领域识别出抽象层并抽象化是开闭原则的关键。应用层基于上下文参数识别业务场景,针对不同场景选择不同的实现。

1.2 依赖反转原则(Dependency Inversion Principle, DIP)

1.2.1 解释

依赖反转原则是Robert C. Martin在1996年为“C++Reporter”所写的专栏Engineering Notebook的第三篇,后来加入到他在2002年出版的经典著作“Agile Software Development, Principles, Patterns, and Practices”一书中。依赖反转原则定义如下:

  • 抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。

因为正常的依赖顺序是,A依赖B,而为了避免依赖具体实现,引入了抽象层C,使A依赖C,B成为C的具体实现。因为继承、实现关系本身是特殊的依赖关系,所以变成被依赖的实现B反过来依赖C这个抽象层,所以叫依赖反转。

1.2.2 好处

依赖反转是实现开闭原则目标的另一个重要手段。

1.2.3 不遵守有什么坏处

少了抽象层,很多设计原则都无法做到,后果可想而知。

1.2.4 出现违反原则的原因

这个原则基本上不会违反,因为有了Spring框架提供的依赖注入。

1.3 职责单一原则(Single Responsibility Principle, SRP)

1.3.1 解释

把因相同原因而变化的东西聚在一起,把因不同原因而变化的东西分离开来。

1.3.2 好处

如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

1.3.3 不遵守有什么坏处

修改一段代码,在实现了一个feature升级的同时,影响了另一个feature的功能。

1.3.4 出现违反原则的原因

职责不单一不是从第一天写代码就开始的,往往是需求变更导致职责拆分后引起的。此时应该将一个方法拆成两个方法,一个类拆成两个类。

1.3.5 实际运用中的取舍

代码结构并不能随着需求的变更每次都做出重构,会影响团队协作效率,需要把握一个度:

  • 只有逻辑足够简单,才可以在方法级别上违反单一职责原则
  • 只有类中方法数量足够少,才可以在方法级别上违反单一职责原则
  • 在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。

1.4 里氏替换原则(Liskov Substitution Principle, LSP)

1.4.1 解释

里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出。

  • 定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
  • 定义2:所有引用父类的地方必须能透明地使用其子类的对象。

可以简单理解为:如果在软件中将一个用父类声明的对象,运行期替换成它的某个子类对象,程序没有产生任何错误和异常,则这个子类的实现是符合里氏替换原则的。

1.4.2 好处

里氏代换原则是实现开闭原则的重要方式之一。由于使用父类对象的地方都可以使用子类对象,因此在程序中尽量使用父类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。从而实现对原有系统的扩展的同时,不影响原有功能。

1.3.3 不遵守有什么坏处

如果子类集成父类后,又增加了特定的方法,那么只有使用子类来声明对象,这个对象才能使用子类特有的方法,导致后续扩展的时候,必须打开子类来修改代码,没办法替换为父类的其他子类。

1.3.4 出现违反原则的原因

代码设计初期没有考虑将来复杂性的可能,没有预留好扩展点,直接依赖具体实现,没有使用接口隔离实现。

1.3.5 实际运用中的取舍

软件架构往往是分层的,层与层之间必须使用接口隔离实现,层内的具体业务逻辑如果看不到有多种场景下使用多种实现的可能性,暂时可以直接依赖实现。当出现第二种实现时,立即使用接口隔离。
声明对象时,必须使用集成结构中适合当前场景的某一个层级的抽象来声明,不要直接使用子类来声明对象。如:当前需要一个有序可重复队列,则使用List来声明对象,因为Collection过于抽象,无法表达有序可重复的要求,直接使用ArrayList声明,则将来传入LinkedList对象会出错。

1.5 接口隔离原则(Interface Segregation Principle, ISP)

1.5.1 解释

使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。

  • 当把“接口”理解成一个类型所提供的所有方法特征的集合的时候,这就是一种逻辑上的概念,接口的划分将直接带来类型的划分。可以把接口理解成角色,一个接口只能代表一个角色,每个角色都有它特定的一个接口,此时,这个原则可以叫做“角色隔离原则”。
  • 如果把“接口”理解成狭义的特定语言的接口,那么ISP表达的意思是指接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。

1.5.2 好处

容易实现职责单一原则,客户端更容易灵活选择合适的实现,扩展性增强。

1.5.3 不遵守有什么坏处

接口太大,降低了扩展的灵活性。有时,一个接口中仅仅一个方法在不同场景有不同实现,此时增加一个全新的实现,则新的实现中有大部分代码和已有实现是代码重复的。而且客户端面对一个大接口,失去了根据场景灵活组合实现的可能性,只能定制一个全新的实现来适应当前场景,导致复用变得难以实现。

1.5.4 出现违反原则的原因

往往初期系统中接口并不大,随着需求的变化,增加了方法,或增加了角色的职责,而没有及时拆分接口。

1.5.5 实际运用中的取舍

在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。

1.6 迪米特法则(Law of Demeter, LoD)

1.6.1 解释

迪米特法则来自于1987年美国东北大学(Northeastern University)一个名为“Demeter”的研究项目。迪米特法则又称为最少知识原则(LeastKnowledge Principle, LKP),其定义如下:

  • 一个软件实体应当尽可能少地与其他实体发生相互作用。

1.6.2 好处

如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。

1.6.3 不遵守有什么坏处

不遵守迪米特法则,会使对象间过渡耦合,其结果就是对象间关系过于复杂,而破坏了扩展性和可维护性。

1.6.4 出现违反原则的原因

不要和“陌生人”说话。除以下类型的对象外,其他的都是“陌生人”。缺乏经验的开发,很容易把“陌生人”之间建立起通信关系,使得系统对象间出现不该有的耦合。

  1. 当前对象本身(this);
  2. 以参数形式传入到当前对象方法中的对象;
  3. 当前对象的成员对象;
  4. 如果当前对象的成员对象是一个集合,集合中的元素;
  5. 当前对象所创建的对象。

通常开发容易把本应属于职责边界内的逻辑暴露到外部,调用这段逻辑的客户端代码容易写的过厚,比如客户端依赖n个组件,然后组织n个组件的关系,最后计算出结果,导致客户端和过多的不相关组件通信。其实这n个组件中,很可能大部分逻辑应该封装在几个组件之中,而客户端只需要和少量几个组件通信即可。

当出现连续“.”的方法调用时,比如a.foo().bar(),此时应该警惕是否违反了迪米特法则。

1.6.5 实际运用中的取舍

实际开发中,为了避免对象间的直接耦合,往往借助“Mediator”、“Proxy”等设计模式,引入中间人,拆开耦合。

二、GoF的23个设计模式

《Design Patterns Elements of Reusable Object-Oriented Software》

作者介绍:

  • Erich Gamma博士是瑞士苏黎士国际面向对象技术软件中心的技术主管。
  • Richard Helm博士是澳大利亚悉尼IBM顾问集团公司面向对象技术公司的成员。
  • Ralph Johnson博士是Urbana-Champaign伊利诺大学计算机科学系成员。
  • John Vlissides博士是位于纽约Hawthorne的IBN托马斯J.沃森研究中心的研究人员。

2.1 创建型设计模式

创建型模式用来解决对象的创建问题,用于解耦对象的使用者和对象的创建过程。

对象的使用者通常不应该包含创建复杂对象的逻辑,否则,对象的创建逻辑的变化会导致对象的使用者需要修改代码。

  1. Factory Method Pattern (工厂方法模式)
  2. Abstract Factory Pattern (抽象工厂模式)
  3. Singleton Pattern (单例模式)
  4. Builder Pattern (建造者模式)
  5. Prototype Pattern (原型模式)

2.2 结构型设计模式

结构型模式用来解决对象间的组合或组装的问题。用于简化客户端代码逻辑,使得客户端依赖的对象可以方便的扩展而不影响客户端。或节省客户端内存。

通常我们希望客户端使用其他对象的时候代码格外精简,优雅,而不是在使用前还要用大量的代码对目标对象做处理。因为这些对目标对象做处理的逻辑很可能发生变化,而这部分逻辑并不是客户端的业务职责。

  1. Proxy Pattern (代理模式)
  2. Adapter Pattern (适配器模式)
  3. Decorator Pattern (装饰器模式)
  4. Facade Pattern (门面模式)
  5. Composition Pattern (组合模式)
  6. Flyweight Pattern (享元模式)
  7. Bridge Pattern (桥接模式)

3.3 行为型设计模式

行为型模式解决的是对象之间交互方式的问题。

不适当的交互方式很容易产生不必要的耦合,使日后扩展受阻,不利于代码复用。

  1. Observer Pattern (观察者模式)
  2. Template Method Pattern (模板方法模式)
  3. Strategy Pattern (策略模式)
  4. Chain of Responsibility Pattern (责任链模式)
  5. State Pattern (状态模式)
  6. Iterator Pattern (迭代器模式)
  7. Visitor Pattern (访问者模式)
  8. Memento Pattern (备忘录模式)
  9. Command Pattern (命令模式)
  10. Interpreter Pattern (解释器模式)
  11. Mediator Pattern (中介模式)

三、The 12-Factor App

Originally devised by developers at Heroku, this methodology identifies key principles shared by successful applications.

The Twelve-Factor App

3.1 Single Codebase (基准代码)

微服务之间不共享代码:多个微服务可以共享一个project,但每个微服务要有一个完整的代码仓库,且微服务之间没有公共的底层代码。

避免一个微服务修改代码影响其他微服务。

3.2 Dependencies (依赖)

显式声明依赖关系

要显式声明依赖,且打包后使微服务自包含;不要假设微服务之外一定存在某个服务而不加依赖声明就去依赖。

避免微服务在不同环境间部署产生差异;使新人上手容易,拉出代码,使用构建工具打包后即可运行。

3.3 Config (配置)

在环境中存储配置

微服务环境相关配置外置。微服务可以从外部拉取对应环境配置,使得微服务在构建之后在不同环境间切换时不用修改构建结果。

避免微服务在不同环境间部署时需要修改源代码引入意外bug,或忘记修改代码中配置导致部署失败。

3.4 Backing Services (后端服务)

把后端服务当做附加资源

微服务依赖的所有第三方服务都应该被视作一个资源,应该使用地址标识该资源,通过防腐层和依赖反转隔离微服务对外部资源的直接依赖,使得在不同环境只需要修改对应资源地址而不用修改代码即可和第三方服务对接,及时第三方服务的技术实现发生了变化微服务也不用修改代码。

避免在不同环境间部署微服务时,因为第三方服务地址不同或实现不同,版本不同而需要修改代码。

3.5 Build, Release and Run (构建,发布,运行)

严格分离构建和运行。

微服务应该严格区分构建,发布和运行三个阶段。构建是从代码变成二进制的过程,和环境无关,而且不可再变;发布是给二进制加上对应环境的配置,在不同环境长需要使用相同的二进制和不同的环境配置重复发布;运行是在对应环境上启动进程。每个环境有各自的职责,禁止在运行阶段直接修改二进制。

避免微服务在上线过程中不同阶段引入不可预知bug。

3.6 Processes (进程)

以一个或多个无状态进程运行应用

微服务的每个实例都应该状态外置,存在缓存或DB中,使微服务实例自身无状态。可以任意水平扩容,负载均衡或代理可以把请求给到任意实例上。

避免无法水平扩容,避免单实例故障后丢失状态。

3.7 Port Binding (端口绑定)

通过端口绑定来提供服务

微服务不应该依赖任何已经存在的服务来对外提供网络服务,应该通过依赖服务提供程序形成自包含,微服务本身就可以对外提供网络服务。

避免网络服务提供能力依赖已存在的其他服务,导致如果其他服务不存在就无法提供网络服务。

3.8 Concurrency (并发)

通过进程模型进行扩展

每个微服务职责单一,不同微服务可根据流量分别水平扩容。微服务不要自己做进程管理。

避免导致微服务扩容的因素过多,造成浪费

3.9 Disposable (易处理)

快速启动和优雅终止可最大化健壮性

微服务应该追求最小启动时间和优雅的终止服务。优雅的终止服务意味着微服务接收到终止信号后首先关闭网络端口不再接收新的请求,而后继续处理已经接收的请求,最后终止服务。

避免因启动时间太长而影响对突发问题的响应能力。避免在重新调度微服务时由于不优雅终止服务而影响业务。

3.10 Dev / Prod Parity (开发环境和线上环境等价)

尽可能的保持开发、预发布、线上环境相同

微服务应该反对在不同环境间使用不同的后端服务。

避免不同的后端服务突然出现的不兼容,导致测试、预发布都正常的代码在线上出现问题。

3.11 Logs (日志)

把日志当做事件流

微服务的日志应当被看作事件流,微服务不要自己管理日志存储,日志应当被日志汇聚底层基础设施统一的处理。

避免登录主机查看日志的麻烦,避免单实例故障导致日志丢失。避免历史事件的不可查询。

3.12 Admin Processes (管理进程)

后台管理任务当做一次性进程运行

后台管理任务的代码随正常业务代码一同升级交付,后台管理任务使用不同的入口,一次性执行,但是基于正常业务的进程执行,两者使用相同的权限

避免管理任务的代码落后于业务代码导致无法使用,避免管理任务的权限超出正常业务进程,导致故障。

四、微服务设计关注点

4.1 系统分层

  • 后端微服务使用领域模型驱动开发模式
  • 使用六边形或整洁架构作为代码的分层架构依据

4.2 持久层设计

  • 禁止全表扫描操作,sql执行要有监控,持续优化慢sql
  • 禁止把大量日志写入关系型数据库

根据业务逻辑复杂度,对性能的诉求,可以考虑是用CQRS模式——即读写分离模式。读写分离模式分为数据库层面的读写分离——即主库用来写,从库用来读,和模型层面读写分离。模型层面读写分离针对写场景和读场景可以完全分开设计,模型可以完全不同,以便基于写和读场景的特定需求定制优化,数据库表结构也可以完全不同,甚至写和读场景使用完全不同的数据库产品,如写场景使用RDBMS,读场景使用NoSQL。

4.3 远程通信

  • 服务注册、发现、负载均衡使用API Gateway,系统间通过API Gateway通信
  • 系统暴露RestfulAPI给其他系统

4.4 数据一致性

  • 数据变更要同源,禁止同一个数据在两处变更然后同步
  • 分布式系统随时可能出现故障而使一次请求失败,对于系统间通信场景,系统设计时要考虑各个环节异常的处理,如:
    • 对方无法正常响应
    • 对方已经接到请求,但是没来得及返回就挂了
    • 接到对方返回,没来的及处理自己挂了
    • 数据已经正常持久化,但是没能及时同步到其他副本,导致从其他副本读数据出现不一致

4.5 性能

  • 消耗资源的服务要以非阻塞方式对外提供,接到请求先持久化,然后立即响应,任务执行完通过回调或消息通知方式通知调用方
  • 禁止轮询的方式读取其他系统状态变化,也禁止通过api的方式提供资源状态变化查询服务,应该使用消息通知的方式
  • 消耗资源较多的api要预估资源占用情况,不要一次调用就把内存耗尽,要有防御性设计,给运维留出余地
  • api设计要支持水平扩展,禁止绑定单台服务器处理任务,任务量上来后要可以通过扩容来降低单机压力
  • 写日志等和主业务逻辑没有强依赖的步骤要做成异步的
  • 对关键系统的写入操作要考虑是否有可能在某些条件下产生写入量突增,要做好预防,以免把关键系统写挂
  • 上线新功能前要预估新功能对资源的需求增量,提前做好扩容准备

4.6 依赖原则

  • 依赖ID,或枚举,不允许依赖名称

4.7 部署规范

  • 每个组件一个IP,统一端口,不允许多个组件合部署在一个IP上

4.8 架构设计交付件

  • 系统关系图
  • 组件关系图
  • 业务逻辑图
  • 部署架构图

五、微服务设计成熟度衡量标准

分类 度量项 参考文档
API
API Rest成熟度
(使用Spring-HATEOAS提供可导航的API为4级最高级)
API 客户端代码自动生成
API 通过Swagger输出API文档
服务注册 服务自动注册发现
服务治理 接入API Gateway或Service mesh
配置 使用分布式动态配置
分布式事务 利用消息总线实现分布式事务最终一致性