服务层是否应该接受来自控制器的DTO或自定义请求对象?

Vin*_*ino 6 rest dto service-layer

顾名思义,设计服务层时的最佳实践是什么?我确实知道服务层应始终返回DTO,以便将域(实体)对象保留在服务层内。但是,控制器对服务层的输入应该是什么?

我在下面提出了三个建议:

方法1:在这种方法中,域对象(项)保留在服务层中。

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(IntemDTO dto)
    {
        // service layer returns a DTO object and accepts a DTO object
        return service.createItem(dto);
    }
}
Run Code Online (Sandbox Code Playgroud)

方法2:这是服务层接收自定义请求对象的地方。我已经在AWS Java SDK和Google Cloud Java API中广泛地看到了这种模式

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(CreateItemRequest request)
    {
        // service layer returns a DTO object and accepts a custom request object
        return service.createItem(request);
    }
}
Run Code Online (Sandbox Code Playgroud)

方法3:服务层接受DTO并返回域对象。我不喜欢这种方法。但是它已在我的工作场所中广泛使用。

class Controller
{
    @Autowired
    private ItemService service;

    public ItemDTO createItem(CreateItemRequest request)
    {
        // service layer returns a DTO object and accepts a DTO object
        Item item = service.createItem(request);
        return ItemDTO.fromEntity(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果上述三种方法都不正确或不是最佳方法,请就最佳实践向我提出建议。

pla*_*alx 9

从概念上讲,您希望能够跨表示层和通过不同的访问端口(例如,控制台应用程序通过 Web 套接字与您的应用程序通信)重用服务/应用程序层。此外,您不希望每个域更改都冒泡到应用程序层之上的层中。

控制器在概念上属于表示层。因此,您不希望应用层与在定义控制器的同一概念层中定义的契约耦合。您也不希望控制器依赖于域,否则它可能必须在域变化。

您需要一个解决方案,其中应用程序层方法契约(参数和返回类型)以任何 Java 本机类型或在服务层边界中定义的类型表示。

如果我们从 Vaughn Vernon获取IDDD 示例,我们可以看到他的应用程序服务方法契约是在 Java 原生类型中定义的。鉴于他使用 CQRS,他的应用程序服务命令方法也没有产生任何结果,但我们可以看到查询方法确实返回了应用程序/服务层包中定义的 DTO。

在上面列出的3种方法中,哪些是正确的/错误的?

#1 和 #2 非常相似,从依赖的角度来看可能是正确的,只要ItemDtoCreateItemRequest是在应用程序层包中定义的,但我更喜欢#2,因为输入数据类型是针对用例而不是命名的只是它处理的实体类型:entity-naming-focus 更适合 CRUD,因此您可能会发现很难为在同一类型实体上运行的其他用例方法的输入数据类型找到好的名称。#2 也通过 CQRS(其中命令通常发送到命令总线)得到普及,但不是 CQRS 独有的。Vaughn Vernon 在IDDD 样本中也使用了这种方法。请注意,您所说的request通常称为command

然而,#3 并不理想,因为它将控制器(表示层)与域耦合。

例如,某些方法接收 4 或 5 个参数。根据 Eric Evans 在 Clean Code 中的说法,必须避免使用此类方法。

这是一个很好的指导方针,我并不是说样本不能改进,但请记住,在 DDD 中,重点是根据无处不在的语言 (UL) 命名并尽可能地遵循它. 因此,仅仅为了将参数组合在一起而将新概念强加到设计中可能是有害的。具有讽刺意味的是,尝试这样做的过程可能仍然提供一些很好的见解,并允许发现可以丰富 UL 的被忽视和有用的领域概念。

PS:Robert C. Martin 写了 Clean Code,而不是 Eric Evans 写的那本蓝皮书。


Ank*_*jay 7

我来自C#背景,但这里的概念保持不变。

在这种情况下,我们必须将参数/状态从应用层传递到服务层,然后从服务层返回结果,我倾向于关注分离。服务层不需要知道Request你的应用层/控制器的参数。同样,您从服务层返回的内容不应与您从控制器返回的内容相结合。这些是不同的层次、不同的要求、不同的关注点。我们应该避免紧耦合。

对于上面的例子,我会做这样的事情:

class Controller
{
     @Autowired
     private ItemService service;

     public ItemResponse createItem(CreateItemRequest request)
     {
        var creatItemDto = GetDTo(request);
        var itemDto = service.createItem(createItemDto);
        return GetItemResponse(itemDto);
    }
}
Run Code Online (Sandbox Code Playgroud)

这可能需要更多的工作,因为现在您需要编写额外的代码来转换不同的对象。但是,这为您提供了极大的灵活性并使代码更易于维护。例如:CreateItemDtoCreateItemRequest. 在这种情况下,您不需要在Request对象中公开这些字段。您只Data Contract向客户公开您的信息,仅此而已。同样,您只将相关字段返回给客户端,而不是从服务层返回的内容。

如果您想避免在C#DtoRequest objectsC#之间进行手动映射,则具有像AutoMapper. 在java世界中,我确信应该有一个等价物。可能ModelMapper可以提供帮助。