Clean Architecture是什么?
1. 什么是Clean Architecture?
Clean Architecture(简称CA)是由Robert C. Martin 在2017年所提出的一种软件架构设计理念。CA主张将软件系统分层,每一层都有着明确的职责和依赖关系,从而实现松耦合、易扩展、易维护等目的。
1.1 CA的层次结构
CA的层次结构通常分为以下五层:
1. Entity(实体层):承载业务逻辑的核心,不依赖于其他层。
2. Use case(用例层):实现业务规则与流程,负责处理应用程序逻辑。
3. Interface adapters(接口适配层):将数据从最外层的格式转换为更适合内部使用的格式,并进行必要的数据验证和转换。
4. Frameworks & drivers(框架和驱动层):包含了框架和工具,如:数据库、Web框架、UI框架等。
5. Plugins(插件层):可选层,用于扩展框架和驱动层的功能。
1.2 CA的原则
CA遵循以下原则:
1. 独立于外部环境:内部架构应该能够相对独立地发展,不受外部框架和工具的影响。
2. 可测试性:所有层次的代码都应可隔离测试。
3. 独立于UI:UI只是整个系统的一个输入输出设备,不应该影响系统的核心业务逻辑。
4. 独立于数据库:所有的业务规则和流程应该不依赖某种数据库技术。
5. 独立于框架:框架和工具只是为了支持系统的开发过程,不能成为系统业务的核心。
2. CA在Node中的实现
使用Node实现CA,可以采用Express作为Web框架,并使用TypeORM作为ORM框架,同时,要定义好各个层次之间的依赖关系,遵循“依赖倒置原则”和“接口隔离原则”。
2.1 工程结构
下面是一个基于Express和TypeORM的CA工程结构的示例:
```
├── src
│ ├── entity
│ ├── usecase
│ ├── adapter
│ ├── framework
│ │ ├── database
│ │ └── server
│ └── plugin
├── test
```
其中:
1. entity目录用于存放实体层相关的代码。
2. usecase目录用于存放用例层相关的代码。
3. adapter目录用于存放接口适配层相关的代码。
4. framework目录用于存放框架和驱动层相关的代码,如Node本身、Express框架、TypeORM框架、数据库等。
5. plugin目录用于存放实现插件层的代码。
2.2 设计实体层
实体层是整个系统的核心,负责承载业务逻辑。在设计实体层时,需要遵循以下原则:
1. 定义良好的数据结构,包括属性和方法。
2. 将实体抽象成一个独立的模块,不依赖于其他层次的代码。
3. 将实体的生命周期和状态转换逻辑封装到实体中。
以下是一个User实体的示例代码:
// src/entity/user.js
class User {
constructor(id = null, name = '', email = '') {
this.id = id;
this.name = name;
this.email = email;
}
get name() {
return this._name;
}
set name(value) {
if (!value) {
throw new Error('name cannot be empty');
}
this._name = value.trim();
}
get email() {
return this._email;
}
set email(value) {
if (!value) {
throw new Error('email cannot be empty');
}
this._email = value;
}
static create(name, email) {
return new User(null, name, email);
}
update(name, email) {
this.name = name;
this.email = email;
}
}
module.exports = User;
2.3 设计用例层
用例层是实现业务规则和流程的地方。用例是一个包含输入、处理和输出的三元组,对于每一个输入,都必须有一个相应的输出。
在实现用例时,需要遵循以下原则:
1. 定义良好的接口规范。
2. 不依赖于具体的实现方式。
3. 处理业务逻辑和流程,确保数据的有效性和完整性。
4. 保证用例及其方法级别的单元测试。
以下是一个创建用户的用例的示例代码:
// src/usecase/create-user.js
class CreateUserUseCase {
constructor(userRepository) {
this.userRepository = userRepository;
}
async execute(name, email) {
const user = User.create(name, email);
await this.userRepository.persist(user);
return user;
}
}
module.exports = CreateUserUseCase;
2.4 设计接口适配层
接口适配层是用于将数据从最外层的格式转换为内部使用的格式,从而实现数据验证和转换的地方。在设计接口适配层时,需要遵循以下原则:
1. 隔离最外层和内部层次的依赖。
2. 定义良好的接口规范,可以是Web API、MQ、RPC等。
3. 正确转换并验证输入参数、输出参数以及结果数据。
以下是一个创建用户的Controller的示例代码:
// src/adapter/create-user-controller.js
class CreateUserController {
constructor(createUserUseCase) {
this.createUserUseCase = createUserUseCase;
}
async handle(req, res) {
try {
const { name, email } = req.body;
const user = await this.createUserUseCase.execute(name, email);
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
}
}
module.exports = CreateUserController;
2.5 设计框架和驱动层
框架和驱动层是指需要用到的框架和工具,包括但不限于Web框架、ORM框架等。在设计框架和驱动层时,需要遵循以下原则:
1. 将外部框架和工具隐藏在API之后,避免内部层次依赖于外部框架,提高代码的可移植性和可测试性。
2. 只在最外层代码使用框架和工具。
以下是一个Express应用的示例代码:
// src/framework/server.js
const express = require('express');
const bodyParser = require('body-parser');
async function createServer() {
const app = express();
app.use(bodyParser.json());
app.post('/users', createUserController.handle.bind(createUserController));
return app;
}
module.exports = createServer;
2.6 实现插件层
插件层是一个可选层,用于扩展系统的功能,如:缓存、消息队列、日志、监控等。在实现插件层时,需要遵循以下原则:
1. 定义良好的接口规范。
2. 实现插件和系统之间的松耦合关系。
3. 确认插件与系统之间的依赖性和对于系统的影响。
4. 编写单元测试和集成测试。
以下是一个Redis缓存插件的示例代码:
// src/plugin/redis-cache.js
const redis = require('redis');
class RedisCache {
constructor() {
this.client = redis.createClient();
}
async get(key) {
const value = await this.client.getAsync(key);
return value != null ? JSON.parse(value) : null;
}
async set(key, value, ttl) {
const payload = JSON.stringify(value);
if (ttl) {
await this.client.setAsync(key, payload, 'EX', ttl);
} else {
await this.client.setAsync(key, payload);
}
}
}
module.exports = RedisCache;
3. 总结
Node实现Clean Architecture,需要遵循一定的软件设计和编程原则,将系统分层(Entity、Use case、Interface Adapters、Frameworks & Drivers、Plugins),保证各个层次的松耦合和易扩展、易维护性。同时,还需要使用适合的框架和工具,如Express、TypeORM等,并编写良好的接口规范和单元测试、集成测试,最终实现一个高质量的、可测试、可扩展和易维护的Node应用程序。