整洁架构
整洁架构(Clean Architecture)由 Robert C. Martin(Uncle Bob)提出,是一种软件架构理念,旨在通过分层和依赖规则,使系统更易于理解、维护和测试。
什么是整洁架构?
整洁架构的核心思想是将软件系统组织为同心圆层次,内层包含核心业务逻辑,外层包含框架、驱动和接口。依赖关系必须向内指向核心领域。
┌─────────────────────────────────────────────────────────────────────────────┐
│ 整洁架构层次图 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ │
│ │ 实体层 │ ← 核心业务规则 │
│ │ (Entities)│ │
│ └─────┬─────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ 用例层 │ ← 应用业务规则 │
│ │ (Use Cases) │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌─────────────────┴─────────────────┐ │
│ │ 接口适配器层 │ ← 适配器转换 │
│ │ (Interface Adapters) │ │
│ └─────────────────┬─────────────────┘ │
│ │ │
│ ┌───────────────────────────┴───────────────────────────┐ │
│ │ 框架与驱动层 │ ← 外部工具 │
│ │ (Frameworks & Drivers) │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ 依赖规则:内层不依赖外层,外层依赖内层 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
四层架构详解
第一层:实体(Entities)
实体封装了企业级的核心业务规则,是最内层,不依赖任何外部框架。
// 实体 - 纯业务对象,无框架依赖
public class User {
private final UserId id;
private Username username;
private Email email;
private Password password;
private UserStatus status;
private final LocalDateTime createdAt;
private User(UserId id, Username username, Email email,
Password password, LocalDateTime createdAt) {
this.id = id;
this.username = username;
this.email = email;
this.password = password;
this.status = UserStatus.ACTIVE;
this.createdAt = createdAt;
}
public static User create(Username username, Email email, Password password) {
return new User(
UserId.generate(),
username,
email,
password,
LocalDateTime.now()
);
}
public void changeEmail(Email newEmail) {
this.email = newEmail;
}
public boolean verifyPassword(String rawPassword) {
return password.matches(rawPassword);
}
public void deactivate() {
if (this.status == UserStatus.INACTIVE) {
throw new IllegalStateException("User is already inactive");
}
this.status = UserStatus.INACTIVE;
}
// Getters only - no setters for immutability
public UserId getId() { return id; }
public Username getUsername() { return username; }
public Email getEmail() { return email; }
public UserStatus getStatus() { return status; }
}
// 值对象
public class Email {
private final String value;
public Email(String value) {
if (!isValid(value)) {
throw new IllegalArgumentException("Invalid email format");
}
this.value = value;
}
private boolean isValid(String email) {
return email != null &&
email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
}
public String getValue() { return value; }
}
public class UserId {
private final UUID value;
private UserId(UUID value) {
this.value = value;
}
public static UserId generate() {
return new UserId(UUID.randomUUID());
}
public static UserId fromString(String id) {
return new UserId(UUID.fromString(id));
}
public String toString() { return value.toString(); }
}
第二层:用例(Use Cases)
用例层包含应用特定的业务规则,编排实体的业务逻辑。
// 用例输入端口(接口)
public interface RegisterUserUseCase {
RegisterUserResult register(RegisterUserInput input);
}
// 用例输入数据
public class RegisterUserInput {
private final String username;
private final String email;
private final String password;
public RegisterUserInput(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
// Getters...
}
// 用例输出数据
public class RegisterUserResult {
private final boolean success;
private final String userId;
private final String errorMessage;
public static RegisterUserResult success(String userId) {
return new RegisterUserResult(true, userId, null);
}
public static RegisterUserResult failure(String errorMessage) {
return new RegisterUserResult(false, null, errorMessage);
}
// Constructor and getters...
}
// 用例实现
public class RegisterUserUseCaseImpl implements RegisterUserUseCase {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;
public RegisterUserUseCaseImpl(UserRepository userRepository,
PasswordEncoder passwordEncoder,
EmailService emailService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.emailService = emailService;
}
@Override
public RegisterUserResult register(RegisterUserInput input) {
// 检查用户名是否已存在
if (userRepository.existsByUsername(new Username(input.getUsername()))) {
return RegisterUserResult.failure("Username already exists");
}
// 检查邮箱是否已存在
if (userRepository.existsByEmail(new Email(input.getEmail()))) {
return RegisterUserResult.failure("Email already registered");
}
try {
// 创建用户实体
User user = User.create(
new Username(input.getUsername()),
new Email(input.getEmail()),
passwordEncoder.encode(input.getPassword())
);
// 保存用户
userRepository.save(user);
// 发送欢迎邮件
emailService.sendWelcomeEmail(user.getEmail().getValue());
return RegisterUserResult.success(user.getId().toString());
} catch (Exception e) {
return RegisterUserResult.failure("Registration failed: " + e.getMessage());
}
}
}
第三层:接口适配器(Interface Adapters)
适配器将数据从外层格式转换为内层格式,反之亦然。
// 控制器适配器
@RestController
@RequestMapping("/api/users")
public class UserController {
private final RegisterUserUseCase registerUserUseCase;
public UserController(RegisterUserUseCase registerUserUseCase) {
this.registerUserUseCase = registerUserUseCase;
}
@PostMapping("/register")
public ResponseEntity<RegisterUserResponse> register(
@RequestBody @Valid RegisterUserRequest request) {
// 将请求转换为用例输入
RegisterUserInput input = new RegisterUserInput(
request.getUsername(),
request.getEmail(),
request.getPassword()
);
// 执行用例
RegisterUserResult result = registerUserUseCase.register(input);
// 将用例输出转换为响应
if (result.isSuccess()) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(new RegisterUserResponse(result.getUserId()));
} else {
return ResponseEntity.badRequest()
.body(new RegisterUserResponse(null, result.getErrorMessage()));
}
}
}
// 请求 DTO
public class RegisterUserRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50)
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
private String email;
@NotBlank(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
// Getters and setters...
}
// 响应 DTO
public class RegisterUserResponse {
private final String userId;
private final String errorMessage;
public RegisterUserResponse(String userId) {
this(userId, null);
}
public RegisterUserResponse(String userId, String errorMessage) {
this.userId = userId;
this.errorMessage = errorMessage;
}
// Getters...
}
第四层:框架与驱动(Frameworks & Drivers)
最外层包含框架、工具和驱动,如数据库、Web 框架、外部服务等。
// 数据库适配器
@Repository
public class JpaUserRepository implements UserRepository {
private final SpringDataUserRepository jpaRepository;
public JpaUserRepository(SpringDataUserRepository jpaRepository) {
this.jpaRepository = jpaRepository;
}
@Override
public User save(User user) {
UserEntity entity = toEntity(user);
UserEntity saved = jpaRepository.save(entity);
return toDomain(saved);
}
@Override
public Optional<User> findById(UserId id) {
return jpaRepository.findById(UUID.fromString(id.toString()))
.map(this::toDomain);
}
@Override
public boolean existsByUsername(Username username) {
return jpaRepository.existsByUsername(username.getValue());
}
@Override
public boolean existsByEmail(Email email) {
return jpaRepository.existsByEmail(email.getValue());
}
private UserEntity toEntity(User user) {
UserEntity entity = new UserEntity();
entity.setId(UUID.fromString(user.getId().toString()));
entity.setUsername(user.getUsername().getValue());
entity.setEmail(user.getEmail().getValue());
entity.setStatus(user.getStatus().name());
entity.setCreatedAt(user.getCreatedAt());
return entity;
}
private User toDomain(UserEntity entity) {
// Convert entity back to domain object
return User.reconstitute(
UserId.fromString(entity.getId().toString()),
new Username(entity.getUsername()),
new Email(entity.getEmail()),
UserStatus.valueOf(entity.getStatus()),
entity.getCreatedAt()
);
}
}
// 邮件服务适配器
@Component
public class SmtpEmailService implements EmailService {
private final JavaMailSender mailSender;
public SmtpEmailService(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
@Override
public void sendWelcomeEmail(String to) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject("Welcome!");
message.setText("Thank you for registering.");
mailSender.send(message);
}
}
依赖规则
整洁架构的核心是依赖规则:源代码依赖只能向内指向核心。
正确:
框架层 ──> 适配器层 ──> 用例层 ──> 实体层
错误:
实体层 ──> 用例层 ──> 适配器层 ──> 框架层 (X)
代码组织结构
src/
├── domain/ # 实体层
│ ├── entity/
│ │ ├── User.java
│ │ ├── Order.java
│ │ └── valueobject/
│ │ ├── Email.java
│ │ ├── UserId.java
│ │ └── Money.java
│ ├── repository/
│ │ ├── UserRepository.java
│ │ └── OrderRepository.java
│ └── service/
│ └── DomainService.java
├── usecase/ # 用例层
│ ├── port/
│ │ ├── in/ # 输入端口
│ │ │ ├── RegisterUserUseCase.java
│ │ │ └── CreateOrderUseCase.java
│ │ └── out/ # 输出端口
│ │ ├── EmailService.java
│ │ └── PaymentGateway.java
│ └── impl/
│ ├── RegisterUserUseCaseImpl.java
│ └── CreateOrderUseCaseImpl.java
├── adapter/ # 适配器层
│ ├── in/
│ │ ├── web/
│ │ │ ├── UserController.java
│ │ │ └── dto/
│ │ └── messaging/
│ └── out/
│ ├── persistence/
│ │ ├── JpaUserRepository.java
│ │ └── entity/
│ ├── email/
│ └── payment/
└── infrastructure/ # 框架与驱动层
├── config/
└── security/
整洁架构的优势
| 优势 | 说明 |
|---|---|
| 独立性 | 业务逻辑独立于框架、UI 和数据库 |
| 可测试性 | 核心业务逻辑可以独立测试 |
| 可维护性 | 清晰的层次使代码易于理解和修改 |
| 灵活性 | 可以轻松替换外部框架和技术 |
总结
整洁架构通过严格的分层和依赖规则,确保业务逻辑的核心地位。虽然初始成本较高,但对于长期维护的复杂系统来说,这种架构投资是值得的。
"好的架构让系统易于改变,整洁架构通过依赖规则确保这一点。" —— Robert C. Martin