使用基于MVC的Web框架时,您的最佳做法是什么?

时间:2011-04-08 23:58:13

标签: model-view-controller design-patterns language-agnostic

对那些精通开发基于网络的应用程序的人提出一些一般性问题。

问题1:

你如何避免“依赖携带”的问题?据我所知,对象检索的第一点应该发生在控制器的动作方法中。从那里,您可以使用可能需要某些对象的各种模型,类,服务和组件。

如何避免将对象传递给另一个对象只是因为它使用的对象需要它?我想避免去数据库/缓存再次获取数据,但我也不想创建需要大量参数的函数。控制器操作应该是您创建请求最终需要的每个对象的位置吗?

问题2:

您在会话中存储了哪些数据?我的理解是,您通常只应存储用户ID,电子邮件地址,名称和访问权限等内容。

如果您在用户登录时需要为每个请求分析数据,该怎么办?是否应将整个用户对象存储在缓存中而不是会话中?

问题3:

您是将数据检索方法放在模型本身中还是放在获取数据并返回模型的单独对象中?这种方法有什么好处?

问题4:

如果您的网站是由用户ID驱动的,那么您如何对代码库进行单元测试?这就是为什么您应该将所有数据检索方法集中在一起,以便在单元测试中覆盖它?

问题5:

一般来说,您是否对控制器进行单元测试?我听过很多人说这是一种困难甚至是不好的做法。你对此有何看法?你在控制器中测试到底是什么?

欢迎您提供有关最佳做法的任何其他信息。我总是愿意学习更多。

2 个答案:

答案 0 :(得分:4)

  • 你如何避免“依赖携带”的问题?

BaseController SuperClass的良好面向对象设计可以处理很多实例化常用对象的繁重工作。使用Composite类型来跨调用共享数据是一种不常见的做法。例如。在Controller中为您的应用程序创建一个独特的Context Object,以便在进程间共享信息并不是一个糟糕的主意。

  • 您在会话中存储了哪些数据?

尽可能少的人类可能。

如果存在一些数据密集型操作需要大量开销来处理并且应用程序经常需要它,那么它是会话存储的合适候选者。是的,存储诸如用户ID和其他个性化信息之类的信息对会话状态来说并不是一种不好的做法。一般来说,使用cookie是个性化的首选方法。始终记得永远不要相信cookie的内容,例如在信任之前正确验证读取的内容。

  • 您是将数据检索方法放在模型本身中还是放在获取数据并返回模型的单独对象中?

我更喜欢为我的模型使用Repository模式。模型本身通常包含简单的业务规则验证等,而存储库命中业务对象以进行结果和转换/操作。市场上有很多模式和ORM工具,这是一个备受争议的话题,所以它有时只是熟悉工具等......

  • 这种方法有什么好处?

我在Repository Pattern中看到的优势是模型的数量越多,它们就越容易修改。如果它们是业务对象(例如Web服务或数据表)的代表,则对作为我的MVC应用程序的表示逻辑充分抽象对这些底层对象的更改。如果我实现了在模型本身内加载模型的所有逻辑,我就会违反关注点模式。尽管如此,这一切都非常主观。

  • 如果您的网站是由用户ID驱动的,那么您如何对代码库进行单元测试?

强烈建议尽可能在代码中使用依赖注入。一些IoC容器可以相当有效地处理这个问题,一旦理解,就会大大改善您的整体架构和设计。话虽这么说,用户上下文本身应该通过某种形式的已知接口实现,然后可以在应用程序中“模拟”。然后,您可以在测试工具中模拟您希望的任何用户,并且所有相关对象都不会知道差异,因为他们只是简单地查看界面。

  • 一般来说,您是否对控制器进行单元测试?

绝对。由于控制器需要返回已知的内容类型,使用适当的测试工具我们可以使用实践来模拟HttpContext信息,调用Action方法并查看结果以查看它们是否符合我们的期望。有时这会导致在结果是大量HTML文档时只查找HTTP状态代码,但在JSON响应的情况下,我们可以很容易地看到action方法按预期返回所有方案的信息

  • 您在控制器中究竟测试了什么?

应彻底测试控制器的所有公开声明的成员。

冗长的问题,更长的答案。希望这对任何人都有帮助,请把这一切都视为我自己的看法。很多这些问题都是宗教辩论,你只需要练习正确的面向对象设计,SOLID,接口编程,DRY等......

答案 1 :(得分:3)

关于依赖性爆炸,本书 .NET中的依赖注入(非常好)解释了太多的依赖关系表明你的控制器承担了太多责任,即违反了单一责任原则。其中一些责任应该在执行多个操作的聚合之后进行抽象。

基本上,你的控制器应该是 dumb 。如果它需要那么多的依赖来完成它的工作,它就做得太多了!它应该只接受用户输入(例如URL,查询字符串或POST数据),并以适当的格式将这些数据传递到您的服务层。

示例,摘自本书

我们从OrderService开始,依赖于OrderRepositoryIMessageServiceIBillingSystemIInventoryManagementILocationService。它不是控制器,但适用同样的原则。

我们注意到ILocationServiceIInventoryManagement都是订单履行算法的真正实现细节(使用位置服务查找最近的仓库,然后管理其库存)。因此,我们将它们抽象为IOrderFulfillment,以及使用LocationOrderFulfillmentIInventoryManagement的具体实现ILocationService。这很酷,因为我们隐藏了一些细节,远离我们的OrderService,并进一步揭示了一个重要的领域概念:订单履行。我们现在可以以非基于位置的方式实现此域概念,而不必更改OrderService,因为它仅取决于接口。

接下来,我们注意到IMessageServiceIBillingSystem和我们的新IOrderFulfillment抽象都以相同的方式使用:他们会收到有关订单的通知。因此,我们创建了INotificationService,并使MessageNotification成为INotificationServiceIMessageService的具体实现。同样适用于BillingNotificationOrderFulfillmentNotification

现在就是诀窍:我们创建了一个新的CompositeNotificationService,它派生自INotificationService并委托给各种“子”INotificationService个实例。我们用于解决原始问题的具体实例将特别委托给MessageNotificationBillingNotificationOrderFulfillmentNotification。但是如果我们希望通知更多系统,我们就不必编辑我们的控制器:我们只需要以不同方式实现我们的CompositeNotificationService

我们的OrderService现在仅取决于OrderRepositoryINotificationService,这更合理!它有两个构造函数参数而不是5,而最重要的是,它几乎不负责找出要做的事情。