DTO命名约定、建模和继承

Posted

技术标签:

【中文标题】DTO命名约定、建模和继承【英文标题】:DTO naming conventions , modeling and inheritance 【发布时间】:2013-09-20 23:44:49 【问题描述】:

我们正在使用 AngularJS、C#、ASP.Net Web API 和 Fluent NHibernate 构建一个 Web 应用程序。 我们决定使用 DTO 将数据传输到表示层(角度视图)。 我对 DTO 的一般结构和命名有一些疑问。 这是一个示例来说明我的情况。 假设我有一个名为 Customer 的域实体,它看起来像:

public class Customer
    
        public virtual int Id  get; set; 
        public virtual string Name  get; set; 
        public virtual Address Address  get; set; 
        public virtual ICollection<Account> Accounts  get; set; 
    

现在,在我的视图/表示层中,我需要检索不同风格的客户,例如:

1) 只是 ID 和名称 2)身份证,姓名和地址 3) 身份证、姓名、地址和账户

我创建了一组 DTO 来完成此任务:

public class CustomerEntry

    public  int Id  get; set; 
    public  string Name  get; set; 


public class CustomerWithAddress : CustomerEntry

    public AddressDetails Address  get; set; 


public class CustomerWithAddressAndAccounts : CustomerWithAddress

    public ICollection<AccountDetails> Accounts  get; set; 

AddressDetails 和 AccountDetails 是具有相应域实体的所有属性的 DTO。

这适用于查询和数据检索;问题是我用什么来插入和更新。在创建新客户记录期间,姓名和地址是必需的,帐户是可选的..所以换句话说,我需要一个包含所有客户属性的对象。因此混乱:

1) 我用什么来插入和更新? CustomerWithAddressAndAccounts DTO 包含所有内容,但它的名称似乎有点难以用于插入/更新。

2) 我是否要创建另一个 DTO .. 如果我这样做了,那不是重复吗,因为新的 DTO 将与 CustomerWithAddressAndAccounts 完全一样?

3) 最后但并非最不重要的一点是,上面描述的 DTO 继承结构似乎很适合需求?还有其他方法可以对此建模吗?

我已经浏览过有关此主题的其他帖子,但没有取得太大进展。 我做的一件事是避免在类名中使用后缀“DTO”。 我觉得有点多余。

很想听听你的想法

谢谢

【问题讨论】:

【参考方案1】:

建议您应该为每个后缀为 DTO 的实体设置一个 DTO 类,例如CustomerEntryDTO 用于 Customer entity(但您当然可以根据选择和要求使用继承层次结构)。

此外,添加一个抽象的DTOBase 类的基类或接口;并且不要对要包含在子 DTO 中的每个地址、帐户和其他属性使用如此深的继承层次结构。而是将这些属性包含在同一 CustomerEntryDTO 类中(如果可能),如下所示:

[Serializable]
public class CustomerEntryDTO : DTOBase, IAddressDetails, IAccountDetails

    public  int Id  get; set; 
    public  string Name  get; set; 
    public AddressDetails Address  get; set;  //Can remain null for some Customers
    public ICollection<AccountDetails> Accounts  get; set;  //Can remain null for some Customemer

此外,您的 DTO 应该可序列化以跨进程边界传递。

更多关于 DTO 模式,请参考以下文章:

Data Transfer Object

MSDN

编辑: 如果您不想通过网络发送某些属性(我知道您需要有条件地这样做,因此需要对此进行更多探索),您可以使用诸如 NonSerialized 之类的属性将它们从序列化机制中排除(但它仅适用于字段而不适用于属性,请参阅使用属性的解决方法文章:NonSerialized on property)。 您还可以创建自己的自定义属性,例如ExcludeFromSerializationAttribute,并将其应用于您不想根据某些规则/条件每次通过网络发送的属性。 另请参阅: Conditional xml serialization

编辑 2: 使用接口来分隔一个CustomerEntryDTO 类中的不同属性。请参阅 Google 或 MSDN 上的接口隔离原则。稍后我将尝试提供示例说明。

【讨论】:

感谢您的回复。使用多个 DTO 类的动机之一是在定义 DTO 的用途时更具表现力。另外,如果您只需要 Id 和 Name ,您真的应该通过网络发送完整的 CustomerEntryDTO(您已经描述过)吗? 很好的回复,但我认为不需要在 DTO 上应用接口。那有什么用?对于具有实际逻辑的对象而不是简单的数据结构,ISP 与其他 SOLID 搭配使用效果很好。 在 .NET 世界中,用 DTO 为您的类添加后缀是愚蠢的,这很简单。我们倾向于将实体用于存储库对象,将模型用于从 API 返回的对象,而对于服务对象,它们通常不使用任何前缀。公平地说,所有这些层都使用 DTO,所以我们应该开始命名实体 XptoDtoEntities 吗?!去掉那个 DTO 名字太丑了。 @Marco 后缀在将视图模型映射到实体时很有用,并且在此过程中导入两个层的类时不希望名称冲突。 我只是引用了你的话,证明你错了。讨论结束。【参考方案2】:

我用什么来插入和更新?

    服务运营的定义通常与业务运营密切相关。业务语言不涉及“插入”和“更新”,服务也不涉及。

    客户管理服务可能有一些Register 操作,它接受客户名称和可能的其他一些可选参数。

我要创建另一个 DTO 吗?

是的,您应该创建另一个 DTO。

有时服务操作合同可能就足够了,不需要为特定操作定义单独的 DTO:

function Register(UserName as String, Address as Maybe(of String)) as Response

但大多数时候最好为单个服务操作定义一个单独的 DTO 类:

class RegisterCommand
    public UserName as String
    public Address as Maybe(of String)
end class

function Register(Command as RegisterCommand) as Response

RegisterCommand DTO 可能看起来与CustomerWithAddress DTO 非常相似,因为它具有相同的字段,但实际上这两个 DTO 具有非常不同的含义并且不能相互替代。

例如,CustomerWithAddress 包含AddressDetails,而简单的String 地址表示可能足以注册客户。

为每个服务操作使用单独的 DTO 需要更多时间来编写,但更易于维护。

【讨论】:

【参考方案3】:

从您的第 1 项开始,对于插入和更新,最好使用命令模式。根据 CQRS,您不需要 DTO。考虑这个模式: 通过blogs.msdn.com

【讨论】:

您仍然需要 DTO 来处理查询方面的问题,所以这不是一个好的答案。 这就像建议某人买摩托车而他想要汽车.. @sotn 摩托车很有趣,虽然

以上是关于DTO命名约定、建模和继承的主要内容,如果未能解决你的问题,请参考以下文章

从基类 (DTO) 继承多个类是不是违反了任何 SOLID 原则?

是否可以在 NestJs 中为 DTO 进行继承?

Hibernate 继承建模

如何在函数式编程中建模继承关系

Objective-C:访问继承类中的私有属性

JavaScript 中原型继承的约定