大家好,我是猿java 。
DDD是微服务中经常用到的一种架构方式,在实际工作中,我们该如何快速落地一个 DDD工程呢?这篇文章,我们将手把手带你落地一个 DDD项目,不管你有没有 DDD经验,都可以轻松使用。
在开始我们的文章之前,我们还是要简单的了解下 DDD是什么,帮助我们下面更好地理解代码工程。
1. 什么是DDD? DDD,全称 Domain-Driven Design,翻译为领域驱动设计,它是一种软件开发方法论,由埃里克·埃文斯(Eric Evans) 在其2003年出版的同名书籍中提出。DDD旨在通过密切关注复杂软件系统的核心业务领域,将业务需求与技术实现紧密结合,从而提高软件的可维护性、可扩展性和灵活性。
1.1 DDD 的核心理念 DDD 以领域为核心,强调将业务领域作为软件开发的核心,致力于深入理解业务需求和业务规则,通过建模来反映实际业务问题。
1.2 统一语言 DDD使得开发团队和业务专家共同使用的一种准确、一致的语言(Ubiquitous Language),用于描述业务领域中的概念、流程和规则,减少沟通障碍,提高理解一致性。
1.3 战略设计与战术设计 DDD 分为战略设计和战术设计两部分:
战略设计:关注整个系统的高层次结构和模块划分,定义不同的子域(Subdomain)和上下文边界(Bounded Context)。 
战术设计:关注特定上下文内的细节,实现领域模型和相关组件。 
 
说实话,DDD的理论确实很烧脑,我们会在后续的文章中慢慢拆解。不管怎样,在对 DDD有了简单的了解之后,我们要进入今天的核心部分:DDD代码实操。
本文目标:使用DDD + SpringBoot + JPA + 双数据源(MySQL + DynamoDB)实现对 user表进行添加和查询功能,完全适合小白操作。
2. 项目整体结构 首先,我们先看下整个工程建的主要模块以及模块之间的依赖关系:
domain :核心领域模型和业务逻辑。 
repository :仓储接口定义。 
application :应用服务层,协调领域对象和仓储。 
infrastructure :基础设施层,包括具体的仓储实现(MySQL 和 DynamoDB)、配置等。 
config :独立的配置模块(可选,视项目复杂程度而定)。 
api (或 web ):入口层,如 REST API 控制器,主要处理外部接口请求。 
 
模块结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ddd-project/ ├── build.gradle ├── settings.gradle ├── domain/ │   └── build.gradle ├── repository/ │   └── build.gradle ├── application/ │   └── build.gradle ├── infrastructure/ │   ├── build.gradle │   ├── persistence-mysql/ │   │   └── build.gradle │   └── persistence-dynamodb/ │       └── build.gradle ├── config/ │   └── build.gradle └── api/     └── build.gradle 
 
3. 项目模块详解 3.1 Gradle 配置 3.1.1 根项目 settings.gradle 在根项目的 settings.gradle 中,包含所有子模块:
1 2 3 4 5 6 7 8 9 10 rootProject.name = 'ddd-project'  include 'domain'  include 'repository'  include 'application'  include 'infrastructure'  include 'infrastructure:persistence-mysql'  include 'infrastructure:persistence-dynamodb'  include 'config'  include 'api'  
 
3.1.2 根项目 build.gradle 根项目的 build.gradle 通常用于定义全局的插件和依赖管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 plugins {     id 'java'      id 'org.springframework.boot'  version '3.0.0'  apply false      id 'io.spring.dependency-management'  version '1.1.0'  apply false  } allprojects {     group = 'com.example'      version = '1.0.0'           repositories {         mavenCentral()     } } subprojects {     apply plugin:  'java'           sourceCompatibility = '17'      targetCompatibility = '17'      dependencies {                  implementation 'org.springframework.boot:spring-boot-starter'          testImplementation 'org.springframework.boot:spring-boot-starter-test'      }      } 
 
3.2 各子模块配置 3.2.1 domain 模块 功能 :定义核心领域模型和业务逻辑。
domain/build.gradle :
 
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package  com.example.domain;public  class  User  {    private  String id;     private  String name;     private  String email;          public  User ()  {}     public  User (String id, String name, String email)  {         this .id = id;         this .name = name;         this .email = email;     }          public  String getId ()  {         return  id;     }      } 
 
3.2.2 repository 模块 功能 :定义仓储接口,供应用层和基础设施层引用。
repository/build.gradle :
1 2 3 dependencies {     implementation project(':domain' ) } 
 
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 package  com.example.repository;import  com.example.domain.User;import  java.util.Optional;import  java.util.List;public  interface  UserRepository  {    void  save (User user) ;     Optional<User> findById (String id) ; } 
 
3.3.3 application 模块 功能 :实现应用服务,协调领域模型和仓储接口。
application/build.gradle :
1 2 3 4 5 dependencies {     implementation project(':domain' )     implementation project(':repository' )     implementation 'org.springframework.boot:spring-boot-starter'  } 
 
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package  com.example.application;import  com.example.domain.User;import  com.example.repository.UserRepository;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.stereotype.Service;import  java.util.List;import  java.util.Optional;@Service public  class  UserService  {    private  final  UserRepository userRepository;     @Autowired      public  UserService (UserRepository userRepository) {         this .userRepository = userRepository;     }     public  void  createUser (User user) {         userRepository.save(user);     }     public  Optional<User> getUserById (String id) {         return  userRepository.findById(id);     } } 
 
3.3.4 infrastructure 模块 功能 :实现基础设施组件,包括具体的仓储实现。
infrastructure/build.gradle :
1 2 3 4 dependencies {     implementation project(':repository' )     implementation 'org.springframework.boot:spring-boot-starter'  } 
 
infrastructure:persistence-mysql子模块 
功能 :实现 MySQL 的具体仓储。
infrastructure/persistence-mysql/build.gradle :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 plugins {     id 'org.springframework.boot'      id 'io.spring.dependency-management'  } dependencies {     implementation project(':domain' )     implementation project(':repository' )     implementation 'org.springframework.boot:spring-boot-starter-data-jpa'      runtimeOnly 'mysql:mysql-connector-java'                implementation 'org.mapstruct:mapstruct:1.5.5.Final'      annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'  } 
 
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package  com.example.infrastructure.persistence.mysql;import  javax.persistence.Entity;import  javax.persistence.Id;import  javax.persistence.Table;@Entity @Table(name = "users") public  class  UserEntity  {    @Id      private  String id;     private  String name;     private  String email;          public  UserEntity ()  {}     public  UserEntity (String id, String name, String email)  {         this .id = id;         this .name = name;         this .email = email;     }      } 
 
1 2 3 4 5 6 7 8 9 package  com.example.infrastructure.persistence.mysql;import  org.springframework.data.jpa.repository.JpaRepository;import  org.springframework.stereotype.Repository;@Repository public  interface  JpaUserRepository  extends  JpaRepository <UserEntity, String> {} 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package  com.example.infrastructure.persistence.mysql;import  com.example.domain.User;import  com.example.repository.UserRepository;import  org.springframework.stereotype.Repository;import  org.springframework.beans.factory.annotation.Autowired;import  java.util.Optional;import  java.util.List;import  java.util.stream.Collectors;@Repository public  class  MySQLUserRepository  implements  UserRepository  {    private  final  JpaUserRepository jpaUserRepository;     @Autowired      public  MySQLUserRepository (JpaUserRepository jpaUserRepository) {         this .jpaUserRepository = jpaUserRepository;     }     @Override      public  void  save (User user)  {         UserEntity  entity  =  new  UserEntity (user.getId(), user.getName(), user.getEmail());         jpaUserRepository.save(entity);     }     @Override      public  Optional<User> findById (String id)  {         Optional<UserEntity> entityOpt = jpaUserRepository.findById(id);         return  entityOpt.map(entity -> new  User (entity.getId(), entity.getName(), entity.getEmail()));     } } 
 
infrastructure:persistence-dynamodb 子模块 
功能 :实现 DynamoDB 的具体仓储。
infrastructure/persistence-dynamodb/build.gradle :
1 2 3 4 5 6 7 8 9 10 11 12 plugins {     id 'org.springframework.boot'      id 'io.spring.dependency-management'  } dependencies {     implementation project(':domain' )     implementation project(':repository' )     implementation 'software.amazon.awssdk:dynamodb:2.20.0'      implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0'      implementation 'org.springframework.boot:spring-boot-starter'  } 
 
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 package  com.example.infrastructure.persistence.dynamodb;import  com.example.domain.User;import  com.example.repository.UserRepository;import  org.springframework.stereotype.Repository;import  software.amazon.awssdk.services.dynamodb.DynamoDbClient;import  software.amazon.awssdk.services.dynamodb.model.*;import  org.springframework.beans.factory.annotation.Autowired;import  com.fasterxml.jackson.databind.ObjectMapper;import  java.util.Optional;import  java.util.List;import  java.util.Map;import  java.util.stream.Collectors;@Repository public  class  DynamoDBUserRepository  implements  UserRepository  {    private  static  final  String  TABLE_NAME  =  "Users" ;     private  final  DynamoDbClient dynamoDbClient;     private  final  ObjectMapper objectMapper;     @Autowired      public  DynamoDBUserRepository (DynamoDbClient dynamoDbClient) {         this .dynamoDbClient = dynamoDbClient;         this .objectMapper = new  ObjectMapper ();     }     @Override      public  void  save (User user)  {         try  {             String  json  =  objectMapper.writeValueAsString(user);             Map<String, Object> map = objectMapper.readValue(json, Map.class);             Map<String, AttributeValue> item = map.entrySet().stream()                     .collect(Collectors.toMap(                             Map.Entry::getKey,                             e -> AttributeValue.builder().s(e.getValue().toString()).build()                     ));             PutItemRequest  request  =  PutItemRequest.builder()                     .tableName(TABLE_NAME)                     .item(item)                     .build();             dynamoDbClient.putItem(request);         } catch  (Exception e) {             throw  new  RuntimeException ("Failed to save user to DynamoDB" , e);         }     }     @Override      public  Optional<User> findById (String id)  {         GetItemRequest  request  =  GetItemRequest.builder()                 .tableName(TABLE_NAME)                 .key(Map.of("id" , AttributeValue.builder().s(id).build()))                 .build();         GetItemResponse  response  =  dynamoDbClient.getItem(request);         if  (response.hasItem()) {             try  {                 String  json  =  objectMapper.writeValueAsString(response.item());                 User  user  =  objectMapper.readValue(json, User.class);                 return  Optional.of(user);             } catch  (Exception e) {                 throw  new  RuntimeException ("Failed to parse user from DynamoDB" , e);             }         }         return  Optional.empty();     } } 
 
DynamoDB 客户端配置 
在 infrastructure:persistence-dynamodb 子模块中配置 DynamoDB 客户端 Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package  com.example.infrastructure.persistence.dynamodb;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;import  software.amazon.awssdk.services.dynamodb.DynamoDbClient;import  software.amazon.awssdk.regions.Region;@Configuration public  class  DynamoDBConfig  {    @Bean      public  DynamoDbClient dynamoDbClient ()  {         return  DynamoDbClient.builder()                 .region(Region.US_EAST_1)                  .build();     } } 
 
3.3.5 config 模块(可选) 功能 :集中管理项目配置,例如选择使用的仓储实现、数据库配置等。
config/build.gradle :
1 2 3 4 5 6 dependencies {     implementation project(':application' )     implementation project(':infrastructure:persistence-mysql' )     implementation project(':infrastructure:persistence-dynamodb' )     implementation 'org.springframework.boot:spring-boot-starter'  } 
 
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package  com.example.config;import  com.example.repository.UserRepository;import  com.example.infrastructure.persistence_mysql.MySQLUserRepository;import  com.example.infrastructure.persistence_dynamodb.DynamoDBUserRepository;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.beans.factory.annotation.Value;import  org.springframework.context.annotation.Bean;import  org.springframework.context.annotation.Configuration;@Configuration public  class  RepositoryConfig  {    @Value("${app.repository.type}")      private  String repositoryType;     private  final  MySQLUserRepository mySQLUserRepository;     private  final  DynamoDBUserRepository dynamoDBUserRepository;     @Autowired      public  RepositoryConfig (MySQLUserRepository mySQLUserRepository, DynamoDBUserRepository dynamoDBUserRepository) {         this .mySQLUserRepository = mySQLUserRepository;         this .dynamoDBUserRepository = dynamoDBUserRepository;     }     @Bean      public  UserRepository userRepository ()  {         if  ("mysql" .equalsIgnoreCase(repositoryType)) {             return  mySQLUserRepository;         } else  if  ("dynamodb" .equalsIgnoreCase(repositoryType)) {             return  dynamoDBUserRepository;         } else  {             throw  new  IllegalArgumentException ("Unsupported repository type: "  + repositoryType);         }     } } 
 
**application.properties**(在 api 模块或根模块)
1 2 3 4 app.repository.type =mysql 
 
3.3.6 api 模块 功能 :作为应用的入口,处理外部请求(如 REST API)。
api/build.gradle :
1 2 3 4 5 6 7 8 9 10 plugins {     id 'org.springframework.boot'      id 'io.spring.dependency-management'  } dependencies {     implementation project(':application' )     implementation project(':config' )     implementation 'org.springframework.boot:spring-boot-starter-web'  } 
 
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package  com.example.api;import  com.example.application.UserService;import  com.example.domain.User;import  org.springframework.beans.factory.annotation.Autowired;import  org.springframework.web.bind.annotation.*;import  java.util.List;import  java.util.Optional;@RestController @RequestMapping("/users") public  class  UserController  {    private  final  UserService userService;     @Autowired      public  UserController (UserService userService) {         this .userService = userService;     }     @PostMapping      public  void  createUser (@RequestBody  User user) {         userService.createUser(user);     }     @GetMapping("/{id}")      public  Optional<User> getUserById (@PathVariable  String id) {         return  userService.getUserById(id);     } } 
 
3.4 模块间依赖关系图 1 2 3 4 5 6 7 8 9 10 11 domain    ↑ repository    ↑ application    ↑ infrastructure    ↑ config    ↑ api 
 
domain  是最底层,其他模块依赖于它。 
repository  依赖于 **domain**。 
application  依赖于 repository  和 **domain**。 
infrastructure  依赖于 repository  和 **domain**,实现具体仓储。 
config  依赖于 application  和 **infrastructure**,进行配置管理。 
api  依赖于 application  和 **config**,作为应用入口。 
 
4. 实施步骤详解 4.1 创建各个模块 使用 Gradle 命令或 IDE 创建各个模块。例如,使用命令行:
1 2 3 4 5 6 7 mkdir  ddd-projectcd  ddd-projectgradle init --type  basic mkdir  domain repository application infrastructuremkdir  infrastructure/persistence-mysql infrastructure/persistence-dynamodbmkdir  config api
 
然后,在各个子模块目录下创建相应的 build.gradle 文件并添加内容,如上所示。
4.2 配置依赖关系 确保每个子模块的 build.gradle 文件中正确地声明依赖关系。例如,在 application 模块的 build.gradle 中:
1 2 3 4 5 dependencies {     implementation project(':domain' )     implementation project(':repository' )     implementation 'org.springframework.boot:spring-boot-starter'  } 
 
类似地,在其他模块中声明所需的依赖。
4.3 配置 SpringBoot 在 api 模块中,添加 SpringBoot应用的主类:
1 2 3 4 5 6 7 8 9 10 11 12 13 package  com.example.api;import  org.springframework.boot.SpringApplication;import  org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication(scanBasePackages = "com.example") public  class  MyDddProjectApplication  {    public  static  void  main (String[] args)  {         SpringApplication.run(MyDddProjectApplication.class, args);     } } 
 
说明 :
使用 @SpringBootApplication 注解并设置 scanBasePackages 为 com.example,确保 Spring 能扫描到所有子模块中的组件。 
 
4.4 配置应用属性 在 api 模块中创建 src/main/resources/application.properties,并添加必要的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 spring.datasource.url =jdbc:mysql://localhost:3306/mydb spring.datasource.username =root spring.datasource.password =secret spring.jpa.hibernate.ddl-auto =update spring.jpa.show-sql =true app.repository.type =mysql 
 
4.5 构建和运行 在根项目目录下,运行以下命令以构建和运行应用:
1 2 ./gradlew build ./gradlew :api:bootRun 
 
确保你的 MySQL 和 DynamoDB(如果选择使用)都已正确配置和运行。
5. 项目优化 5.1 使用 Spring Profiles 为了更灵活地在不同环境(如开发、测试、生产)中选择仓储实现,可以使用 Spring Profiles。
示例: 
在 MySQLUserRepository 和 DynamoDBUserRepository 上添加 @Profile 注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 @Repository @Profile("mysql") public  class  MySQLUserRepository  implements  UserRepository  {     } @Repository @Profile("dynamodb") public  class  DynamoDBUserRepository  implements  UserRepository  {     } 
 
在 application.properties 中指定活跃配置:
1 2 3 spring.profiles.active =mysql 
 
5.2 使用 MapStruct进行对象映射 手动在仓储实现中转换领域对象和持久化对象可能繁琐,使用 MapStruct 可以简化这一过程。
示例 :
添加 MapStruct 依赖(已在 persistence-mysql 模块中添加)。
定义映射接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package  com.example.infrastructure.persistence.mysql;import  com.example.domain.User;import  org.mapstruct.Mapper;import  org.mapstruct.factory.Mappers;@Mapper public  interface  UserMapper  {    UserMapper  INSTANCE  =  Mappers.getMapper(UserMapper.class);     UserEntity toEntity (User user) ;     User toDomain (UserEntity entity) ; } 
 
在 MySQLUserRepository 中使用 UserMapper 进行转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package  com.example.infrastructure.persistence.mysql;import  com.example.domain.User;import  com.example.repository.UserRepository;import  org.springframework.stereotype.Repository;import  org.springframework.beans.factory.annotation.Autowired;import  java.util.Optional;import  java.util.List;import  java.util.stream.Collectors;@Repository @Profile("mysql") public  class  MySQLUserRepository  implements  UserRepository  {    private  final  JpaUserRepository jpaUserRepository;     private  final  UserMapper  userMapper  =  UserMapper.INSTANCE;     @Autowired      public  MySQLUserRepository (JpaUserRepository jpaUserRepository) {         this .jpaUserRepository = jpaUserRepository;     }     @Override      public  void  save (User user)  {         UserEntity  entity  =  userMapper.toEntity(user);         jpaUserRepository.save(entity);     }     @Override      public  Optional<User> findById (String id)  {         Optional<UserEntity> entityOpt = jpaUserRepository.findById(id);         return  entityOpt.map(userMapper::toDomain);     } } 
 
5.3 增加测试模块 为每个模块编写单元测试和集成测试,确保各部分功能正常。
5.4 使用依赖注入选择仓储实现 在 config 模块中动态选择仓储实现,或使用工厂模式进一步封装。
6. 总结 本文,我们没有讲解 DDD那些烧脑的理论知识,而是按照 DDD 的分层原则详细地落地了一个user添加和查询功能,并实现支持多种持久化机制(MySQL 和 DynamoDB)的仓储层设计。只要你有过 MVC 的 web开发经验,应该可以快速理解上述 DDD的代码工程。
对于 DDD模块化设计带来的好处,可以总结为以下几点:
高内聚、低耦合 :每个模块都有明确的职责,模块之间通过接口进行通信,降低了耦合度。 
易于扩展 :未来添加新的持久化机制(如 PostgreSQL、Redis 等)时,只需新增相应的基础设施子模块,实现仓储接口即可。 
团队协作 :不同团队或开发者可以并行开发不同模块,减少冲突和依赖问题。 
可维护性 :清晰的模块边界和职责分离,使得代码更易于理解和维护。 
 
在实际项目中,我们可根据具体的业务需求灵活地调整模块划分和依赖关系,确保架构设计既符合业务需求,又具备良好的技术基础。
7. 学习交流 如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。