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

六边形架构与DDD实践

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

    @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. 领域服务
// 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;
    }

    @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. 适配器层 - 输出适配器 (持久化实现)
// 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. 环境感知适配器
// 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. 配置类
// 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)

# 可选值: dev, test
app.environment=dev

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

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

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值