下面是一个更详细的六边形架构实现方案,包含清晰的架构图和完整代码实现。这个方案严格遵循领域驱动设计(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. 领域层 - 模型与端口
java
// 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;
}
@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);
}
}
// 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. 领域服务
java
// 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. 应用层服务
java
// 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控制器)
java
// 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;
}
@PostMapping
public 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. 适配器层 - 输出适配器 (持久化实现)
java
// 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;
}
@Override
public 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();
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public Optional<User> findById(UserId userId) {
System.out.println("Querying MongoDB in " + environment + " environment");
return Optional.ofNullable(mongoTemplate.findById(userId.getValue(), User.class));
}
@Override
public User save(User user) {
System.out.println("Saving to MongoDB in " + environment + " environment");
return mongoTemplate.save(user);
}
@Override
public void delete(UserId userId) {
System.out.println("Deleting from MongoDB in " + environment + " environment");
mongoTemplate.remove(findById(userId).orElseThrow());
}
}
6. 环境感知适配器
java
// 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;
}
@Override
public Optional<User> findById(UserId userId) {
return getActiveRepository().findById(userId);
}
@Override
public User save(User user) {
return getActiveRepository().save(user);
}
@Override
public void delete(UserId userId) {
getActiveRepository().delete(userId);
}
private DatabasePort getActiveRepository() {
switch(activeEnvironment.toLowerCase()) {
case "test":
return testRepository;
case "dev":
default:
return devRepository;
}
}
}
7. 配置类
java
// 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";
@Bean
public 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)
bash
# 可选值: dev, test
app.environment=dev
# 可选值: jdbc, mongo
app.database.type=jdbc
# 其他环境特定配置...
这种架构设计确保了业务代码与底层实现的完全解耦,调用方只需要通过统一的接口访问服务,不需要关心具体使用哪个环境或哪个数据库。当需要添加新环境或切换数据库时,只需扩展适配器层,核心业务逻辑完全不受影响。