六边形架构实现:领域驱动设计 + 端口适配器模式

下面是一个更详细的六边形架构实现方案,包含清晰的架构图和完整代码实现。这个方案严格遵循领域驱动设计(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");
    }
}

架构优势说明

  1. 领域核心隔离‌:

    • 领域模型(User, UserId)完全独立,不依赖任何框架或数据库
    • 领域服务(UserService)只依赖抽象端口(DatabasePort)
  2. 环境透明性‌:

    • 通过EnvironmentAwareRepository实现环境自动切换
    • 业务代码完全不知道底层使用的具体环境
  3. 可扩展性‌:

    • 支持多种数据库类型(JDBC/MongoDB)
    • 轻松添加新环境(如生产环境)或新数据库类型
  4. 可测试性‌:

    • 领域核心可独立测试,无需数据库连接
    • 可使用内存数据库或Mock对象进行测试
  5. 运行时配置‌:

    • 通过application.properties配置环境和数据库类型
    • 无需修改代码即可切换环境

配置示例 (application.properties)

bash 复制代码
# 可选值: dev, test
app.environment=dev

# 可选值: jdbc, mongo
app.database.type=jdbc

# 其他环境特定配置...

这种架构设计确保了业务代码与底层实现的完全解耦,调用方只需要通过统一的接口访问服务,不需要关心具体使用哪个环境或哪个数据库。当需要添加新环境或切换数据库时,只需扩展适配器层,核心业务逻辑完全不受影响。

相关推荐
档案宝档案管理2 小时前
档案管理系统如何对企业效率重构与提升?
大数据·数据库·人工智能·重构·档案·档案管理
武子康3 小时前
大数据-112 Flink DataStream API :数据源、转换与输出 文件、Socket 到 Kafka 的完整流程
大数据·后端·flink
数据猿3 小时前
AI时代下,我们需要新一代的金融基础软件
大数据·人工智能·金融
有Li3 小时前
EndoChat:面向内镜手术的基于事实依据的多模态大型语言模型|文献速递-文献分享
大数据·论文阅读·人工智能·算法·文献·医学生
IT毕设梦工厂4 小时前
大数据毕业设计选题推荐-基于大数据的全球经济指标数据分析与可视化系统-Hadoop-Spark-数据可视化-BigData
大数据·hadoop·数据分析·spark·毕业设计·源码·bigdata
编程指南针5 小时前
【系统架构师-案例分析】2024年11月份案例分析第一题-架构评估
架构·系统架构
星川皆无恙5 小时前
知识图谱之深度学习:基于 BERT+LSTM+CRF 驱动深度学习识别模型医疗知识图谱问答可视化分析系统
大数据·人工智能·深度学习·bert·知识图谱
AI大数据智能洞察9 小时前
大数据领域数据仓库的备份恢复方案优化
大数据·数据仓库·ai
AI应用开发实战派9 小时前
大数据领域数据仓库的自动化测试实践
大数据·数据仓库·ai