六边形架构实现:领域驱动设计 + 端口适配器模式
下面是一个更详细的六边形架构实现方案,包含清晰的架构图和完整代码实现。这个方案严格遵循领域驱动设计(DDD)和六边形架构原则,实现环境无关的数据库服务接口。
架构图
graph TD
subgraph 外部适配器
A[API 控制器] -->|使用| B[应用服务]
C[数据库适配器] -->|实现| D[领域端口]
E[消息队列适配器] -->|实现| D
end
subgraph 应用层
B -->|依赖| F[领域服务]
end
subgraph 领域层
F -->|依赖| D[DatabasePort]
G[实体] --> D
H[值对象] --> G
end
subgraph 基础设施
I[环境配置] --> C
J[外部服务] --> E
end
style D fill:#f9f,stroke:#333
style F fill:#9f9,stroke:#333
style G fill:#ff9,stroke:#333
style C fill:#f99,stroke:#333
完整代码实现
项目结构
src/
├── main/
│ ├── java/
│ │ ├── com/
│ │ │ └── example/
│ │ │ ├── domain/ # 领域层
│ │ │ │ ├── model/ # 领域模型
│ │ │ │ │ ├── User.java
│ │ │ │ │ └── UserId.java
│ │ │ │ ├── service/ # 领域服务
│ │ │ │ │ └── UserService.java
│ │ │ │ └── port/ # 领域端口
│ │ │ │ └── DatabasePort.java
│ │ │ ├── application/ # 应用层
│ │ │ │ └── UserAppService.java
│ │ │ ├── adapter/ # 适配器层
│ │ │ │ ├── in/ # 输入适配器
│ │ │ │ │ └── web/ # Web控制器
│ │ │ │ │ └── UserController.java
│ │ │ │ └── out/ # 输出适配器
│ │ │ │ ├── persistence/ # 持久化适配器
│ │ │ │ │ ├── jdbc/ # JDBC实现
│ │ │ │ │ │ └── JdbcUserRepository.java
│ │ │ │ │ ├── mongo/ # MongoDB实现
│ │ │ │ │ │ └── MongoUserRepository.java
│ │ │ │ │ └── env/ # 环境选择适配器
│ │ │ │ │ └── EnvironmentAwareRepository.java
│ │ │ │ └── messaging/ # 消息适配器
│ │ │ │ └── KafkaEventPublisher.java
│ │ │ ├── config/ # 配置
│ │ │ │ └── AppConfig.java
│ │ │ └── MainApplication.java # 启动类
│ └── resources/
│ └── application.properties # 配置文件
└── test/ # 测试目录
代码实现
1. 领域层 - 模型与端口
// src/main/java/com/example/domain/model/UserId.java
package com.example.domain.model;import java.util.Objects;
import java.util.UUID;public final class UserId {private final String value;public UserId(String value) {if (value == null || value.isBlank()) {throw new IllegalArgumentException("User ID cannot be null or empty");}this.value = value;}public static UserId generate() {return new UserId(UUID.randomUUID().toString());}public String getValue() {return value;}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(value);}
}// src/main/java/com/example/domain/model/User.java
package com.example.domain.model;import java.time.LocalDateTime;public class User {private final UserId id;private String username;private String email;private final LocalDateTime createdAt;public User(UserId id, String username, String email) {this.id = id;this.username = username;this.email = email;this.createdAt = LocalDateTime.now();}// Getter方法public UserId getId() { return id; }public String getUsername() { return username; }public String getEmail() { return email; }public LocalDateTime getCreatedAt() { return createdAt; }// 领域行为public void updateEmail(String newEmail) {if (!isValidEmail(newEmail)) {throw new IllegalArgumentException("Invalid email format");}this.email = newEmail;}private boolean isValidEmail(String email) {return email != null && email.contains("@");}
}// src/main/java/com/example/domain/port/DatabasePort.java
package com.example.domain.port;import com.example.domain.model.User;
import com.example.domain.model.UserId;import java.util.Optional;public interface DatabasePort {Optional<User> findById(UserId userId);User save(User user);void delete(UserId userId);
}
2. 领域服务
// src/main/java/com/example/domain/service/UserService.java
package com.example.domain.service;import com.example.domain.model.User;
import com.example.domain.model.UserId;
import com.example.domain.port.DatabasePort;
import com.example.domain.port.EventPublisher;public class UserService {private final DatabasePort databasePort;private final EventPublisher eventPublisher;public UserService(DatabasePort databasePort, EventPublisher eventPublisher) {this.databasePort = databasePort;this.eventPublisher = eventPublisher;}public User createUser(String username, String email) {UserId userId = UserId.generate();User user = new User(userId, username, email);User savedUser = databasePort.save(user);eventPublisher.publish("USER_CREATED", savedUser);return savedUser;}public User updateUserEmail(UserId userId, String newEmail) {return databasePort.findById(userId).map(user -> {user.updateEmail(newEmail);User updatedUser = databasePort.save(user);eventPublisher.publish("USER_UPDATED", updatedUser);return updatedUser;}).orElseThrow(() -> new RuntimeException("User not found"));}
}
3. 应用层服务
// src/main/java/com/example/application/UserAppService.java
package com.example.application;import com.example.domain.model.User;
import com.example.domain.model.UserId;
import com.example.domain.service.UserService;public class UserAppService {private final UserService userService;public UserAppService(UserService userService) {this.userService = userService;}public User createUser(String username, String email) {return userService.createUser(username, email);}public User updateUserEmail(String userId, String newEmail) {return userService.updateUserEmail(new UserId(userId), newEmail);}public User getUser(String userId) {return userService.getUser(new UserId(userId)).orElseThrow(() -> new RuntimeException("User not found"));}
}
4. 适配器层 - 输入适配器 (Web控制器)
// src/main/java/com/example/adapter/in/web/UserController.java
package com.example.adapter.in.web;import com.example.application.UserAppService;
import com.example.domain.model.User;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/users")
public class UserController {private final UserAppService userAppService;public UserController(UserAppService userAppService) {this.userAppService = userAppService;}@PostMappingpublic User createUser(@RequestBody CreateUserRequest request) {return userAppService.createUser(request.username(), request.email());}@PatchMapping("/{userId}/email")public User updateEmail(@PathVariable String userId, @RequestBody String newEmail) {return userAppService.updateUserEmail(userId, newEmail);}@GetMapping("/{userId}")public User getUser(@PathVariable String userId) {return userAppService.getUser(userId);}record CreateUserRequest(String username, String email) {}
}
5. 适配器层 - 输出适配器 (持久化实现)
// src/main/java/com/example/adapter/out/persistence/jdbc/JdbcUserRepository.java
package com.example.adapter.out.persistence.jdbc;import com.example.domain.model.User;
import com.example.domain.model.UserId;
import com.example.domain.port.DatabasePort;
import org.springframework.jdbc.core.JdbcTemplate;import java.util.Optional;public class JdbcUserRepository implements DatabasePort {private final JdbcTemplate jdbcTemplate;private final String environment;public JdbcUserRepository(JdbcTemplate jdbcTemplate, String environment) {this.jdbcTemplate = jdbcTemplate;this.environment = environment;}@Overridepublic Optional<User> findById(UserId userId) {System.out.println("Querying JDBC database in " + environment + " environment");String sql = "SELECT * FROM users WHERE id = ?";return jdbcTemplate.query(sql, (rs, rowNum) -> new User(new UserId(rs.getString("id")),rs.getString("username"),rs.getString("email")), userId.getValue()).stream().findFirst();}@Overridepublic User save(User user) {System.out.println("Saving to JDBC database in " + environment + " environment");String sql = "INSERT INTO users (id, username, email) VALUES (?, ?, ?) " +"ON DUPLICATE KEY UPDATE username = ?, email = ?";jdbcTemplate.update(sql, user.getId().getValue(), user.getUsername(), user.getEmail(),user.getUsername(), user.getEmail());return user;}@Overridepublic void delete(UserId userId) {System.out.println("Deleting from JDBC database in " + environment + " environment");jdbcTemplate.update("DELETE FROM users WHERE id = ?", userId.getValue());}
}// src/main/java/com/example/adapter/out/persistence/mongo/MongoUserRepository.java
package com.example.adapter.out.persistence.mongo;import com.example.domain.model.User;
import com.example.domain.model.UserId;
import com.example.domain.port.DatabasePort;
import org.springframework.data.mongodb.core.MongoTemplate;import java.util.Optional;public class MongoUserRepository implements DatabasePort {private final MongoTemplate mongoTemplate;private final String environment;public MongoUserRepository(MongoTemplate mongoTemplate, String environment) {this.mongoTemplate = mongoTemplate;this.environment = environment;}@Overridepublic Optional<User> findById(UserId userId) {System.out.println("Querying MongoDB in " + environment + " environment");return Optional.ofNullable(mongoTemplate.findById(userId.getValue(), User.class));}@Overridepublic User save(User user) {System.out.println("Saving to MongoDB in " + environment + " environment");return mongoTemplate.save(user);}@Overridepublic void delete(UserId userId) {System.out.println("Deleting from MongoDB in " + environment + " environment");mongoTemplate.remove(findById(userId).orElseThrow());}
}
6. 环境感知适配器
// src/main/java/com/example/adapter/out/persistence/env/EnvironmentAwareRepository.java
package com.example.adapter.out.persistence.env;import com.example.domain.model.User;
import com.example.domain.model.UserId;
import com.example.domain.port.DatabasePort;
import java.util.Optional;public class EnvironmentAwareRepository implements DatabasePort {private final DatabasePort devRepository;private final DatabasePort testRepository;private final String activeEnvironment;public EnvironmentAwareRepository(DatabasePort devDataSource, DatabasePort testDataSource,String activeEnvironment) {this.devRepository = devDataSource;this.testRepository = testDataSource;this.activeEnvironment = activeEnvironment;}@Overridepublic Optional<User> findById(UserId userId) {return getActiveRepository().findById(userId);}@Overridepublic User save(User user) {return getActiveRepository().save(user);}@Overridepublic void delete(UserId userId) {getActiveRepository().delete(userId);}private DatabasePort getActiveRepository() {switch(activeEnvironment.toLowerCase()) {case "test":return testRepository;case "dev":default:return devRepository;}}
}
7. 配置类
// src/main/java/com/example/config/AppConfig.java
package com.example.config;import com.example.adapter.out.persistence.env.EnvironmentAwareRepository;
import com.example.adapter.out.persistence.jdbc.JdbcUserRepository;
import com.example.adapter.out.persistence.mongo.MongoUserRepository;
import com.example.domain.port.DatabasePort;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.data.mongodb.core.MongoTemplate;@Configuration
public class AppConfig {@Value("${app.environment:dev}")private String environment;@Value("${app.database.type:jdbc}")private String databaseType;private static final String JDBC = "jdbc";private static final String MONGO = "mongo";@Beanpublic DatabasePort databasePort(JdbcTemplate jdbcTemplate, MongoTemplate mongoTemplate) {// 根据不同环境创建不同的数据库适配器DatabasePort devAdapter = createDatabaseAdapter("dev", JDBC.equals(databaseType) ? jdbcTemplate : mongoTemplate);DatabasePort testAdapter = createDatabaseAdapter("test", JDBC.equals(databaseType) ? jdbcTemplate : mongoTemplate);// 返回环境感知的适配器return new EnvironmentAwareRepository(devAdapter, testAdapter, environment);}private DatabasePort createDatabaseAdapter(String env, Object template) {if (template instanceof JdbcTemplate) {return new JdbcUserRepository((JdbcTemplate) template, env);} else if (template instanceof MongoTemplate) {return new MongoUserRepository((MongoTemplate) template, env);}throw new IllegalArgumentException("Unsupported database template type");}
}
架构优势说明
领域核心隔离:
- 领域模型(User, UserId)完全独立,不依赖任何框架或数据库
- 领域服务(UserService)只依赖抽象端口(DatabasePort)
环境透明性:
- 通过EnvironmentAwareRepository实现环境自动切换
- 业务代码完全不知道底层使用的具体环境
可扩展性:
- 支持多种数据库类型(JDBC/MongoDB)
- 轻松添加新环境(如生产环境)或新数据库类型
可测试性:
- 领域核心可独立测试,无需数据库连接
- 可使用内存数据库或Mock对象进行测试
运行时配置:
- 通过application.properties配置环境和数据库类型
- 无需修改代码即可切换环境
配置示例 (application.properties)
# 可选值: dev, test
app.environment=dev# 可选值: jdbc, mongo
app.database.type=jdbc# 其他环境特定配置...
这种架构设计确保了业务代码与底层实现的完全解耦,调用方只需要通过统一的接口访问服务,不需要关心具体使用哪个环境或哪个数据库。当需要添加新环境或切换数据库时,只需扩展适配器层,核心业务逻辑完全不受影响。