【问题标题】:Software Architecture question. BL creates DTOs differently by type. Is there anything better? [closed]软件架构问题。 BL 根据类型创建不同的 DTO。有更好的吗? [关闭]
【发布时间】:2010-11-01 23:13:16
【问题描述】:

我有一个包含三层的应用程序: 1.数据层:暴露Entity Framework实体 2.业务逻辑层:查询EF模型,获取实体,暴露DTO(数据传输对象) 3. UI层:查询BL,获取DTO,查看。

现在我有一个问题。我的应用程序的不同部分需要相同的 DTO,但字段略有不同。为简单起见,假设我的 BL 类中的一个公开了一个名为 Person 的 DTO,它需要 Name 和 Surname 一次,而 Name 和 Date Of Birth 在其他地方。

我想听听您对我的简单解决方案的看法。我的 UI 必须与 BL 就“DTO 合同”达成一致,以便两层就类达成一致。在我的例子中,我会:

a) 创建一个抽象的 Person 类。此类没有任何方法或字段 b) 在 BL 中创建一个名为 GetPerson 的方法,该方法接受 Person 类作为参数 c) 定义两个或多个派生自 Person 的类(假设 PersonName 和 PersonDOB) d) 我的 UI 调用 GetPerson 传入所需的类型(如 GetPerson(typeof(PersonName))) e) BL填写Person类

你怎么看?有没有更好的解决方案?我认为这不是那么好买没有更好的东西出现在我的脑海中。

非常感谢。 马可

【问题讨论】:

    标签: design-patterns architecture prototype dto


    【解决方案1】:

    我的 MVC2 应用程序中有类似的架构。我做了什么:

    1. BL 返回一个包含 Name、Surname 和 DoB 的 Person DTO(所有这些属性都是 Person 的一部分)

    2. 我的视图与模型一起使用,因此我为每个视图创建特定模型。因此,我将有 2 个 Person 模型,即。一个有名字和姓氏,另一个有名字和出生日期。

    DTO 和模型之间的转换是由一组我称之为适配器的类完成的。为了简化代码,我使用 Automapper。这是一段出色的代码,它将通过考虑命名约定和显式配置将属性从 DTO 复制到模型。请看一下 is,因为您可能希望使用它来填充 EF 类中的 DTO。

    总结一下,我有一个一致的 BL,没有任何异味(“给我这种类型的子类”业务对我来说有点异味),我的观点是使用仅包含相关数据的强类型模型.

    【讨论】:

    • 感谢 Jakub 的意见。事实上,我使用 Automapper 将实体映射到 DTO,但问题是我需要在我的应用程序的不同位置对相同数据进行非常不同的视图。我无法创建作为所需 DTO 联合的 DTO,因为我将结束使用实体。我需要调整我的查询,所以我只需要为 UI 选择所需的信息。
    • @Marconline - 在那种情况下,我会像你一样创建几个 DTO,但会伴随着具体方法(GetPersonFull、GetPersonBasic)返回这些具体类型,而不将 Type 作为参数传递给单个方法。或者按照 Adrian 的建议,使用接口仅公开对象的某些部分,而不是具体的类。
    • 你为什么不只创建一个具有该类型的方法?我的意思是:更高级别必须知道 DTO 结构,所以我认为我们所说的与 GetPersonFull 或 GetPersonBasic 没有什么不同。我认为界面的东西在理论上是完美的,但在实践中并不是那么好。我的 DTO 很大,我最终会得到很多接口……为了什么?
    • 还是谢谢你,我最后的评论没有感谢你的时间。马可
    • @Marconline - “为什么?” - 因为你可以调用传递完全无效类型的方法来获取运行时异常。我总是更喜欢编译时错误而不是运行时异常。 '为了什么?' - 我认为这是您的要求:不要在许多地方公开包含大量数据的完整 DTO...
    【解决方案2】:

    有几件事:首先是术语。我不想成为一个术语纳粹 - 只是有一些细微的差异可能会影响你对问题的看法。

    DTO 与 POCO

    当您谈到 DTO(数据传输对象)时,它听起来更像是 POCO(普通旧 CLR 对象)。

    POCO:

    • 是一种用于在层之间传递信息的简单数据结构。
    • 这通常发生在应用程序中;特别是 .Net 应用程序(托管代码)。
    • 它们通常在设计时考虑到SRP(单一职责原则)。

    在这种情况下(特定 POCO 的设计)SRP 将意味着“业务”驱动的使用案例(即:在搜索结果中显示“人员”数据的摘要)或通用/以数据为中心的案例(即:提供一个“人”)。

    DTO:

    直到最近我还盲目地假设 POCO == DTO;当然,很多人(我认为)倾向于那样谈论它们;然后我阅读了 Martin Fowler 对 DTO 的定义,这是不同的。

    • 如果在应用程序的层之间使用 POCO,则在应用程序之间使用 DTO。
    • 基本原理是,如果您必须通过网络发送数据 - 并且该调用很昂贵 - 那么与其进行多次调用以传递多个对象/数据/无论您将它们全部包装到一个实体中并进行一次调用,不如进行一次调用。该实体就是 DTO。

    因此,可以想象,您可能会使用 DTO 将多个 POCO 传递给外部服务。

    问题的答案

    在设计 POCO(以及层和组件之间的接口)时,您的首要考虑应该是“为什么”?在设计 POCO(和 DTO)时,您会考虑一些不同的观点和动机:

    • 责任:谁“拥有” POCO? (即:哪个系统拥有其中的数据)。
    • 用例:POCO 是否存在,因为它以一般/通用方式有意义,或者,它是否用于特定/专门目的?
    • 性能和其他运行时系统质量:DTO 肯定会受到性能考虑的影响,并且可以想象,在层之间交换信息时,类似的想法也可以应用于 POCO。

    因此,您可以考虑以下两种方法...

    使用具体 POCO 驱动责任

    设计和构建一个 POCO(如在实际的类或结构中)来完成您想要的工作;这将沿着上面讨论的路线(“业务”或“通用/以数据为中心”)。

    这就是我目前做事的方式。我经常有一个“胖” POCO,它完整地定义了一个实体(有时包括其他 POCO),以及一个用于列表的“瘦” POCO。我也倾向于使用单独的 POCO 进行保存和更新。

    虽然将 POCO 的设计方式标准化是有意义的(例如:它们都由域模型驱动,并且每个实体只有一个 POCO) - 但事实是,您会有不同的动机来自不同的方向;我的建议是屈服于这一点,否则你最终会得到一个不灵活、易于维护或高性能的系统。

    使用接口

    这是一种更“纯粹”的方法。而不是让您的应用程序围绕使用接口传递具体的 POCO。然后,当您构建实现 POCO 时,它可以实现任意数量的这些接口。例如:

    public interface PersonID
    {
        Guid PersonID { get; }
    }
    
    public interface PersonFullName
    {
        string FirstName { get; }
        string Lastname { get; }
        string Honorific { get; }
    }
    
    public interface PersonDateOfBirth
    {
        DateTime DateOfBirth { get; }
    }
    

    奖励积分

    您没有要求这个(至少不是直接要求),但我不会将我的 BL 绑定到 EF - 事实上我根本不会将它绑定到数据访问层。如果你这样做了,任何想要使用你的 BL 的东西也将与 EF 绑定。您可能需要考虑Dependency Inversion。

    【讨论】:

    • 亲爱的 Adrian,感谢您在 DTO 和 POCO 上的精确性。我很高兴阅读您引用的文章。感谢您的解决方案:如果我理解得很好,您基本上是在说我在想什么(即创建不同的 POCO)。这是正确的吗?谢谢,马可
    • @Marconline,是的 - 你在正确的轨道上 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 2013-02-27
    • 1970-01-01
    • 2021-08-30
    • 2010-10-04
    • 1970-01-01
    相关资源
    最近更新 更多