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

news/2025/10/3 22:31:00/文章来源:https://www.cnblogs.com/slgkaifa/p/19125104

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

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

架构优势说明

  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
# 其他环境特定配置...

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/926396.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

长春网站设计团队好用的cms系统

数据介绍:根据2023年上市公司年报数据进行更新,包括基本信息、财务指标、环境、社会与治理、数字化转型、企业发展、全要素生产率等1413指标。数据范围:A股上市公司数据年份:1990-2023年指标数目:1413个指标&#xff0…

VSCode 中无法定位 Go 项目中自定义方法或类

自定义函数或类无法定位Go 项目中,自定义的函数、类型等无法通过 “转到定义” 或 “查找引用” 功能进行定位。 解决方法 如果 Go 项目没有正确初始化为 Go 模块(即没有 go.mod 文件),gopls 将无法正确解析代码的…

网站如何快速免费推广深圳制作广告宣传片制作

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。 环境要求 1、Java SDK 1.8 下载 2、Eclipse IDE for Java EE Mars 2 (4.5.2) 下载(依Eclipse举例,IDEA雷同。&…

青海建筑网站建设公司注册德国网站域名

文章目录 启动环境漏洞复现下载bp插件漏洞扫描dnslog测试是否向外请求资源用工具构造rmi服务器 反弹shell 启动环境 到vulhub目录下 cd vulhub/fastjson/1.2.24-rce安装环境并启动: sudo docker-compose up -d && sudo docker-compose up -d启动成功&…

中国网站排名前100天堂软件

From: http://blog.csdn.net/ljfrocky/article/details/46531137这篇文章主要介绍了如何使用PhpStorm Xdebug调试PHP程序,需要的朋友可以参考下。运行环境PhpStorm版本:8.0.3 PHP版本:5.4.12 xdebug版本:php_xdebug-2.2.3-5.4-vc…

乡镇网站建设宝塔面板设置wordpress伪静态

给你一个整数数组 arr 和一个整数 k 。现需要从数组中恰好移除 k 个元素,请找出移除后数组中不同整数的最少数目。 示例 1: 输入:arr [5,5,4], k 1 输出:1 解释:移除 1 个 4 ,数组中只剩下 5 一种整数。…

写作业

完成离散作业

P11164 [BalkanOI 2023] Permutations

思路 先判断是否有解。 即判断区间是否存在三元组 \((p_i,p_j,p_k)(i < j < k)\) 使得 \(p_i > p_j > p_k\);或者二元组 \((p_i,p_j)(i<j)\) 使得 \(p_i > p_j > \min_{k=1}^{L-1} \min_{k=R+1}…

旅游电子商务的三创赛网站建设英文seo推广

本文将详细介绍如何在Spring Boot应用程序中实现邮件发送服务。我们将探讨Spring Boot集成邮件发送服务的基本概念&#xff0c;以及如何使用Spring Boot和第三方邮件服务提供商来实现邮件发送。此外&#xff0c;我们将通过具体的示例来展示如何在Spring Boot中配置和使用邮件发…

Spring事务管理:-rollbackFor

rollbackFor属性用于控制出现何种异常类型,回滚事务。(默认情况下,只有出现RuntimeException才会回滚) 如: @Transactional(rollbackFor = {Exception.class}) @Override public void save(Emp emp) {//1.保存员工…

在JavaScript / HTML中,动态计算调整文字大小 - 详解

在JavaScript / HTML中,动态计算调整文字大小 - 详解2025-10-03 22:09 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; di…

微信图片批量保存的办法

微信聊天的图片有时候想要批量保存,但又不想一张一张取点,所以请看此文章已经给官方提建议了,目前还没在最新版看到相关功能。 解决办法:python 脚本(通过pywinauto 控制点击下一张和下载按钮,然后通过判断文件夹…

网站运营专员具体每天怎么做网站建设收费标准报价

ps&#xff1a; 为啥不用蒲公英了&#xff0c;就是因为有广告了&#xff0c;获取个UDID还安装游戏&#xff0c;真恶心?&#xff0c;所以找了新的获取UDID都方法&#xff0c;网页直接获取就可以&#xff0c;不会安装软件。 UDID 是一种 iOS 设备的特殊识别码。除序号之外&…

详细介绍:使用 C# 设置 Excel 单元格数据验证

详细介绍:使用 C# 设置 Excel 单元格数据验证pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &q…

乐清人才网官方网站深圳建网站公司哪家好

原理图&#xff1a; 矩阵按键原理图&#xff1a; 实验板接口原理图&#xff1a; 得到对应图&#xff1a; 扫描按键原理&#xff1a; 按键的COLUMN1、2、3分别制0&#xff0c;每次只允许其中一个为0其他都是1&#xff08;POW1和POW2正常状况为上拉&#xff09;&#xff0c;当有…

博客园实验1

1 // 打印一个字符小人 2 #include <stdio.h> 3 int main() 4 { 5 printf(" O \n"); 6 printf("<H>\n"); 7 printf("I I\n"); 8 return 0; 9 }View Code1 …

做盗版系统网站会不会php app网站建设

本书目录&#xff1a;点击进入 一、总结内容 二、习题 2.1 【选择题】以下Vue指令中&#xff0c;哪些指令具备简写方式&#xff1f; 2.2 【编程题】以下Vue指令中&#xff0c;哪些指令具备简写方式&#xff1f; &#xff1e; 效果 &#xff1e; 代码 一、总结内容 了解核…

arm汇编

寄存器 R0–R3 传参/返回值 R4–R11 局部变量(callee 保存) R12 临时 R13 SP 栈指针 R14 LR 返回地址 R15 PC 程序计数器 CPSR 标志位:N Z C V 常用指令 MOV Rd, #imm 立即数传送 ADD/ADC…

腾度网站建设品牌营销模式

前段时间&#xff0c;家里的iPad被家人误操作&#xff0c;导致iPad变成不可使用状态。自己折腾了半天&#xff0c;没有找到解决办法。没有办法&#xff0c;只好拿到手机维修店去修理,很快就修理好了.其实也很简单--就是对iPad进行了刷机操作。当然我也看到了刷机的方法。今天&a…

模型与分词器

当我们说“让AI理解人类语言”时,第一步是什么? 计算机是无法直接处理文本的,它需要数字。而模型与分词器就是完成“文本 → 数字 → 理解”这个神奇转换的关键二人组。本文将带你彻底理解这两者是什么、如何工作以…