一种常见的方法是将 DTO 转换逻辑拆分为自己的类。根据项目的大小,创建存储库类也可能很有用。这给我们留下了三个类:
-
控制器:执行 REST 操作
-
存储库:从数据库或数据访问对象 (DAO) 中获取 DTO
-
DTO 映射器:将 DTO 转换为域对象
存储库允许我们对控制器完全隐藏 DTO。相反,控制器将只处理域对象,并且根本不知道发生了从 DTO 到域对象的转换。
存储库(假设从FooDto 对象创建的Foo 域对象)是:
public Foo {
private long id;
private String name;
// ...getters & setters...
}
public interface FooRepository {
public List<Foo> findAll();
public Optional<Foo> findById(long id);
public Foo create(long id, String name);
public Foo update(long id, String name);
public void delete(long id);
}
DTO 转换逻辑是:
public class FooDto {
private long id;
private String name;
// ...getters & setters...
}
public class FooDtoMapper {
public Foo fromDto(FooDto dto) {
Foo foo = new Foo();
foo.setId(dto.getId();
foo.setName(dto.getName();
return foo;
}
public FooDto toDto(Foo foo) {
FooDto dto = new FooDto();
dto.setId(foo.getId();
dto.setName(foo.getName();
return dto;
}
}
创建FooDtoMapper 后,我们可以创建FooRepository 实现:
public class DatabaseFooRepository implements FooRepository {
@Inject
private DatabaseConnection dbConnection;
@Inject
private FooDtoMapper mapper;
@Override
public List<Foo> findAll() {
return dbConnection.getAllFromCollection("FOO", FooDto.class)
.stream()
.map(mapper::fromDto)
.collect(Collectors.toList());
}
// ...implement other methods
}
dbConnection 对象是从中提取 DTO 的数据库的抽象。在此示例中,我们可以假设 getAllFromCollection("FOO", FooDto.class) 返回一个 List<FooDto>,然后我们使用 FooDtoMapper 对象 (mapper) 将其流式传输并转换为 List<Foo>。在您的项目中,这可能会被替换为 JPA 特定的代码,但原理仍然相同:从 JPA 接口获取 DTO,并使用 mapper 对象将它们转换为域对象。
这导致以下控制器逻辑:
@Path("foo")
@Controller
public class FooController {
@Inject
private FooRepository repository;
@GET
public Response findAll() {
List<Foo> foos = repository.findAll();
Response.ok(foos);
}
// ...other controller methods...
}
使用这种模式,我们将 DTO 转换为自己的类的逻辑抽象出来,控制器只负责处理域对象。
一般来说,最好有许多简单的类来做一件事,而不是将所有逻辑放在一个类中(如您的原始控制器)以希望减少类的数量。例如,FooDtoMapper 只负责将FooDto 对象转换为Foo 对象,反之亦然。 DatabaseFooRepository 仅负责从数据库中获取 DTO 并使用 FooDtoMapper 将 DTO 转换为域对象(即从数据库中获取域对象)。最后,FooController 只负责从FooRepository 获取域对象(即运行时的DatabaseFooRepository)并提供必要的REST API 元数据(即HTTP 状态OK)。
请注意,在这种情况下,Foo 和 FooDto 对象是相同的,没有太多理由将 Foo 和 FooDto 对象分开(即,为什么不只存储 Foo数据库中的对象而不是 FooDto 对象?),但情况并非总是如此。通常,域对象和 DTO 会有所不同。例如,域对象可能具有必须转换为 String 或其他可以存储在数据库中的数据结构的货币金额或日期(DTO 将具有此 String 字段,而域对象将具有实际字段,例如货币或日期)。在本例中,为了简单起见,我将域对象和 DTO 设置为相同。