【问题标题】:Web app architecture adviceWeb 应用架构建议
【发布时间】:2012-04-10 12:07:15
【问题描述】:

与我一起工作的团队(3 人)目前正在讨论一个新的中型 Web 应用程序的架构,您可以想象,团队内部对于应该如何设计应用程序存在不同的意见。我正在尝试做的是发布其中一些想法以及我们所知道的每种方法的优点/缺点,以便 SO 社区可以帮助我们走上最佳路线和/或达成妥协。

架构 A:

  1. 存储层:

    对实体框架的调用

  2. 服务/业务层:

    • 每个“经理”都有一个界面
    • 每个“经理”有 2 个实现(1 个模拟/1 个实际)
    • Manager 类中调用 Repository 然后应用业务逻辑的方法
    • 每个方法都要进行单元测试
  3. 表示层(MVC):

    • 通过某种服务定位器模式创建管理器
    • 每个控制器都有针对它编写的单元测试

架构 B:

  1. 服务/业务层:

    • 仅包含具体的“经理”类
    • 方法直接调用实体框架(例如,我们将 EF 视为 repo)
    • 可以使用接口,但仅限于需要不同实现的地方
    • 在需要时进行单元测试

2。表示层(MVC):

  • 直接创建管理器类
  • 仅对脆弱或非常复杂的代码进行单元测试

你可以做出一些假设:

  • 应用程序中的大多数方法都是数据库调用,具有奇数位的身份验证/根据用户权限/等返回的限制数据
  • 95% 的控制器不包含明显的复杂性
  • 没有业务需要换出或有业务层类的不同实现。万一我们不得不切换数据库等,业务部门很乐意接受重构的冲击
  • 包括标记等在内的估计代码大小约为 50k 行(基于之前版本的网络应用,这是一团糟)
  • 开发时间紧迫
  • 有专门的 QA 人员

很抱歉,如果这看起来相反,但出于多种原因,我的首选架构实际上是 B。

  1. 我从事过抽象到 n 级的项目,对每一层进行更改、在 VS 中重建、修改测试/模拟方法等确实需要更长的时间,它提供对于本质上是简单的 CRUD 应用程序的项目来说回报并不多

  2. 企业并不关心应用是否拥有世界上最干净的架构,他们只想快速发布产品,没有太多错误

  3. 对于绝大多数方法(在所有层中),只需进行简单的功能测试即可。如果 GetProductById 方法不起作用,请修复它!在我看来,简单的方法不需要包含比被测试方法更多的代码行的单元测试方法。

  4. 我希望能够右键单击一个方法,选择“转到定义”并查看该实际方法源!我不想看到接口定义,然后不得不在其他地方查找实际方法。当然,如果一切都使用接口,那么交换到不同的数据库应该* 轻而易举,但实际上这不会发生,也不太可能发生在这个项目中。

总而言之,我并不反对接口或单元测试,但是其中任何一种似乎都在泛滥,而这几乎没有带来任何好处,我看不出重点。

有人有什么建议/意见吗?

【问题讨论】:

  • 抱歉格式问题,我是在 iPad 上输入的!

标签: c# asp.net-mvc model-view-controller architecture


【解决方案1】:

根据业务需求,我似乎很清楚,您应该坚持以最简单的方式编写代码,我会跳过所有 TDD 内容并使用专门的 QA 人员,并跳过正式的接口声明,除非它们在有道理。

【讨论】:

    【解决方案2】:

    架构 A

    1) 存储层:对实体框架的调用

    抽象掉 OR/M 是使依赖类 (SL/BL) 可测试的好方法。

    2) 服务/业务层: - 每个“经理”都有一个接口 - 每个“经理”有 2 个实现(1 个模拟/1 个实际) - 经理类中调用存储库然后应用业务逻辑的方法 - 每个方法进行单元测试

    不要将模拟放在业务层中。他们不属于那里。改用模拟框架,这些框架还允许您验证类是否调用了依赖项中的正确方法。

    3) 表示层 (MVC): - 通过某种服务定位器模式创建管理器 - 每个控制器都针对其编写单元测试

    服务位置隐藏依赖关系。改用依赖注入(当今最简单的方法是使用控制反转容器)

    架构 B

    1) 服务/业务层: - 仅包含具体的“管理器”类 - 方法直接调用实体框架(例如,我们将 EF 视为存储库) - 可以使用接口,但仅在需要不同实现的情况下使用- 在需要时进行单元测试

    当然。您可以模拟 EF4,但要验证所有调用是否正确要困难得多。 Unit tests as and when when required。应该总是如此。为什么要测试不需要测试的东西。公司政策规定应该始终存在测试的时间和内容。

    2) 表示层 (MVC): - 直接创建管理器类 - 仅对脆弱或非常复杂的代码进行单元测试

    直接创建类会使范围界定变得困难,如何创建复杂的对象图?它邀请您将管理器类变成神类或在类之间创建高耦合。

    问题

    1) 我从事过抽象到第 n 级的项目,对每一层进行更改、在 VS 中重建、修改测试/模拟方法等确实需要更长的时间,但它提供的不是本质上是简单的 CRUD 应用程序的项目可以获得很大回报

    很少有应用程序是 CRUD。 Crud 应用程序不会验证模型,而不仅仅是检查语法是否正确。这些应用程序将包含大量从业务角度来看无效的数据(但格式正确有效)

    2) 企业并不关心应用程序是否拥有世界上最干净的架构,他们只想快速发布产品且没有太多错误

    他们确实关心几年后维护成本飙升。

    3) 对于绝大多数方法(在所有层中),只需要一个简单的功能测试。如果 GetProductById 方法不起作用,请修复它!

    从现在起六个月后,当您引入新的更改/功能时,您会做什么?强制用户测试可能受您的更改影响的所有内容?如果 GetProductById 在您测试之前无法正常工作,那么您现在无法*。

    在我看来,简单的方法不需要包含比被测试方法更多的代码行的单元测试方法。

    恕我直言,该方法需要重构并分解为更小的方法。难以测试的方法很可能有异味。

    4) 我希望能够右键单击一个方法,选择“转到定义”并查看该实际方法源!我不想看到接口定义,然后不得不在其他地方查找实际方法。

    这个让我害怕。您是否愿意仅仅因为您的 IDE 对您没有帮助而牺牲代码质量?购买 resharper,您将获得“Goto implementation”。 Resharper 接受了很多软件质量检查,物有所值。

    如果一切都使用接口,那么交换到不同的数据库应该* 是一件轻而易举的事,但实际上这不会发生,也不太可能发生在这个项目中。

    这不会发生在任何项目中,也不是使用接口的原因。定义良好的接口使重构代码和遵循 SOLID 原则变得更加容易。

    【讨论】:

      【解决方案3】:

      我同意 jgauffin 的说法,但我想补充一点,所以我使用他的模板来统一:

      架构 A

      1) 存储层:对实体框架的调用

      正如 jgauffin 所说,抽象 O/RM 是 a 使 BL 可测试的好方法,实际上我会说这是对你的单元进行单元测试的最佳方法BL,这通常很重要。但请记住,有几个原因有advocates who say that you shouldn't hide the O/RM behind a repository。从那篇博文:

      这种模式的问题在于它完全忽略了成熟的持久性技术的存在,例如 NHibernate。 NHibernate 已经提供了一种内存访问的假象,事实上,这是它存在的唯一原因。声明式查询,检查。持久性存储上的 OO 视图,检查。域和数据存储之间的一种方式依赖关系,检查。

      那么,当我已经拥有 NHibernate(或类似的,现在大多数 OR/M 具有匹配功能)时,使用存储库模式可以获得什么?

      辩论仍在进行中,因此由您决定。使用 NHibernate,您仍然可以对模拟 ISession 的 BL 进行单元测试,但我不知道它如何与 EF 一起使用:或者您可以使用针对内存数据库(例如 SQLite)的集成测试来测试您的 BL。

      2) 服务/业务层: - 每个“经理”都有一个接口 - 每个“经理”有 2 个实现(1 个模拟/1 个实际) - 经理类中调用存储库然后应用业务逻辑的方法 - 每个方法进行单元测试

      jgauffin 说你不应该在你的 BL 中放置模拟,他是对的,但只要你的模拟在一个额外的项目中,我只看到集成测试期间的优势,特别是如果你使用 WCF 服务:那么你可以对模拟和真实服务有不同的配置(或者根本没有配置,只有范围),并针对多个配置测试您的服务。

      3) 表示层 (MVC): - 通过某种服务定位器模式创建管理器 - 每个控制器都有针对其编写的单元测试

      有些人声称 Service Locator is an antipattern 并且这个论点非常有说服力,即使有时服务定位器颂歌看起来像是较小的邪恶。与 WebForms 相比,MVC 为依赖注入提供了更优雅的解决方案,因此您一定要研究一下。

      架构 B

      1) 服务/业务层: - 仅包含具体的“管理器”类 - 方法直接调用实体框架(例如,我们将 EF 视为存储库) - 可以使用接口,但仅在需要不同实现的情况下使用- 在需要时进行单元测试

      如前所述,理论上这可以很容易地与使用内存数据库的集成测试一起工作,如果它对您来说足够快的话。

      2) 表示层 (MVC): - 直接创建管理器类 - 仅对脆弱或非常复杂的代码进行单元测试

      我看到的不使用 IoC 容器的唯一优势是少了一个移动部件;出于测试目的,我仍然会使用 DI,所以你真的想手动注入依赖项,Poor Man 的 DI 风格,用于中型项目吗?

      总结

      简而言之,总有一个中间立场,我们无法真正为您做出决定。作为一个中型项目,我倾向于类似于架构 A 的东西;唯一的例外是,如果它是一个短暂的项目,您有一个确定的日期,您的应用程序将过时并且将被删除(考虑一个特定非重复事件的应用程序)。

      一般来说,如果不是演示项目,我很少会跳过一个好的抽象。正如 jgauffin 暗示的那样,不要将抽象视为交换实现的手段,而是将其视为管理应用程序复杂性的一种方式

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-08-04
        • 2010-10-06
        • 1970-01-01
        • 2012-08-08
        • 2011-06-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多