You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重构领域模型:如何摆脱@PersistenceCreator等基础设施注解依赖?

摆脱领域类中Spring Data持久化注解依赖的重构思路

第一种思路:用工厂类封装领域对象的重建逻辑

核心是把从持久化层加载Ticket的逻辑,从领域类本身挪到领域层的工厂类里。仓库只负责获取数据库里的原始数据(比如用DTO或者Map),然后交给工厂来创建Ticket实例,这样领域类完全不需要任何持久化相关的注解。

调整后的领域类Ticket

把原来的私有全参构造器换成包级私有构造器,专门给同包下的工厂使用,同时保留创建新工单的公开构造器:

package com.kaue.ticketservice.domain.model;
import io.swagger.v3.oas.annotations.media.Schema;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.time.Instant;
import java.time.OffsetDateTime;

@ToString
public class Ticket {
    @Schema(description = "User email")
    @Getter
    final private String requesterEmail;
    @Schema(description = "Assignee / Agent email")
    @Getter @Setter
    private String assigneeEmail;
    @Schema(description = "Title of the ticket")
    @Getter
    final private String title;
    @Schema(description = "Ticket description")
    @Getter @Setter
    private String description;
    @Schema(description = "Create date")
    @Getter
    final private Instant createDate;
    @Getter @Setter
    private Instant updatedDate;
    @Schema(description = "Resolution date")
    @Getter @Setter
    private OffsetDateTime resolutionDate;

    // 创建新工单的公开构造器
    public Ticket(String requesterEmail, String title, String description) {
        this.requesterEmail = requesterEmail;
        this.title = title;
        this.description = description;
        this.createDate = Instant.now();
    }

    // 包级私有构造器,仅给同包工厂使用
    Ticket(String requesterEmail, String title, String description, Instant createDate) {
        this.requesterEmail = requesterEmail;
        this.title = title;
        this.description = description;
        this.createDate = createDate;
    }
}

领域层的Ticket工厂类

专门负责从持久化数据重建Ticket:

package com.kaue.ticketservice.domain.model;

import java.time.Instant;
import java.time.OffsetDateTime;

public class TicketFactory {
    public static Ticket fromPersistentData(String requesterEmail, String assigneeEmail, String title, String description, 
                                           Instant createDate, Instant updatedDate, OffsetDateTime resolutionDate) {
        Ticket ticket = new Ticket(requesterEmail, title, description, createDate);
        ticket.setAssigneeEmail(assigneeEmail);
        ticket.setDescription(description);
        ticket.setUpdatedDate(updatedDate);
        ticket.setResolutionDate(resolutionDate);
        return ticket;
    }
}

基础设施层的仓库实现

用MongoTemplate查询出持久化文档,再通过工厂转成领域类:

package com.kaue.ticketservice.infrastructure.persistence;

import com.kaue.ticketservice.domain.model.Ticket;
import com.kaue.ticketservice.domain.model.TicketFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;

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

public class TicketRepositoryImpl {
    private final MongoTemplate mongoTemplate;

    public TicketRepositoryImpl(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    public List<Ticket> findAll() {
        List<TicketDocument> documents = mongoTemplate.find(new Query(), TicketDocument.class);
        return documents.stream()
                .map(doc -> TicketFactory.fromPersistentData(
                        doc.getRequesterEmail(),
                        doc.getAssigneeEmail(),
                        doc.getTitle(),
                        doc.getDescription(),
                        doc.getCreateDate(),
                        doc.getUpdatedDate(),
                        doc.getResolutionDate()
                ))
                .collect(Collectors.toList());
    }
}

这里的TicketDocument是基础设施层的专属实体,只和MongoDB交互,领域层完全不依赖它。

第二种思路:用持久化DTO做中间层,隔离领域类和数据库

创建一个专门的持久化DTO(比如TicketDocument),让Spring Data直接映射到这个DTO上,然后在仓库实现里把DTO转换成领域类Ticket。这样领域类完全不用碰任何持久化相关的代码,保持纯净。

基础设施层的TicketDocument

package com.kaue.ticketservice.infrastructure.persistence;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;
import java.time.OffsetDateTime;

@Document(collection = "tickets")
@Getter @Setter
public class TicketDocument {
    @Id
    private String id;
    private String requesterEmail;
    private String assigneeEmail;
    private String title;
    private String description;
    private Instant createDate;
    @LastModifiedDate
    private Instant updatedDate;
    private OffsetDateTime resolutionDate;
}

仓库的转换逻辑

在仓库实现中,把查询到的TicketDocument转换成领域类Ticket,这里可以直接用工厂方法:

package com.kaue.ticketservice.infrastructure.persistence;

import com.kaue.ticketservice.domain.model.Ticket;
import com.kaue.ticketservice.domain.repository.TicketRepository;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Repository;

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

@Repository
public class MongoTicketRepository implements TicketRepository {
    private final MongoTemplate mongoTemplate;

    public MongoTicketRepository(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    @Override
    public List<Ticket> findAll() {
        List<TicketDocument> docs = mongoTemplate.find(new Query(), TicketDocument.class);
        return docs.stream()
                .map(doc -> TicketFactory.fromPersistentData(
                        doc.getRequesterEmail(),
                        doc.getAssigneeEmail(),
                        doc.getTitle(),
                        doc.getDescription(),
                        doc.getCreateDate(),
                        doc.getUpdatedDate(),
                        doc.getResolutionDate()
                ))
                .collect(Collectors.toList());
    }
}

第三种思路:调整构造器,让Spring Data自动识别,不用加注解

如果不想搞工厂或DTO,也可以调整领域类的构造器,让Spring Data MongoDB自动匹配。Spring Data会优先找参数名称和数据库字段名一致的构造器,只要编译时保留参数名称,就不用加@PersistenceCreator注解。

修改领域类构造器

去掉@PersistenceCreator注解,保留私有全参构造器:

// 移除@PersistenceCreator注解
private Ticket(String requesterEmail, String assigneeEmail, String title, String description, Instant createDate, Instant updatedDate, OffsetDateTime resolutionDate) {
    this.requesterEmail = requesterEmail;
    this.assigneeEmail = assigneeEmail;
    this.title = title;
    this.description = description;
    this.createDate = createDate;
    this.updatedDate = updatedDate;
    this.resolutionDate = resolutionDate;
}

配置编译参数,保留参数名称

Spring Data需要通过参数名称匹配字段,所以编译时要加上-parameters参数:

Maven配置

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <compilerArgs>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

Gradle配置

tasks.withType(JavaCompile) {
    options.compilerArgs.add('-parameters')
}

这样Spring Data MongoDB就能自动识别这个全参构造器,领域类也不用依赖持久化注解了。


内容的提问来源于stack exchange,提问作者Kaue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 04:09:52