引言
“架构的目标是最大化程序员的生产力,最小化系统的维护成本。”
—— Robert C. Martin (Uncle Bob)
随着业务发展,系统复杂度不断增加,我们常常面临:
- 业务逻辑分散,难以定位
- 框架依赖严重,难以替换
- 测试困难,覆盖率低
- 新人上手慢,代码理解成本高
整洁架构(Clean Architecture) 通过关注点分离和依赖规则,有效解决了这些问题。
一、什么是整洁架构
架构分层
graph TB
subgraph Entities["Entities(企业业务规则)"]
E1[业务对象]
E2[业务规则]
end
subgraph UseCases["Use Cases(应用业务规则)"]
U1[用例 1]
U2[用例 2]
U3[用例 3]
end
subgraph InterfaceAdapters["Interface Adapters(接口适配器)"]
I1[Controller]
I2[Presenter]
I3[Gateway Impl]
end
subgraph Frameworks["Frameworks & Drivers(框架与驱动)"]
F1[Web 框架]
F2[数据库]
F3[外部接口]
end
Frameworks --> InterfaceAdapters
InterfaceAdapters --> UseCases
UseCases --> Entities
四层结构
| 层级 | 职责 | 依赖方向 | 示例 |
|---|---|---|---|
| Entities | 企业级业务规则 | 无依赖 | User, Order, Product |
| Use Cases | 应用级业务规则 | 依赖 Entities | CreateUser, PlaceOrder |
| Interface Adapters | 数据格式转换 | 依赖 Use Cases | Controller, DTO, Repository Impl |
| Frameworks | 框架与驱动 | 依赖 Interface Adapters | Spring, MySQL, Redis |
依赖规则
核心原则:依赖只能指向内部,不能指向外部。
❌ 错误:Use Cases 依赖 Controller
✅ 正确:Controller 依赖 Use Cases
二、项目结构
标准目录结构
src/
├── domain/ # 领域层(Entities + Use Cases)
│ ├── model/ # 领域模型(Entities)
│ │ ├── User.java
│ │ ├── Order.java
│ │ └── Product.java
│ ├── repository/ # 仓库接口(Use Cases)
│ │ ├── UserRepository.java
│ │ └── OrderRepository.java
│ └── service/ # 领域服务(Use Cases)
│ ├── CreateUserUseCase.java
│ └── PlaceOrderUseCase.java
├── application/ # 应用层(Interface Adapters)
│ ├── controller/ # 控制器
│ │ ├── UserController.java
│ │ └── OrderController.java
│ ├── dto/ # 数据传输对象
│ │ ├── request/
│ │ └── response/
│ ├── mapper/ # 对象映射
│ │ └── UserMapper.java
│ └── service/ # 应用服务实现
│ ├── UserServiceImpl.java
│ └── OrderServiceImpl.java
├── infrastructure/ # 基础设施层(Frameworks)
│ ├── persistence/ # 持久化
│ │ ├── entity/ # JPA 实体
│ │ ├── repository/ # 仓库实现
│ │ └── mapper/ # 实体映射
│ ├── config/ # 配置类
│ └── exception/ # 异常处理
└── interfaces/ # 接口层(Frameworks)
├── web/ # Web 接口
│ ├── controller/
│ └── advice/
└── rpc/ # RPC 接口
└── grpc/
Maven 模块划分(推荐)
parent/
├── domain/ # 领域模块(无外部依赖)
├── application/ # 应用模块
├── infrastructure/ # 基础设施模块
├── interfaces/ # 接口模块
└── starter/ # 启动模块
三、领域层实现
1. 领域模型(Entities)
// domain/model/User.java
package com.example.domain.model;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* 用户实体
* 领域模型只包含业务逻辑,无框架依赖
*/
public class User {
private final UserId id;
private String username;
private String email;
private UserRole role;
private boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// 私有构造,强制使用工厂方法
private User(UserId id, String username, String email, UserRole role) {
if (id == null) {
throw new IllegalArgumentException("User ID is required");
}
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("Username is required");
}
if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
throw new IllegalArgumentException("Invalid email");
}
this.id = id;
this.username = username;
this.email = email;
this.role = role;
this.active = true;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
// 工厂方法
public static User create(String username, String email, UserRole role) {
return new User(UserId.generate(), username, email, role);
}
// 业务方法
public void updateEmail(String newEmail) {
if (!newEmail.equals(this.email)) {
this.email = newEmail;
this.updatedAt = LocalDateTime.now();
}
}
public void updateRole(UserRole newRole) {
if (!newRole.equals(this.role)) {
this.role = newRole;
this.updatedAt = LocalDateTime.now();
}
}
public void deactivate() {
this.active = false;
this.updatedAt = LocalDateTime.now();
}
public void activate() {
this.active = true;
this.updatedAt = LocalDateTime.now();
}
// 业务规则
public boolean canPlaceOrder() {
return active && role.canPlaceOrder();
}
public boolean canAccessAdmin() {
return active && role.canAccessAdmin();
}
// Getters
public UserId getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
public UserRole getRole() { return role; }
public boolean isActive() { return active; }
public LocalDateTime getCreatedAt() { return createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
2. 值对象(Value Objects)
// domain/model/UserId.java
package com.example.domain.model;
import java.util.Objects;
import java.util.UUID;
/**
* 用户 ID 值对象
*/
public class UserId {
private final String value;
private UserId(String value) {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException("User ID cannot be empty");
}
this.value = value;
}
public static UserId generate() {
return new UserId(UUID.randomUUID().toString());
}
public static UserId of(String value) {
return new UserId(value);
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserId userId = (UserId) o;
return Objects.equals(value, userId.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return value;
}
}
3. 仓库接口(Repository Interface)
// domain/repository/UserRepository.java
package com.example.domain.repository;
import com.example.domain.model.User;
import com.example.domain.model.UserId;
import java.util.Optional;
/**
* 用户仓库接口
* 定义在领域层,由基础设施层实现
*/
public interface UserRepository {
User save(User user);
Optional<User> findById(UserId id);
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
void delete(UserId id);
}
4. 用例(Use Cases)
// domain/service/CreateUserUseCase.java
package com.example.domain.service;
import com.example.domain.model.User;
import com.example.domain.model.UserRole;
/**
* 创建用户用例接口
*/
public interface CreateUserUseCase {
User execute(String username, String email, UserRole role);
}
// domain/service/GetUserUseCase.java
package com.example.domain.service;
import com.example.domain.model.User;
import com.example.domain.model.UserId;
import java.util.Optional;
public interface GetUserUseCase {
Optional<User> execute(UserId id);
Optional<User> executeByUsername(String username);
}
// domain/service/UpdateUserUseCase.java
package com.example.domain.service;
import com.example.domain.model.User;
import com.example.domain.model.UserId;
public interface UpdateUserUseCase {
User execute(UserId id, String newEmail, String newUsername);
}
四、应用层实现
1. DTO 定义
// application/dto/request/CreateUserRequest.java
package com.example.application.dto.request;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class CreateUserRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
private String email;
private String role = "USER";
// Getters and Setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
}
// application/dto/response/UserResponse.java
package com.example.application.dto.response;
import java.time.LocalDateTime;
public class UserResponse {
private String id;
private String username;
private String email;
private String role;
private boolean active;
private LocalDateTime createdAt;
// Builder pattern
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String id;
private String username;
private String email;
private String role;
private boolean active;
private LocalDateTime createdAt;
public Builder id(String id) { this.id = id; return this; }
public Builder username(String username) { this.username = username; return this; }
public Builder email(String email) { this.email = email; return this; }
public Builder role(String role) { this.role = role; return this; }
public Builder active(boolean active) { this.active = active; return this; }
public Builder createdAt(LocalDateTime createdAt) { this.createdAt = createdAt; return this; }
public UserResponse build() {
UserResponse response = new UserResponse();
response.id = this.id;
response.username = this.username;
response.email = this.email;
response.role = this.role;
response.active = this.active;
response.createdAt = this.createdAt;
return response;
}
}
// Getters
public String getId() { return id; }
public String getUsername() { return username; }
public String getEmail() { return email; }
public String getRole() { return role; }
public boolean isActive() { return active; }
public LocalDateTime getCreatedAt() { return createdAt; }
}
2. 对象映射
// application/mapper/UserMapper.java
package com.example.application.mapper;
import com.example.domain.model.User;
import com.example.domain.model.UserRole;
import com.example.application.dto.request.CreateUserRequest;
import com.example.application.dto.response.UserResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
@Mapper(componentModel = "spring")
public interface UserMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "role", source = "role", qualifiedByName = "parseRole")
User toDomain(CreateUserRequest request);
@Mapping(target = "id", source = "id.value")
@Mapping(target = "role", source = "role", qualifiedByName = "roleToString")
UserResponse toResponse(User user);
@Named("parseRole")
default UserRole parseRole(String role) {
try {
return UserRole.valueOf(role.toUpperCase());
} catch (IllegalArgumentException e) {
return UserRole.USER;
}
}
@Named("roleToString")
default String roleToString(UserRole role) {
return role != null ? role.name() : "USER";
}
}
3. 用例实现
// application/service/UserServiceImpl.java
package com.example.application.service;
import com.example.domain.model.User;
import com.example.domain.model.UserId;
import com.example.domain.repository.UserRepository;
import com.example.domain.service.CreateUserUseCase;
import com.example.domain.service.GetUserUseCase;
import com.example.domain.service.UpdateUserUseCase;
import com.example.application.mapper.UserMapper;
import com.example.application.dto.request.CreateUserRequest;
import com.example.application.dto.response.UserResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements CreateUserUseCase, GetUserUseCase, UpdateUserUseCase {
private final UserRepository userRepository;
private final UserMapper userMapper;
@Override
@Transactional
public User execute(String username, String email, UserRole role) {
// 业务验证
if (userRepository.existsByUsername(username)) {
throw new UsernameAlreadyExistsException(username);
}
if (userRepository.existsByEmail(email)) {
throw new EmailAlreadyExistsException(email);
}
// 创建用户
User user = User.create(username, email, role);
return userRepository.save(user);
}
@Override
public Optional<User> execute(UserId id) {
return userRepository.findById(id);
}
@Override
public Optional<User> executeByUsername(String username) {
return userRepository.findByUsername(username);
}
@Override
@Transactional
public User execute(UserId id, String newEmail, String newUsername) {
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
// 业务验证
if (newEmail != null && !newEmail.equals(user.getEmail())) {
if (userRepository.existsByEmail(newEmail)) {
throw new EmailAlreadyExistsException(newEmail);
}
user.updateEmail(newEmail);
}
if (newUsername != null && !newUsername.equals(user.getUsername())) {
if (userRepository.existsByUsername(newUsername)) {
throw new UsernameAlreadyExistsException(newUsername);
}
user.updateUsername(newUsername);
}
return user;
}
}
五、基础设施层实现
1. JPA 实体
// infrastructure/persistence/entity/UserJpaEntity.java
package com.example.infrastructure.persistence.entity;
import lombok.*;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(access = AccessLevel.PRIVATE)
@Getter
public class UserJpaEntity {
@Id
private String id;
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false, unique = true, length = 100)
private String email;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private UserRole role;
@Column(nullable = false)
private boolean active;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
public static UserJpaEntity fromDomain(com.example.domain.model.User user) {
return UserJpaEntity.builder()
.id(user.getId().getValue())
.username(user.getUsername())
.email(user.getEmail())
.role(user.getRole())
.active(user.isActive())
.createdAt(user.getCreatedAt())
.updatedAt(user.getUpdatedAt())
.build();
}
public com.example.domain.model.User toDomain() {
return com.example.domain.model.User.restore(
com.example.domain.model.UserId.of(this.id),
this.username,
this.email,
this.role,
this.active,
this.createdAt,
this.updatedAt
);
}
}
2. 仓库实现
// infrastructure/persistence/repository/UserJpaRepository.java
package com.example.infrastructure.persistence.repository;
import com.example.infrastructure.persistence.entity.UserJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserJpaRepository extends JpaRepository<UserJpaEntity, String> {
Optional<UserJpaEntity> findByUsername(String username);
Optional<UserJpaEntity> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
// infrastructure/persistence/repository/UserRepositoryImpl.java
package com.example.infrastructure.persistence.repository;
import com.example.domain.model.User;
import com.example.domain.model.UserId;
import com.example.domain.repository.UserRepository;
import com.example.infrastructure.persistence.entity.UserJpaEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepository {
private final UserJpaRepository userJpaRepository;
private final UserDomainMapper mapper;
@Override
public User save(User user) {
UserJpaEntity entity = UserJpaEntity.fromDomain(user);
UserJpaEntity saved = userJpaRepository.save(entity);
return saved.toDomain();
}
@Override
public Optional<User> findById(UserId id) {
return userJpaRepository.findById(id.getValue())
.map(UserJpaEntity::toDomain);
}
@Override
public Optional<User> findByUsername(String username) {
return userJpaRepository.findByUsername(username)
.map(UserJpaEntity::toDomain);
}
@Override
public Optional<User> findByEmail(String email) {
return userJpaRepository.findByEmail(email)
.map(UserJpaEntity::toDomain);
}
@Override
public boolean existsByUsername(String username) {
return userJpaRepository.existsByUsername(username);
}
@Override
public boolean existsByEmail(String email) {
return userJpaRepository.existsByEmail(email);
}
@Override
public void delete(UserId id) {
userJpaRepository.deleteById(id.getValue());
}
}
六、接口层实现
1. REST Controller
// interfaces/web/controller/UserController.java
package com.example.interfaces.web.controller;
import com.example.domain.service.CreateUserUseCase;
import com.example.domain.service.GetUserUseCase;
import com.example.domain.service.UpdateUserUseCase;
import com.example.application.dto.request.CreateUserRequest;
import com.example.application.dto.response.UserResponse;
import com.example.application.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserController {
private final CreateUserUseCase createUserUseCase;
private final GetUserUseCase getUserUseCase;
private final UpdateUserUseCase updateUserUseCase;
private final UserMapper userMapper;
@PostMapping
public ResponseEntity<UserResponse> createUser(
@RequestBody @Validated CreateUserRequest request) {
var user = createUserUseCase.execute(
request.getUsername(),
request.getEmail(),
com.example.domain.model.UserRole.valueOf(request.getRole().toUpperCase())
);
UserResponse response = userMapper.toResponse(user);
return ResponseEntity
.created(URI.create("/api/v1/users/" + user.getId().getValue()))
.body(response);
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable String id) {
return getUserUseCase.execute(com.example.domain.model.UserId.of(id))
.map(userMapper::toResponse)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(
@PathVariable String id,
@RequestBody @Validated UpdateUserRequest request) {
var user = updateUserUseCase.execute(
com.example.domain.model.UserId.of(id),
request.getEmail(),
request.getUsername()
);
return ResponseEntity.ok(userMapper.toResponse(user));
}
}
2. 全局异常处理
// interfaces/web/advice/GlobalExceptionHandler.java
package com.example.interfaces.web.advice;
import com.example.domain.exception.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
log.warn("User not found: {}", e.getUserId());
return ResponseEntity
.status(404)
.body(new ErrorResponse(404, "USER_NOT_FOUND", e.getMessage(), LocalDateTime.now()));
}
@ExceptionHandler(UsernameAlreadyExistsException.class)
public ResponseEntity<ErrorResponse> handleUsernameAlreadyExists(UsernameAlreadyExistsException e) {
log.warn("Username already exists: {}", e.getUsername());
return ResponseEntity
.status(409)
.body(new ErrorResponse(409, "USERNAME_ALREADY_EXISTS", e.getMessage(), LocalDateTime.now()));
}
@ExceptionHandler(EmailAlreadyExistsException.class)
public ResponseEntity<ErrorResponse> handleEmailAlreadyExists(EmailAlreadyExistsException e) {
log.warn("Email already exists: {}", e.getEmail());
return ResponseEntity
.status(409)
.body(new ErrorResponse(409, "EMAIL_ALREADY_EXISTS", e.getMessage(), LocalDateTime.now()));
}
@ExceptionHandler(BindException.class)
public ResponseEntity<ErrorResponse> handleValidation(BindException e) {
log.warn("Validation error: {}", e.getBindingResult());
return ResponseEntity
.status(400)
.body(new ErrorResponse(400, "VALIDATION_ERROR",
e.getBindingResult().getAllErrors().get(0).getDefaultMessage(),
LocalDateTime.now()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception e) {
log.error("Unexpected error", e);
return ResponseEntity
.status(500)
.body(new ErrorResponse(500, "INTERNAL_ERROR", "An unexpected error occurred", LocalDateTime.now()));
}
record ErrorResponse(int status, String code, String message, LocalDateTime timestamp) {}
}
七、测试策略
1. 领域层测试(单元测试)
// test/domain/model/UserTest.java
@Test
void 用户应该可以成功创建 () {
// Given
String username = "testuser";
String email = "test@example.com";
UserRole role = UserRole.USER;
// When
User user = User.create(username, email, role);
// Then
assertThat(user.getId()).isNotNull();
assertThat(user.getUsername()).isEqualTo(username);
assertThat(user.getEmail()).isEqualTo(email);
assertThat(user.getRole()).isEqualTo(role);
assertThat(user.isActive()).isTrue();
}
@Test
void 无效的用户名应该抛出异常 () {
// Given
String invalidUsername = "";
String email = "test@example.com";
// When & Then
assertThatThrownBy(() -> User.create(invalidUsername, email, UserRole.USER))
.isInstanceOf(IllegalArgumentException.class);
}
@Test
void 非活跃用户不能下单 () {
// Given
User user = User.create("testuser", "test@example.com", UserRole.USER);
user.deactivate();
// When
boolean canPlaceOrder = user.canPlaceOrder();
// Then
assertThat(canPlaceOrder).isFalse();
}
2. 应用层测试(集成测试)
// test/application/service/UserServiceImplTest.java
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserServiceImplTest {
@Autowired
private CreateUserUseCase createUserUseCase;
@Autowired
private GetUserUseCase getUserUseCase;
@Autowired
private UserRepository userRepository;
@AfterEach
void tearDown() {
userRepository.deleteAll();
}
@Test
@DisplayName("创建用户成功")
void 创建用户成功 () {
// Given
String username = "testuser";
String email = "test@example.com";
UserRole role = UserRole.USER;
// When
User user = createUserUseCase.execute(username, email, role);
// Then
assertThat(user).isNotNull();
assertThat(user.getUsername()).isEqualTo(username);
assertThat(user.getEmail()).isEqualTo(email);
// 验证可以从数据库查询到
Optional<User> found = getUserUseCase.execute(user.getId());
assertThat(found).isPresent();
assertThat(found.get().getId()).isEqualTo(user.getId());
}
@Test
@DisplayName("重复用户名应该抛出异常")
void 重复用户名抛出异常 () {
// Given
String username = "duplicate";
String email1 = "test1@example.com";
String email2 = "test2@example.com";
createUserUseCase.execute(username, email1, UserRole.USER);
// When & Then
assertThatThrownBy(() -> createUserUseCase.execute(username, email2, UserRole.USER))
.isInstanceOf(UsernameAlreadyExistsException.class);
}
}
3. 接口层测试(端到端测试)
// test/interfaces/web/controller/UserControllerTest.java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private CreateUserUseCase createUserUseCase;
@MockBean
private GetUserUseCase getUserUseCase;
@Autowired
private ObjectMapper objectMapper;
@Test
@WithMockUser
void 创建用户返回 201() throws Exception {
// Given
CreateUserRequest request = new CreateUserRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setRole("USER");
User createdUser = User.create("testuser", "test@example.com", UserRole.USER);
when(createUserUseCase.execute(any(), any(), any())).thenReturn(createdUser);
// When & Then
mockMvc.perform(post("/api/v1/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.username").value("testuser"))
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
@WithMockUser
void 获取用户返回 200() throws Exception {
// Given
User user = User.create("testuser", "test@example.com", UserRole.USER);
when(getUserUseCase.execute(any(UserId.class))).thenReturn(Optional.of(user));
// When & Then
mockMvc.perform(get("/api/v1/users/{id}", user.getId().getValue()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value("testuser"));
}
@Test
@WithMockUser
void 获取不存在的用户返回 404() throws Exception {
// Given
when(getUserUseCase.execute(any(UserId.class))).thenReturn(Optional.empty());
// When & Then
mockMvc.perform(get("/api/v1/users/non-existent"))
.andExpect(status().isNotFound());
}
}
八、架构收益
可维护性提升
| 指标 | 传统架构 | 整洁架构 | 提升 |
|---|---|---|---|
| 代码复用率 | 40% | 75% | +87% |
| 单元测试覆盖率 | 30% | 85% | +183% |
| 新人上手时间 | 4 周 | 2 周 | -50% |
| Bug 修复时间 | 4 小时 | 1.5 小时 | -62% |
依赖解耦
// 领域层:无外部依赖
// 只有纯 Java 代码
// 应用层:依赖领域层 + 少量框架(如 MapStruct)
// 负责数据转换和流程控制
// 基础设施层:依赖所有内部层 + 外部框架
// Spring, JPA, Redis, etc.
// 接口层:依赖应用层 + Web 框架
// Controller, RPC, etc.
九、总结
核心原则
- 依赖规则:依赖只能指向内部
- 关注点分离:业务逻辑与框架分离
- 可测试性:业务逻辑独立于框架
- 框架无关性:可以轻松替换框架
实施建议
-
从领域模型开始
- 先定义 Entities 和 Value Objects
- 再定义 Use Cases
-
逐步重构
- 不要一次性重写
- 从新模块开始应用
-
团队培训
- 统一认识
- 代码 Review
-
工具支持
- ArchUnit 验证架构规则
- 自动化测试保证质量
参考资料:
- Robert C. Martin - 《Clean Architecture》
- Vaughn Vernon - 《实现领域驱动设计》
- ArchUnit 官方文档
- Spring Boot 最佳实践