【问题标题】:Dependency Injection vs Layered Architecture依赖注入与分层架构
【发布时间】:2013-07-30 00:31:53
【问题描述】:

我已经阅读了很多关于依赖注入和服务定位器(反?)模式的内容 - 很多都在 StackOverflow 上(谢谢大家 :)。我有一个关于这种模式在 n 层架构中如何工作的问题。

我看过很多博客文章,其中描述了将 IDataAccess 组件注入业务对象。例如

public class Address
{
    IDataAccess _dataAccess;
    public Address(IDataAccess dataAccess)
    {
        this._dataAccess = dataAccess;
    }
}

但是,我的印象是,在 n 层架构中,UI 层应该不需要任何数据访问层的知识......甚至不需要知道 /is/ 有数据访问层!如果 DI 需要在 BusinessObjects 的构造函数中公开 IDataAccess 接口,那么这会向 UI 公开业务层在后台使用数据访问层这一事实 - UI 肯定不需要知道或关心的事情?

所以,我的基本问题是:DI 是否要求我将所有下层接口暴露给所有上层,这是好事还是坏事?

谢谢

编辑:为了澄清(在几个 cmets 之后),我知道我的业务对象应该不知道它使用哪个特定的 实现 IDataAccess(因此将依赖项注入到构造函数中)但是我认为 BO 之上的层不应该知道业务对象甚至 需要 对 DAL 的依赖。

【问题讨论】:

  • 我真的不明白为什么您需要将任何类型的数据访问层传递给Address 类。数据和视图之间的“粘合”过程应该在宿主控制器/视图模型中完成(取决于架构),这意味着您可以将数据映射到上述控制器/视图模型中的业务对象。
  • 您认为业务层究竟是如何获取数据的?它必须调用something,而那个东西就是你注入的东西。否则,业务层将无法使用。当然,除非您实际上并不是指业务层,而是指的是 POCO 之类的东西,它们应该与数据无关,但它们本身并不是业务层。
  • 我真的需要写一篇关于这个的博客,因为这是一个很常见的问题。也许如果你查看“Composition Root”,你会得到一些更好的关于构建 DI 图以及依赖关系如何向下传播的例子.
  • 你可能会觉得这篇文章很有趣:blog.ploeh.dk/2012/02/09/IsLayeringWorththeMapping
  • @ColinSmith - 这是一个古老的论点,并且已经被重新讨论过很多次。是的,从技术上讲,您的 UI 依赖于您的数据层,因为您的 UI 成为“组合根”,但是通过将其限制为仅 DI 的配置部分,您可以将其降至最低。这是那些必要的邪恶之一。

标签: c# architecture dependency-injection


【解决方案1】:

这确实是一个相当复杂的话题,并且有很多方法可以实现 n 层架构。没有一种方法是“正确的方法”,如何做取决于您的需求和您的个人喜好。

依赖注入是关于管理依赖的。如果您的对象不知道任何依赖关系,那么您就不会按照您提到的方式编写您的对象。相反,您将拥有一些其他服务或方法,这些服务或方法将以不可知的方式填充数据。数据也不意味着“数据库”。所以 IDataAccess 可能意味着它来自数据库,或者来自网络套接字,或者来自磁盘上的文件。这里的重点是 Address 不选择它创建的依赖项。这是通过组合根目录中的配置完成的。

事物需要数据,否则您的应用可能毫无用处。然而,让你的 Address 对象自己加载可能不是最好的方法。更好的方法可能是使用工厂类或服务方法。

【讨论】:

  • 即使我使用了工厂模式,我仍然必须将 IDataAccess 组件传递给工厂——这意味着 AddressFactory 必须有一个接受 IDataAccess 的构造函数。这只是意味着我的 UI 在构造工厂而不是业务对象时“看到”了 DataAccess 层。我在问 UI 是否应该曾经看到 IDataAccess 接口?
  • @ColinSmith 例如使用 MvvM 时;您拥有应该了解 DataAccess 的业务逻辑层。其实你指的是SoC(关注点分离)。
  • 使用 UI 引用的引导程序项目。该项目可以单独引用其他所有层。允许您设置依赖项。
  • @ColinSmith:如果您不希望您的 UI 直接与 DAL 层的IDataAccess 接口进行通信,答案是在两者之间放置一个抽象。此抽象将成为您的 BL 的一部分,并且给定的实现会将调用转发给 DAL。但是当那个 BL 实现只转发和映射它返回的数据时,你真的应该质疑layering is worth the mapping
  • @Steven:我的地址业务对象不仅仅将数据传送到数据库。例如。它保护 AddressID 属性,使其不能由 UI 设置。 BO 都是正确的域对象,但它们通常需要使用 DAL 将信息持久保存到存储介质中。他们不在乎/如何/这是完成的(因此他们只需要一个 IDataAccess 接口并且不依赖于 SqlDataAccess 类)。但是,如果 IDataAccess 是用于 DI 目的的构造函数中的参数,那么这会暴露我正在向 UI 使用 a 数据访问层这一事实。
【解决方案2】:

我认为答案很简单。你的底层(接口、bll、dal、实体)只是一堆libraries。由客户决定使用哪些库,这将增加客户的灵活性。此外,它们是库,因此任何与应用程序相关的配置(连接字符串、数据缓存等)都位于客户端上。这些配置本身,有时也需要注入并包含在 Composition Root 中。

但是,如果您想要uniform logic 而不是客户端的灵活性,您可以选择网络/应用服务作为附加层。

1st Layer        Entities

2nd Layer       Interface

3rd Layer       BLL  &  DAL

4th Layer    Web/App Services

5th Layer           UI

这样,您的合成根存在于一层(第 4 层)中。添加您的 UI 只需将服务引用添加到第 4 层(或第 1 层,如果需要)。然而,这又暗示了同样的 Mark Seeman 的文章,layering is worth the mapping。我假设您可以将app/web service 更改为Composition Root

此外,这种(应用程序/网络服务)设计有利有弊。优点:

  1. 您的应用已封装

    您的应用正在通过应用/网络服务进行桥接。保证你的UI不知道DataAccess,从而满足你的要求。

  2. 您的应用是安全的

    简单地说,让 UI 需要访问应用服务在安全方面是一个巨大的收获。

  3. 访问可移植性

    现在您的应用程序可以随处访问。它可以通过 3rd 方应用程序(其他网络)连接,而无需依赖 dll。

缺点:

  1. 服务调用期间的间接费用

    身份验证、网络连接等会导致webservice调用的开销。我对性能影响没有经验,但对于高流量应用来说应该足够了。

  2. 客户不灵活

    客户端现在需要使用服务而不是普通对象来访问 BLL/服务。

  3. 为不同类型的客户提供更多服务

    现在您需要提供比需要更多的服务。比如WebRequestRetrieverMobileRequestRetriever,而不是仅仅访问IRequestRetriever,让组合根连接其余部分。

如果这个答案引起了话题,请道歉(完成后才意识到)。

【讨论】:

  • 好的,我认为我缺少的一点实际上是在您的第二句话中:您的底层(接口、bll、dal、实体)只是一堆库”。我从未考虑过 BO层本身可能不会被直接引用……而是作为注入到 UI 层的依赖项!我当然可以看到能够交换业务层的能力(例如,用于自动化 UI 测试),但一般来说,我想要所有生产接口遍历我真正的业务层(这样我就知道所有业务规则都已运行)。
  • 没错。您主要关心的是防止 UI 直接访问 DAL。它可以有两种方式,一种是开发成本高(Web 服务),一种是测试成本高(使用传统的组合根)。在我看来,除非您使用关键系统,例如银行或飞行系统,或多租户 SAAS,否则使用 web 服务是一种矫枉过正的做法。此外,您还将在集成测试中涵盖它。
【解决方案3】:

恕我直言:

这取决于谁注射!-

您似乎需要/期望拥有一个 MVC 或 MVP 架构,其中控制器或 Presenter 负责将 UI 调用来回转换为业务对象 - 创建 IDataAccess 的具体实现,将其发送到 Address 类。 这样 UI 就完全不知道是谁在提供它需要的数据,而是为您提供了预期的可扩展性。

谢谢 塔里克

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 2012-03-15
    • 2018-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多