同一实体的多个Dtos

时间:2018-05-28 06:12:25

标签: c# .net asp.net-web-api2 api-design

在不同的API端点中为同一实体使用多个DTO是一种好习惯。例如: 我有一个api端点,它接受以下Dto:

public class AddressDto
{
    public string City { get; set; }
    public string Country { get; set; }
    public string Contact { get; set; }
    public string Street1 { get; set; }
    public string Street2 { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

现在有第二个Api接受相同的dto,但在api通话中,我只使用Streer1, Street2, Contact所有其他都被忽略。

我应该为第二个DTO制作另一个api endpoint,如:

public class AddressDtoForSecondAPI
{
    public string Contact { get; set; }
    public string Street1 { get; set; }
    public string Street2 { get; set; }
}

1 个答案:

答案 0 :(得分:1)

简而言之,是的,这是可以接受的。

但是,正如您在评论和其他答案中所看到的,并非所有人都同意这里。让我解释一下我的答案。

参数1 - 误导消费者

  

现在有第二个Api接受相同的dto,但在api通话中,我只使用Streer1, Street2, Contact所有其他都被忽略。

这里的问题是让你的意图明确。如果您允许消费者向您发送完全充实的AddressDTO,但只使用属性的子集,那么您就会误导您的消费者。您已让他们认为其他属性是相关的。

这实际上与:

相同
public int AddNumbersTogether(int a, int b, int c, int d)
{
    return a + c + d; //we ignore b
}

b没有理由存在。当AddNumbersTogether(1,2,3,4)返回值8时,使用此方法的任何人都会摸不着头脑。语法与行为相矛盾。

是的,省略未使用的方法参数比开发第二个DTO更容易。但是你需要在这里保持一致并坚持同样的原则:不要误导消费者

参数2 - DTO不是实体

您的消费者与您的API的互动需要在消费者不了解您的数据库记录结构的情况下进行。

这就是您开始使用DTO而不是实体类的原因!您在采取行动和存储该行动的数据之间提供了逻辑上的分离。

消费者并不关心数据存储的位置。无论您是将街道存储在与地址相同的表中,还是将不同的表(或数据库)存储在同一个表中,在调用API方法的消费者范围内无关紧要

参数3 - 反击S.Akbari

  

SOLID 中的继承和/或接口隔离原则怎么样? - S.Akbari

对于这种特殊情况,这些不是有效的参数。

继承是一种有缺陷的方法。是的,您可以技术在发布的示例代码中执行类似AddressDto : AddressDtoForSecondAPI的操作,但这是一个巨大的代码味道。
当需要第三个DTO时会发生什么,例如一个只使用邮政编码和城市名称的地方?您无法AddressDto从多个来源继承,AddressDtoForSecondAPI与新创建的AddressDtoForThirdAPI之间不存在逻辑重叠。

接口不是此处的解决方案。是的,您可以在技术上创建一个包含相应字段的IAddressDtoForSecondAPIIAddressDtoForThirdAPI界面,然后执行AddressDto : IAddressDtoForSecondAPI, IAddressDtoForThirdAPI之类的操作。然而,这又是一种巨大的代码味道。

如果第二个和第三个变体有一些共享属性,并且有几个单独的属性,会发生什么?如果应用接口隔离,则需要在接口中自行抽象重叠属性 如果那时第四个变体呈现出来,它有一些与第二个变化相同的属性,一些具有第三个变化,一些具有第二个和第三个变化,以及一些单独的属性,那么你将需要创建更多接口!

给出相同实体的足够变化并反复应用界面隔离原则;您将最终获得该实体的每个属性的接口;这需要大量的锅炉电镀。你最终会得到类似的东西:

公共类AddressDto:IAddressCity,IAddressCountry,IAddressContact,IAddressStreet1,IAddressStreet2,IAddressState,IAddressZip {     public string City {get;组; }     public string Country {get;组; }     public string联系{get;组; }     public string Street1 {get;组; }     public string Street2 {get;组; }     public string State {get;组; }     public string Zip {get;组; } }

想象一下,必须为所有课程做到这一点;因为相同的原则适用于API使用的每个DTO。

参数4 - DRY不适用

我有点理解你为什么要担心创建两个课程。最有可能的是,你脑子里会出现DRY / WET错误标志。

避免WET是一种很好的反应;但你不能总是听它。因为如果你真的避免重复,那么你也应该有效地不创建单独的实体和DTO类,因为它们通常是彼此的复制/粘贴。

DRY不是绝对的。以实体/ DTO为例,这里有一个平衡点:

  • 你想不惜一切代价避免重复吗? (=干)
  • 您想将DAL与API逻辑分开吗? (=关注点分离)

在这种情况下,后者通常会胜出。

同样的论点适用于您的情况。 DRY之后反对的参数(我刚刚列出的参数)超过了在这种情况下跟随DRY的好处。