今天在学习一个项目时,教程推荐先看 types.ts,给的理由:后面的模块都依赖这个公共类型定义。刚开始看觉得很合理,但仔细一想,又觉得这个理由过于浅显。
从依赖关系上来讲,这句话并没什么毛病,types.ts 确实是其他模块都要依赖的部分,任何项目都会有类似的依赖抽象:
底层 ← 业务层 ← 页面
依赖只能向下。
所以:
types.ts ← services ← components ← pages
这属于现象层(What)。
但再进一步思考,types.ts 更应该理解为是项目设计层面在代码中的体现,这是我们需要先看它的本质原因。
Type-First Design 的概念相信大家也有所耳闻:即在实现任何逻辑前,需要用类型系统精确描述 "这个系统是什么?边界在哪里?以及数据长什么样" 。而不是先蒙头输出代码,再反过来确认它有什么类型。
在 Design 阶段,我们一般需要从三个方面出发:
领域建模(Domain Model)
它来解决整个系统最基础的问题,这个系统到底有什么?
比如我们要做一个编辑器。那首先要思考的就是,在编辑器中,需要哪些核心要素?比如:Editor、Extension、Command 等等。注意,这些不是 TypeScript,而是在编辑器这个业务领域真正存在的概念。换句话说,领域建模就是在建立整个系统的词汇表,以后整个项目都会围绕这些名字进行交流。它最终会产出一组稳定的业务概念,这将贯穿整个产品。(领域建模是 DDD-领域驱动设计 最核心的一部分)
架构设计(Architecture)
有了领域模型之后,如何把这些业务概念组织成一个系统?这是架构设计需要解决的问题。它将决定:系统如何组织、模块如何依赖、职责如何划分。最终产出一份系统结构表或者"骨架图"。
这一部分最容易被搞混淆,我们需要先搞清楚一件事情:架构一定是在更高一个层次来看整个系统的。直白点来说:软件架构其实就是软件系统的骨架。
架构是服务于业务的,但对于架构以及架构的组成来说是复杂多样化的。
对于架构来说,是存在不同视角或者不同域的,通常是:
- 业务架构:业务流程、业务能力、业务组织等等
- 应用架构:应用模块、服务划分、接口等
- 数据架构:数据模型、存储、数据流等
- 技术架构:基础设施、中间件、部署、技术选型
这是横向视角 看到的架构类型或者架构域。
那从纵向视角 来看,也就是从架构的描述结构来看,可以套用四个通用维度:组成部分 、组织结构 、约束关系 和演化方式 。我们可以把这四个部分理解为架构描述的通用语言。
- 组成部分:比如在业务架构下,有 User / Order / Payment 等模块;在技术架构下,有 UI / DB / API 等模块。
- 组织结构:不同的模块之间如何进行组合?比如业务流程编排、组织映射;又比如模块之间分层、依赖及通信。
- 约束关系:业务上会有业务规则、合规约束;技术上有技术选型和性能约束等等。
- 演化方式:软件不是一成不变的,业务逻辑会变、技术会升级。
因此,整个架构可以看作是一个矩阵:架构 = 不同架构域 x 四个描述维度。每一个架构域都需要用这四个维度来完整描述它的静态结构和动态演变。
虽然 types.ts 本身不直接话架构图,但高质量的类型设计往往会暗含模块边界和依赖方向------例如通过接口隔离、泛型约束来固化分层规则,这本身就是架构思维在代码层面的映射。
markdown
架构视角(横向)
┌──────────────────────────────────────┐
│ 业务架构 / 应用架构 / 技术架构 / 数据架构 │
└──────────────────────────────────────┘
↓
每一个架构都需要用四维度描述
↓
┌──────────────────────────────┐
│ 组成部分 / 组织结构 / 约束 / 演化 │
└──────────────────────────────┘
契约设计(Contract)
在领域模型和架构都已确定的情况下,接下来我们就要进行模块之间的精确契约,可以从三个层面来看:
- 业务契约:每个操作的前置条件、后置条件以及业务不变量
- 技术契约:接口签名、数据格式、通信协议以及错误处理
- 质量契约:性能、安全、可用性、兼容性等非功能契约(它更多体现在架构和实现中)
契约设计的产物是可以直接知道编码和测试的可验证约定,可以看出来它起到一个承上启下的作用。上可以验证领域模型和架构设计是否可行。下可以直接指导编码、测试、甚至团队协作边界。
如果说架构设计是负责定骨架和画边界 ,那契约设计就是负责填血肉和定细节。而领域设计、架构设计以及契约设计三者之间的关系,可以大致理解为:
领域建模 ────────────────┐
│ │
│ 提供业务概念和规则 │ 贯穿始终的
▼ │ 业务约束
架构设计 ────────────────┤
│ │
│ 提供模块划分和骨架 │
▼ ▼
契约设计 ◄──────── 业务契约 + 非功能契约
│
│ 为每个模块定义精确的:
│ - 业务契约(前置/后置/不变量)
│ - 技术接口(方法、参数、返回值)
│ - 质量承诺(性能、安全、兼容性)
▼
实现 + 测试(可并行进行)
types.ts 主要会承载 领域模型 和契约两部分的设计 ,就像文章开头说的,它是设计在代码中的表达。但它不直接表达架构,因为架构的多元化的且主要体现在目录结构、模块划分、依赖方向等整个系统组织上。
综上,我们又回到一个很基础的问题上------软件设计。软件设计从来都不是单一层级的抽象,由上可知对于一个软件而言是需要分层思考的,是从概念、结构、交互逐步演进的。
学习和理解优秀的项目源码时,也可以按照:先看领域模型 → 再看架构 → 再看契约 → 最后看实现 这个顺序进行。在实际操作中,可以先通读 types.ts 快速识别核心实体和它们之间的关系(领域模型),然后对照目录结构和模块划分理解架构骨架,之后再回过头细看 types.ts 中的接口定义、泛型约束等细节(契约)。
最后,对于整个软件从设计到实现,我们就可以统一为以下层次:
vbnet
业务目标(要解决什么问题)
│
▼
领域模型(What) ------ 定义系统有哪些核心概念(如 Editor、Extension、Command)
│
▼
架构设计(Structure) ------ 决定系统如何组织(插件式、分层、模块划分、依赖规则)
│
▼
契约设计(How) ------ 定义模块间如何交互(接口、API、协议)
│
▼
代码表达(Representation) ------ 用 types.ts / interface 等将领域模型与契约写成代码
│
▼
实现(Implementation) ------ 用 class / function 兑现设计
│
▼
运行(Execution) ------ 系统真正工作