簡體   English   中英

如何分離dto映射邏輯

[英]How to separate the dto mapping logic

我在代碼中組織映射方法時遇到了一些問題。 我正在 controller 層中執行映射邏輯,但某些實體需要對每個操作(插入、更新和刪除)使用不同的 dto。

我創建了一個具有 2 種類型的通用 controller:原始實體和 dto 類型。 但是在這種情況下,這會導致它使用多個 dto 表示。 我不確定創建 3 個泛型類型是否是處理此問題的好方法。

另一個問題是我的 controller 層隨着許多映射方法變得越來越大。 即使使用 ModelMapper 作為自動映射器,在某些情況下我更喜歡自己做而不是創建復雜的轉換器類。

如何組織我的 dto 映射代碼,並且不讓我的 controller 使用很多映射方法?

PS:我的項目是 rest api 與 jax-rs、cdi 和 jpa

我建議您遵循以下規則:

  • 將服務於 API 的 DTO 與其他域類分開,並使用命名約定來快速識別它(例如 XxxAPI)。 您可以在資源專用的 package 中組織:controller、DTO 和映射器類。
  • 不要害怕編寫大量代碼,尤其是在映射器類中,您可以使用一些 IDE 技巧來生成和測試它們。
  • 小心使用 automapper,太多的魔法是危險的,你可以考慮 Builder 模式來促進你的 DTO / Domain 映射

問候。

一種常見的方法是將 DTO 轉換邏輯拆分為自己的 class。 根據項目的大小,創建存儲庫 class 也可能有用。 這給我們留下了三個類:

  1. Controller :執行 REST 操作
  2. 存儲庫:從數據庫或數據訪問 Object (DAO) 獲取 DTO
  3. DTO 映射器:將 DTO 轉換為域 object

該存儲庫允許我們完全隱藏 controller 中的 DTO。 相反,controller 將只處理域對象,並且根本不知道從 DTO 到域 object 的轉換。

存儲庫(假設是從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 object 是從中提取 DTO 的數據庫的抽象。 在此示例中,我們可以假設getAllFromCollection("FOO", FooDto.class)返回一個List<FooDto> ,然后我們將其 stream 並使用FooDtoMapper object ( mapper )轉換為List<Foo> 在您的項目中,這可能會替換為特定於 JPA 的代碼,但原理仍然相同:從 JPA 接口獲取 DTO,並使用mapper object 將它們轉換為域對象。

這導致以下 controller 邏輯:

@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 轉換為它自己的 class 的邏輯,而 controller 只負責處理域對象。

一般來說,最好有許多簡單的類來做一件事,而不是把所有的邏輯放在一個 class(就像你原來的控制器一樣)中,以期減少類的數量。 例如, FooDtoMapper只負責從FooDto對象轉換為Foo對象,反之亦然。 DatabaseFooRepository只負責從數據庫中獲取 DTO,並使用FooDtoMapper將 DTO 轉換為域對象(即從數據庫中獲取域對象)。 最后, FooController只負責從FooRepository獲取域對象(即運行時的DatabaseFooRepository )並提供必要的 REST API 元數據(即OK

請注意,在這種情況下, FooFooDto對象是相同的,沒有太多理由將FooFooDto對象分開(即,為什么不將Foo對象而不是FooDto對象存儲在數據庫中?),但是並非總是如此。 通常,域 object 和 DTO 會有所不同。 例如,域 object 可能具有必須轉換為String或其他可以存儲在數據庫中的數據結構的貨幣金額或日期(DTO 將具有此String字段,而域 object 將具有實際字段,例如作為貨幣或日期)。 在這種情況下,為簡單起見,我將域 object 和 DTO 設為相同。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM