Ddd模式示例

1. 项目目录结构

src/main/java/com/company/project/
├── application/                    # 应用层
│   ├── command/                   # 命令
│   │   ├── CreateUserCommand.java
│   │   └── UpdateUserCommand.java
│   ├── handler/                   # 命令处理器
│   │   ├── CreateUserHandler.java
│   │   └── UpdateUserHandler.java
│   ├── service/                   # 应用服务
│   │   └── UserApplicationService.java
│   ├── dto/                       # 数据传输对象
│   │   ├── CreateUserDto.java
│   │   └── UserDto.java
│   └── query/                     # 查询服务
│       └── UserQueryService.java
├── domain/                        # 领域层
│   ├── entity/                    # 实体
│   │   ├── User.java
│   │   └── Order.java
│   ├── valueobject/              # 值对象
│   │   ├── Email.java
│   │   ├── Money.java
│   │   └── UserId.java
│   ├── aggregate/                # 聚合根
│   │   └── OrderAggregate.java
│   ├── repository/               # 仓储接口
│   │   ├── UserRepository.java
│   │   └── OrderRepository.java
│   ├── service/                  # 领域服务
│   │   └── UserDomainService.java
│   ├── event/                    # 领域事件
│   │   ├── DomainEvent.java
│   │   ├── UserCreatedEvent.java
│   │   └── OrderConfirmedEvent.java
│   └── exception/                # 领域异常
│       ├── DomainException.java
│       ├── InvalidEmailException.java
│       └── DuplicateEmailException.java
├── infrastructure/               # 基础设施层
│   ├── repository/              # 仓储实现
│   │   ├── UserRepositoryImpl.java
│   │   └── OrderRepositoryImpl.java
│   ├── persistence/             # 持久化
│   │   ├── entity/
│   │   │   ├── UserJpaEntity.java
│   │   │   └── OrderJpaEntity.java
│   │   └── mapper/
│   │       ├── UserMapper.java
│   │       └── OrderMapper.java
│   ├── config/                  # 配置
│   │   └── DatabaseConfig.java
│   └── external/               # 外部服务
│       └── EmailService.java
└── interfaces/                  # 接口层
    ├── rest/                   # REST控制器
    │   └── UserController.java
    ├── dto/                    # 接口DTO
    │   ├── CreateUserRequest.java
    │   └── UserResponse.java
    └── config/                 # Web配置
        └── WebConfig.java

2. 领域层实现

2.1 值对象 (Value Objects)

// domain/valueobject/Email.java
package com.company.project.domain.valueobject;

import com.company.project.domain.exception.InvalidEmailException;
import java.util.Objects;
import java.util.regex.Pattern;

public final class Email {
    private static final Pattern EMAIL_PATTERN = 
        Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
    
    private final String value;
    
    public Email(String value) {
        if (value == null || value.trim().isEmpty()) {
            throw new InvalidEmailException("Email cannot be null or empty");
        }
        if (!EMAIL_PATTERN.matcher(value).matches()) {
            throw new InvalidEmailException("Invalid email format: " + value);
        }
        this.value = value.toLowerCase().trim();
    }
    
    public String getValue() {
        return value;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Email email = (Email) obj;
        return Objects.equals(value, email.value);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
    
    @Override
    public String toString() {
        return value;
    }
}
// domain/valueobject/UserId.java
package com.company.project.domain.valueobject;

import java.util.Objects;
import java.util.UUID;

public final class UserId {
    private final String value;
    
    public UserId(String value) {
        if (value == null || value.trim().isEmpty()) {
            throw new IllegalArgumentException("UserId 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 obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        UserId userId = (UserId) obj;
        return Objects.equals(value, userId.value);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}
// domain/valueobject/Money.java
package com.company.project.domain.valueobject;

import java.math.BigDecimal;
import java.util.Objects;

public final class Money {
    private final BigDecimal amount;
    private final String currency;
    
    public Money(BigDecimal amount, String currency) {
        if (amount == null) {
            throw new IllegalArgumentException("Amount cannot be null");
        }
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Amount cannot be negative");
        }
        if (currency == null || currency.trim().isEmpty()) {
            throw new IllegalArgumentException("Currency cannot be null or empty");
        }
        this.amount = amount;
        this.currency = currency.toUpperCase();
    }
    
    public Money(double amount, String currency) {
        this(BigDecimal.valueOf(amount), currency);
    }
    
    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("Cannot add different currencies");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
    
    public Money multiply(int multiplier) {
        return new Money(this.amount.multiply(BigDecimal.valueOf(multiplier)), this.currency);
    }
    
    public BigDecimal getAmount() {
        return amount;
    }
    
    public String getCurrency() {
        return currency;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Money money = (Money) obj;
        return Objects.equals(amount, money.amount) && Objects.equals(currency, money.currency);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }
}

2.2 实体 (Entities)

// domain/entity/User.java
package com.company.project.domain.entity;

import com.company.project.domain.valueobject.Email;
import com.company.project.domain.valueobject.UserId;
import com.company.project.domain.event.UserCreatedEvent;
import com.company.project.domain.exception.InvalidUserNameException;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class User {
    private final UserId id;
    private Email email;
    private String name;
    private final LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private boolean active;
    private List<DomainEvent> domainEvents = new ArrayList<>();
    
    // 构造函数 - 创建新用户
    public User(Email email, String name) {
        this.id = UserId.generate();
        this.email = email;
        this.name = validateAndSetName(name);
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
        this.active = true;
        
        // 添加领域事件
        this.addDomainEvent(new UserCreatedEvent(this.id, this.email));
    }
    
    // 重建构造函数 - 从存储重建
    public User(UserId id, Email email, String name, LocalDateTime createdAt, 
                LocalDateTime updatedAt, boolean active) {
        this.id = id;
        this.email = email;
        this.name = name;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
        this.active = active;
    }
    
    public void changeName(String newName) {
        String validatedName = validateAndSetName(newName);
        if (!this.name.equals(validatedName)) {
            this.name = validatedName;
            this.updatedAt = LocalDateTime.now();
        }
    }
    
    public void changeEmail(Email newEmail) {
        if (!this.email.equals(newEmail)) {
            this.email = newEmail;
            this.updatedAt = LocalDateTime.now();
        }
    }
    
    public void activate() {
        if (!this.active) {
            this.active = true;
            this.updatedAt = LocalDateTime.now();
        }
    }
    
    public void deactivate() {
        if (this.active) {
            this.active = false;
            this.updatedAt = LocalDateTime.now();
        }
    }
    
    private String validateAndSetName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new InvalidUserNameException("Name cannot be null or empty");
        }
        if (name.trim().length() > 100) {
            throw new InvalidUserNameException("Name cannot exceed 100 characters");
        }
        return name.trim();
    }
    
    private void addDomainEvent(DomainEvent event) {
        this.domainEvents.add(event);
    }
    
    public List<DomainEvent> getDomainEvents() {
        return new ArrayList<>(domainEvents);
    }
    
    public void clearDomainEvents() {
        this.domainEvents.clear();
    }
    
    // Getters
    public UserId getId() { return id; }
    public Email getEmail() { return email; }
    public String getName() { return name; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public LocalDateTime getUpdatedAt() { return updatedAt; }
    public boolean isActive() { return active; }
}

2.3 聚合根 (Aggregate Root)

// domain/aggregate/OrderAggregate.java
package com.company.project.domain.aggregate;

import com.company.project.domain.entity.OrderItem;
import com.company.project.domain.valueobject.*;
import com.company.project.domain.event.OrderConfirmedEvent;
import com.company.project.domain.exception.OrderModificationException;
import com.company.project.domain.exception.EmptyOrderException;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class OrderAggregate {
    private final OrderId id;
    private final UserId customerId;
    private final List<OrderItem> items;
    private OrderStatus status;
    private final LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private List<DomainEvent> domainEvents = new ArrayList<>();
    
    // 创建新订单
    public OrderAggregate(UserId customerId) {
        this.id = OrderId.generate();
        this.customerId = customerId;
        this.items = new ArrayList<>();
        this.status = OrderStatus.PENDING;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    // 从存储重建
    public OrderAggregate(OrderId id, UserId customerId, List<OrderItem> items, 
                         OrderStatus status, LocalDateTime createdAt, LocalDateTime updatedAt) {
        this.id = id;
        this.customerId = customerId;
        this.items = new ArrayList<>(items);
        this.status = status;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }
    
    public void addItem(ProductId productId, String productName, 
                       Money unitPrice, int quantity) {
        if (status != OrderStatus.PENDING) {
            throw new OrderModificationException("Cannot modify order in " + status + " status");
        }
        
        // 检查是否已存在该商品
        OrderItem existingItem = findItemByProductId(productId);
        if (existingItem != null) {
            existingItem.changeQuantity(existingItem.getQuantity() + quantity);
        } else {
            OrderItem newItem = new OrderItem(productId, productName, unitPrice, quantity);
            items.add(newItem);
        }
        
        this.updatedAt = LocalDateTime.now();
    }
    
    public void removeItem(ProductId productId) {
        if (status != OrderStatus.PENDING) {
            throw new OrderModificationException("Cannot modify order in " + status + " status");
        }
        
        items.removeIf(item -> item.getProductId().equals(productId));
        this.updatedAt = LocalDateTime.now();
    }
    
    public void confirm() {
        if (items.isEmpty()) {
            throw new EmptyOrderException("Cannot confirm empty order");
        }
        
        if (status != OrderStatus.PENDING) {
            throw new OrderModificationException("Order is already " + status);
        }
        
        this.status = OrderStatus.CONFIRMED;
        this.updatedAt = LocalDateTime.now();
        
        // 发布领域事件
        this.addDomainEvent(new OrderConfirmedEvent(this.id, this.customerId, getTotalAmount()));
    }
    
    public void cancel() {
        if (status == OrderStatus.SHIPPED || status == OrderStatus.DELIVERED) {
            throw new OrderModificationException("Cannot cancel " + status + " order");
        }
        
        this.status = OrderStatus.CANCELLED;
        this.updatedAt = LocalDateTime.now();
    }
    
    public Money getTotalAmount() {
        if (items.isEmpty()) {
            return new Money(0, "USD");
        }
        
        return items.stream()
                   .map(OrderItem::getSubtotal)
                   .reduce(Money::add)
                   .orElse(new Money(0, "USD"));
    }
    
    private OrderItem findItemByProductId(ProductId productId) {
        return items.stream()
                   .filter(item -> item.getProductId().equals(productId))
                   .findFirst()
                   .orElse(null);
    }
    
    private void addDomainEvent(DomainEvent event) {
        this.domainEvents.add(event);
    }
    
    // Getters
    public OrderId getId() { return id; }
    public UserId getCustomerId() { return customerId; }
    public List<OrderItem> getItems() { return new ArrayList<>(items); }
    public OrderStatus getStatus() { return status; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public LocalDateTime getUpdatedAt() { return updatedAt; }
    public List<DomainEvent> getDomainEvents() { return new ArrayList<>(domainEvents); }
    public void clearDomainEvents() { this.domainEvents.clear(); }
}

2.4 仓储接口 (Repository Interface)

// domain/repository/UserRepository.java
package com.company.project.domain.repository;

import com.company.project.domain.entity.User;
import com.company.project.domain.valueobject.Email;
import com.company.project.domain.valueobject.UserId;

import java.util.List;
import java.util.Optional;

public interface UserRepository {
    Optional<User> findById(UserId id);
    Optional<User> findByEmail(Email email);
    List<User> findByNameContaining(String name);
    void save(User user);
    void delete(UserId id);
    boolean existsByEmail(Email email);
}

2.5 领域服务 (Domain Service)

// domain/service/UserDomainService.java
package com.company.project.domain.service;

import com.company.project.domain.entity.User;
import com.company.project.domain.repository.UserRepository;
import com.company.project.domain.valueobject.Email;
import com.company.project.domain.exception.DuplicateEmailException;

public class UserDomainService {
    private final UserRepository userRepository;
    
    public UserDomainService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public void validateEmailUniqueness(Email email) {
        if (userRepository.existsByEmail(email)) {
            throw new DuplicateEmailException("Email " + email.getValue() + " is already in use");
        }
    }
    
    public boolean canUserChangeEmail(User user, Email newEmail) {
        if (user.getEmail().equals(newEmail)) {
            return true; // 没有改变
        }
        
        return !userRepository.existsByEmail(newEmail);
    }
}

2.6 领域事件 (Domain Events)

// domain/event/DomainEvent.java
package com.company.project.domain.event;

import java.time.LocalDateTime;

public abstract class DomainEvent {
    private final LocalDateTime occurredOn;
    
    protected DomainEvent() {
        this.occurredOn = LocalDateTime.now();
    }
    
    public LocalDateTime getOccurredOn() {
        return occurredOn;
    }
}
// domain/event/UserCreatedEvent.java
package com.company.project.domain.event;

import com.company.project.domain.valueobject.Email;
import com.company.project.domain.valueobject.UserId;

public class UserCreatedEvent extends DomainEvent {
    private final UserId userId;
    private final Email email;
    
    public UserCreatedEvent(UserId userId, Email email) {
        super();
        this.userId = userId;
        this.email = email;
    }
    
    public UserId getUserId() { return userId; }
    public Email getEmail() { return email; }
}

3. 应用层实现

3.1 命令 (Commands)

// application/command/CreateUserCommand.java
package com.company.project.application.command;

public class CreateUserCommand {
    private final String email;
    private final String name;

    public CreateUserCommand(String email, String name) {
        this.email = email;
        this.name = name;
    }

    public String getEmail() { return email; }
    public String getName() { return name; }
}

3.2 命令处理器 (Command Handlers)

// application/handler/CreateUserHandler.java
package com.company.project.application.handler;

import com.company.project.application.command.CreateUserCommand;
import com.company.project.domain.entity.User;
import com.company.project.domain.repository.UserRepository;
import com.company.project.domain.service.UserDomainService;
import com.company.project.domain.valueobject.Email;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class CreateUserHandler {
    private final UserRepository userRepository;
    private final UserDomainService userDomainService;
    
    public CreateUserHandler(UserRepository userRepository, 
                           UserDomainService userDomainService) {
        this.userRepository = userRepository;
        this.userDomainService = userDomainService;
    }
    
    @Transactional
    public void handle(CreateUserCommand command) {
        Email email = new Email(command.getEmail());
        
        // 使用领域服务验证业务规则
        userDomainService.validateEmailUniqueness(email);
        
        // 创建用户实体
        User user = new User(email, command.getName());
        
        // 保存用户
        userRepository.save(user);
        
        // 处理领域事件(通常由框架自动处理)
        // eventPublisher.publishEvents(user.getDomainEvents());
        // user.clearDomainEvents();
    }
}

3.3 应用服务 (Application Services)

// application/service/UserApplicationService.java
package com.company.project.application.service;

import com.company.project.application.command.CreateUserCommand;
import com.company.project.application.dto.CreateUserDto;
import com.company.project.application.dto.UserDto;
import com.company.project.application.handler.CreateUserHandler;
import com.company.project.domain.entity.User;
import com.company.project.domain.repository.UserRepository;
import com.company.project.domain.valueobject.Email;
import com.company.project.domain.valueobject.UserId;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class UserApplicationService {
    private final CreateUserHandler createUserHandler;
    private final UserRepository userRepository;
    
    public UserApplicationService(CreateUserHandler createUserHandler,
                                UserRepository userRepository) {
        this.createUserHandler = createUserHandler;
        this.userRepository = userRepository;
    }
    
    public UserDto createUser(CreateUserDto dto) {
        CreateUserCommand command = new CreateUserCommand(dto.getEmail(), dto.getName());
        createUserHandler.handle(command);
        
        // 返回创建的用户信息
        Optional<User> user = userRepository.findByEmail(new Email(dto.getEmail()));
        return user.map(this::mapToDto)
                  .orElseThrow(() -> new RuntimeException("User creation failed"));
    }
    
    public Optional<UserDto> getUserById(String id) {
        Optional<User> user = userRepository.findById(new UserId(id));
        return user.map(this::mapToDto);
    }
    
    private UserDto mapToDto(User user) {
        return new UserDto(
            user.getId().getValue(),
            user.getEmail().getValue(),
            user.getName(),
            user.isActive(),
            user.getCreatedAt()
        );
    }
}

3.4 数据传输对象 (DTOs)

// application/dto/CreateUserDto.java
package com.company.project.application.dto;

public class CreateUserDto {
    private String email;
    private String name;
    
    public CreateUserDto() {}
    
    public CreateUserDto(String email, String name) {
        this.email = email;
        this.name = name;
    }
    
    // Getters and Setters
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}
// application/dto/UserDto.java
package com.company.project.application.dto;

import java.time.LocalDateTime;

public class UserDto {
    private final String id;
    private final String email;
    private final String name;
    private final boolean active;
    private final LocalDateTime createdAt;
    
    public UserDto(String id, String email, String name, boolean active, LocalDateTime createdAt) {
        this.id = id;
        this.email = email;
        this.name = name;
        this.active = active;
        this.createdAt = createdAt;
    }
    
    // Getters
    public String getId() { return id; }
    public String getEmail() { return email; }
    public String getName() { return name; }
    public boolean isActive() { return active; }
    public LocalDateTime getCreatedAt() { return createdAt; }
}

4. 基础设施层实现

4.1 JPA实体 (JPA Entities)

// infrastructure/persistence/entity/UserJpaEntity.java
package com.company.project.infrastructure.persistence.entity;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class UserJpaEntity {
    @Id
    @Column(name = "id", length = 36)
    private String id;
    
    @Column(name = "email", unique = true, nullable = false)
    private String email;
    
    @Column(name = "name", nullable = false, length = 100)
    private String name;
    
    @Column(name = "active", nullable = false)
    private boolean active;
    
    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;
    
    protected UserJpaEntity() {}
    
    public UserJpaEntity(String id, String email, String name, boolean active, 
                        LocalDateTime createdAt, LocalDateTime updatedAt) {
        this.id = id;
        this.email = email;
        this.name = name;
        this.active = active;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }
    
    // Getters and Setters
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public boolean isActive() { return active; }
    public void setActive(boolean active) { this.active = active; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
    public LocalDateTime getUpdatedAt() { return updatedAt; }
    public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

4.2 仓储实现 (Repository Implementation)

// infrastructure/repository/UserRepositoryImpl.java
package com.company.project.infrastructure.repository;

import com.company.project.domain.entity.User;
import com.company.project.domain.repository.UserRepository;
import com.company.project.domain.valueobject.Email;
import com.company.project.domain.valueobject.UserId;
import com.company.project.infrastructure.persistence.entity.UserJpaEntity;
import com.company.project.infrastructure.persistence.mapper.UserMapper;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Repository
public class UserRepositoryImpl implements UserRepository {
    private final UserJpaRepository jpaRepository;
    private final UserMapper mapper;
    
    public UserRepositoryImpl(UserJpaRepository jpaRepository, UserMapper mapper) {
        this.jpaRepository = jpaRepository;
        this.mapper = mapper;
    }
    
    @Override
    public Optional<User> findById(UserId id) {
        return jpaRepository.findById(id.getValue())
                           .map(mapper::toDomain);
    }
    
    @Override
    public Optional<User> findByEmail(Email email) {
        return jpaRepository.findByEmail(email.getValue())
                           .map(mapper::toDomain);
    }
    
    @Override
    public List<User> findByNameContaining(String name) {
        return jpaRepository.findByNameContainingIgnoreCase(name)
                           .stream()
                           .map(mapper::toDomain)
                           .collect(Collectors.toList());
    }
    
    @Override
    public void save(User user) {
        UserJpaEntity entity = mapper.toJpaEntity(user);
        jpaRepository.save(entity);
    }
    
    @Override
    public void delete(UserId id) {
        jpaRepository.deleteById(id.getValue());
    }
    
    @Override
    public boolean existsByEmail(Email email) {
        return jpaRepository.existsByEmail(email.getValue());
    }
}

interface UserJpaRepository extends JpaRepository<UserJpaEntity, String> {
    Optional<UserJpaEntity> findByEmail(String email);
    boolean existsByEmail(String email);
    List<UserJpaEntity> findByNameContainingIgnoreCase(String name);
}

4.3 映射器 (Mapper)

// infrastructure/persistence/mapper/UserMapper.java
package com.company.project.infrastructure.persistence.mapper;

import com.company.project.domain.entity.User;
import com.company.project.domain.valueobject.Email;
import com.company.project.domain.valueobject.UserId;
import com.company.project.infrastructure.persistence.entity.UserJpaEntity;
import org.springframework.stereotype.Component;

@Component
public class UserMapper {
    
    public User toDomain(UserJpaEntity entity) {
        return new User(
            new UserId(entity.getId()),
            new Email(entity.getEmail()),
            entity.getName(),
            entity.getCreatedAt(),
            entity.getUpdatedAt(),
            entity.isActive()
        );
    }
    
    public UserJpaEntity toJpaEntity(User user) {
        return new UserJpaEntity(
            user.getId().getValue(),
            user.getEmail().getValue(),
            user.getName(),
            user.isActive(),
            user.getCreatedAt(),
            user.getUpdatedAt()
        );
    }
}

5. 接口层实现

5.1 REST控制器 (REST Controller)

// interfaces/rest/UserController.java
package com.company.project.interfaces.rest;

import com.company.project.application.dto.CreateUserDto;
import com.company.project.application.dto.UserDto;
import com.company.project.application.service.UserApplicationService;
import com.company.project.domain.exception.DuplicateEmailException;
import com.company.project.domain.exception.InvalidEmailException;
import com.company.project.interfaces.dto.CreateUserRequest;
import com.company.project.interfaces.dto.UserResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserApplicationService userApplicationService;
    
    public UserController(UserApplicationService userApplicationService) {
        this.userApplicationService = userApplicationService;
    }
    
    @PostMapping
    public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
        try {
            CreateUserDto dto = new CreateUserDto(request.getEmail(), request.getName());
            UserDto userDto = userApplicationService.createUser(dto);
            UserResponse response = mapToResponse(userDto);
            return ResponseEntity.status(HttpStatus.CREATED).body(response);
        } catch (InvalidEmailException | DuplicateEmailException e) {
            return ResponseEntity.badRequest().build();
        }
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUser(@PathVariable String id) {
        return userApplicationService.getUserById(id)
                                   .map(this::mapToResponse)
                                   .map(ResponseEntity::ok)
                                   .orElse(ResponseEntity.notFound().build());
    }
    
    private UserResponse mapToResponse(UserDto dto) {
        return new UserResponse(
            dto.getId(),
            dto.getEmail(),
            dto.getName(),
            dto.isActive(),
            dto.getCreatedAt()
        );
    }
}

5.2 请求/响应DTO

// interfaces/dto/CreateUserRequest.java
package com.company.project.interfaces.dto;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

public class CreateUserRequest {
    @NotBlank(message = "Email is required")
    @Email(message = "Invalid email format")
    private String email;
    
    @NotBlank(message = "Name is required")
    @Size(max = 100, message = "Name cannot exceed 100 characters")
    private String name;
    
    // Getters and Setters
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

6. 配置类

6.1 Spring配置

// infrastructure/config/DomainConfig.java
package com.company.project.infrastructure.config;

import com.company.project.domain.repository.UserRepository;
import com.company.project.domain.service.UserDomainService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DomainConfig {
    
    @Bean
    public UserDomainService userDomainService(UserRepository userRepository) {
        return new UserDomainService(userRepository);
    }
}

6.2 maven依赖

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

7. 总结

这种Java DDD实现具有以下特点:

  1. 清晰的分层架构:严格按照DDD分层原则组织代码
  2. 丰富的类型系统:使用值对象确保数据完整性
  3. 领域逻辑封装:业务规则集中在领域层
  4. 依赖倒置:基础设施层实现领域层定义的接口
  5. 事件驱动:支持领域事件处理
  6. 可测试性:每层都可以独立进行单元测试
CodeCrafter 版权所有
使用 Hugo 构建
主题 StackJimmy 设计