Luna Tech

Tutorials For Dummies.

Clean Architecture

2021-06-13



0. 前言

Reference 1: Clean Architecture - Jason @SSW

Reference 2: Clean Architecture - Pluralsight - Matthew Renze

最近看了一些关于 Software Architecture 相关的课程和讲座,今天简单总结一下我学到的内容。


1. Database-centric vs Domain-centric

Database-centric

传统的软件设计是自下而上的 database-centric approach,这种 approach 分为三个 layer:

  1. UI
  2. Business Logic
  3. Data Access

顾名思义,database-centric 就是指其他 layer 都是依赖于 data access 这个 layer 的。

Domain-centric

也被称为 DDD (Domain-driven Design),把一个 solution 分为 4-5 个 layer:

  1. core
    • domain layer
    • application layer
  2. infrastructure (有人分为 persistence + infrastructure, 有人合在一起,总之就是存数据的那个 layer)
  3. presentation: 就是 UI

source: ref 1

Domain-centric 的特点

因为 domain + application 是核心,infrastructure 和 presentation 都依赖于 core,所以 framework 换起来更轻松,只要 business case 不变,core 可以保持相对稳定。


2. DDD 的每个 layer 都包含什么呢?

PS:只是可能包含的内容,而不是必须包含的内容,要根据你的 business case 来看哦~

1. Domain Layer

  1. Entities
  2. Value objects
  3. Enumerations
  4. Logic
  5. Exceptions

2. Application Layer

  1. Interfaces
  2. Models
  3. Logic
  4. Commands/Queries
  5. Validators
  6. Exceptions

3. Infrastructure Layer

  1. Persistence
  2. Identity
  3. File System
  4. System Clock
  5. API Clients

4. Presentation Layer

  1. SPA - Angular, React, Vue, Blazor
  2. Web API
  3. Razor Pages
  4. MVC
  5. Web Forms

3. 如何实现 DDD 呢?

法宝 1:User Story + UML

既然是 domain-driven,我们得先搞清楚这个 application 到底是干嘛的对不?

所以 user story/use case 必须清晰地定义出来,然后借助 class diagram 等工具来搞清楚 domain entity 有哪些。

法宝 2:Abstraction + Interface

接着我们就从内向外开始写代码,牢记内圈是核心,外圈依赖内圈,所以内圈所需要的一些 logic 可以用 interface + abstraction 来定义,然后外圈去 implement。

法宝 3:Dependency Injection + Inversion of control

既然外圈包含着具体的implementation,那么我们就要在内圈 inject 这些 implementation,这样在 runtime 的时候就可以用到具体的实现了。比如把 dbcontext inject 到 application layer。

每个 layer 的 dependency injection 可以放在一个单独的 class 里面,在 startup file 里面就只需要 add 这个 layer 的 DI。

比如 infrastructure layer 有一个专门的 dependencyInjection class,里面包含 DbContext, IdentityService, DateTimeService 等等,我们在 startup class 里面就只需要 addInfrastructure,而不是单独添加每个 dependency。

法宝 4:Cross-cutting concern 放在一起

这些 common class 需要整理到每个 layer 的单独文件夹里面,比如每个 layer 都有各自的 exception handling,但是同一个 layer 的 exception handling 应该是放在一个地方的。

又比如 audit 和 timezone 这种,跟 domain 其实没太大关系,就是一个通用的、常见的 concern,那么我们也应该 exact 出来,单独给它一个 abstraction/interface class.

法宝 5:CQRS 读写分离架构 + Mediator Pattern

CQRS 的全称是 Command Query Responsibility Segregation,它的核心在于把 application layer 的读写分开,read request 被称为 query,write request 被称为 command。

这个架构和 Mediator pattern 配套使用体验更加棒,在这里就不展开了。

Mediator pattern is a behavioural software design pattern that helps you to build simpler code by making all components communicate via a “mediator” object, instead of directly with each other.

法宝 6:Microservice

微服务适合用于优化成本,实现 high conhesion and low coupling(高内聚和低耦合),每个 service 都是独立的,可以用不同的 technology 来实现。

不过根据康威定律(Conway’s Law),系统设计本质上反映了企业的组织结构,假如公司是 top-down 的结构,就不太适合用 microservice了,bottom-up 的公司可能更适合用这个模式。

If your organization and team are not set up in a manner that can communicate and coordinate effectively in the way that microservices entail, your organizational structure will likely experience significant resistance to creating microservice architectures.


4. 结语

DDD 的目的是让我们的 solution 更加 flexible, scalable, easy to maintain and change, free of framework choices.

我学了这个话题相关的内容之后有以下几点感悟:

  1. 我们写软件最重要的是实现 business case 和 business logic,而 logic 和 framework 之间的关联度越低越好,infrastructure 和 presentation layer 都是非常可能被替换的,比如 sqlserver 改 cosmos,angular 改 react,我们在 design 的时候把 logic 跟这两个 layer 分开,就能让 core logic 的那部分代码拥有更加长的生命周期。
  2. 另外,从 developer 的角度来看,用 DDD 来设计和组织代码,能够让 solution 看起来更加整洁(点题!Clean architecture!),你要改动或者加新功能的时候不用担心把另外一个 layer 搞坏,假如你是在改 common class,那你就能从 code file 的所属 folder hierarchy 了解到自己所做的改动可能造成的影响。
  3. 由于每个 layer 都是相对独立,根据 interface 来进行交互的,我们可以更容易地写 test case + 用一些 mock,从而保证系统的稳定性。

最后推荐 Reference 1: Clean Architecture - Jason @SSW 里面用到的一个 clean architecture template,大家有空的话也可以听一下这个视频,我觉得讲的挺不错的~