【问题标题】:What are the best practices for preventing SQL creep?防止 SQL 蠕变的最佳实践是什么?
【发布时间】:2011-04-16 01:57:09
【问题描述】:

我有一个使用 MySQL 数据库后端用 PHP 编写的 webapp。这个问题可以很容易地应用于任何尝试使用 SQL 数据库和 MVC OOP 设计的语言和应用程序。

如何将 SQL 代码限制在模型中?

这个问题背后有一个相当长的故事,专门针对我的案例。如前所述,我正在开发一个 PHP/MySQL/AJAX 网站。我使用 OOP 和 MVC 设计原则设计了它——带有模型、视图和控制器。我设法将视图元素(例如标记和样式)完全限制在视图中,并使它们可以很容易地重用。我以为我对 SQL 代码做了同样的事情。但随着工作的进展,很明显模型需要进行一些认真的重构。

我发现将 SQL 保留在模型中的方法是将每个 SQL 查询封装在其自己的 Query 对象中。然后当我需要在视图或控制器中调用一些 SQL 时,我会通过工厂访问查询。控制器或视图中不存在 SQL 代码。

但这变得异常乏味。我认为我实际上并没有通过这样做获得任何东西,并且花费了太多时间来创建名称为“SelectIdsFromTableWhereUser”的查询。查询的工厂接近数千行。在 Eclipse 中进行一些搜索后发现,这些查询中的绝大多数都在一个或两个地方使用,并且再也不会使用。不好。

我知道,在好的 MVC 中,您希望将 SQL 与 Controller 或 View 完全分开。但在这一点上,在我看来,将 SQL 放在代码中需要它的地方会更好,而不是试图将它和数据库代码埋在模型的深处。这些查询只使用一次,为什么还要封装它们呢?

将 SQL 与 Controller 或 View 分开有那么重要吗?这样做有什么收获?让它传播会失去什么?你如何解决这个问题?

编辑根据要求,这里是关于我的模型的更多细节。

它有两个部分。表格部分和查询部分。表部分包含域对象——主要设计为类对象的包装器,类对象与数据库中的表完全类似。例如,可能有一个数据库表Foo,其中包含字段idnametype。将有一个 Table 对象 (class FooTable),其中包含一个包含字段“id”、“name”和“type”的数组。它看起来像这样:

class FooTable extends MySQLTable {
    private $id;
    private $data;
    private $statements;

    public function __construct($id, $data=NULL, $populate=false) {
         // Initialize the table with prepared statements to populate, update and insert.  Also,
         // initialize it with any data passed in from the $data object.
    }

    public function get($field) {}
    public function set($field, $value) {}
    public function populate() {}
    public function update() {}
    public function insert() {}
}

如果有一个fooBar 数据库表具有一对多关系(一个Foo 多个Bars)与字段idfooIDbar,那么将有一个FooBar表格对象 (class FooBarTable) 看起来与上面的 FooTable 几乎完全相同。

FooTable 和许多 FooBarTable 对象都将包含在 Foo 对象中。为Foo 对象工厂提供Foo 表的ID,它会使用Foo 的数据及其所有Bars 及其数据填充自己。

Query 对象用于按需要的顺序提取那些Foo id。因此,如果我想要按日期、投票或姓名排序的 Foo 对象,我需要一个不同的查询对象来执行此操作。或者,如果我想选择在一定范围内具有Bar 的所有Foo 对象。我需要一个查询对象。

大多数时候,我使用 Table 对象(包装器,而不是基表)与数据库进行交互。但是,当涉及到选择哪些表对象时,这就是查询的用武之地。

在最初的设计过程中,我认为不会有太多查询,我认为它们将成为可以重用的东西。因为可能有几个地方我希望Foos 按日期顺序排列。但事情并没有这样发展。它们的数量比预期的要多,其中大多数是一次性的,在某些视图或命令中使用过一次,然后就再也不会了。我还认为查询可能封装了相当复杂的 SQL,将它们作为对象会很好,这样我就可以始终确保为它们提供所需的数据,这将是一个相对净化的环境,可以在其中测试 SQL 查询本身.但同样,它并没有那样做。它们中的大多数都包含非常简单的 SQL。

【问题讨论】:

  • 我想你已经回答了你自己的问题。您遵循了看似明智的“最佳实践”,却发现这种做法对于边际收益而言已成为负担。 “正确”固然好,但“有效”更好。
  • @Robert 了解,但我想知道是否可能正是我遵循“最佳实践”的方式导致了我的问题。除了封装对大量一次性查询更好的查询之外,还有其他方法可以将 SQL 限制在模型中吗?
  • @Robert 我认为仅仅因为 Daniel 没有做出好的抽象就将抽象抛诸脑后还为时过早。
  • @timedev:哦,我完全赞成好的抽象,如果这就是问题所在。
  • @OMG Ponies:如果仅从一个地方调用任何函数(不仅仅是查询),则表明您的抽象不是很好。它可能已经足够好了。但是,如果每个新视图都需要将新功能添加到模型中,就像模型上的功能通常只从一个视图中使用的情况一样,那么您不会从“封装”中获得太多收益尽可能的模型。

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


【解决方案1】:

为什么不使用repositories?在我看来,这将是封装 SQL 的一种很好、简单的方法。您当前的方法似乎过于复杂。

NerdDinner 教程has a good example 在 MVC 上下文中使用存储库;尽管它不在 PHP 中,但希望它能让您了解这种模式的工作原理。

【讨论】:

  • +1 用于提出具体建议。听起来 OP 需要深入研究一般的设计模式,并找出可能解决他的问题的方法。
【解决方案2】:

在不知道自己在做什么的情况下,不可能给出好的建议,但很明显,有些事情可能是非常错误的。

从您所说的看来,您认为您的模型需要进行一些重大重构是正确的。事实上,听起来它需要进行一些认真的重新设计(这意味着您的控制器和视图用来访问它的 API 将会改变)。

一些想法:

你说:

我发现的保存 SQL 的方法 该模型是封装每一个 单个 SQL 查询在其自己的 Query 中 目的。然后当我需要打电话时 视图或控制器中的一些 SQL I 将通过 a 访问查询 工厂。中不存在 SQL 代码 Controller 或 View.Controller 或 View。

这让我觉得你没有抓住重点。您的模型应该不仅仅是一堆样板代码,这样 SQL 就不会(喘气!)出现在某些控制器中。您的模型应该是领域对象的模型——您所描述的只是 SQL 的低效代理。

如果您发布一些有关如何使用您的模型的示例,这可能会有所帮助。也就是说,编辑您的问题以包含一些示例,说明您的控制器和视图如何使用您的模型。

【讨论】:

  • 我的模型有两个部分。有一个部分对域对象进行建模。查询用于从数据库中获取 id 列表,然后用于实例化各个域对象。
  • 好的,用更多关于模型的细节编辑了原始问题。
【解决方案3】:

从最后一个问题开始:获得的是关注点分离或简单的英语“将属于一起的事物保持在一起”。这里的一个关键词是属于,这是一个相当主观的词。

在早期的 PHP 时代,很多人发现单个页面上的任何内容都“属于”在一起。由于 PHP 曾经是“个人主页”的首字母缩写词,因此它的设计目标是具有几页的小型网站,因此这种归属感是绝对有意义的。

随着事情的发展,归属感会发生变化。当我们得到一个复杂的数据模型,需要保持一致并且必须随着时间的推移而演变时,突然间,这个“模型”上的操作开始“归属”在一起,因为很难挖掘出所有的操作和 SQL 查询。这个地方。因此,为了保持控制,我们需要在同一页面上进行所有模型操作。

作为一种实用的方法,我喜欢在 UI 和模型之间画一条线,并在那里定义一个 API 来封装迷你用户目标(用户想要查看促销 --> 需要 getPromotions 方法,用户想要向购物车添加一些东西:需要 addToCart 方法,等等...)。

我喜欢在这里划清界限,因为它捕捉到了用户的愿望,UI 的职责是让用户以一种简单有效的方式到达那里,服务/模型/存储层的职责就是实现这种愿望。

如果操作正确,它也会从用户中移除的第 1 级步骤。然而,许多开发人员将用户想要的东西与需要实现的功能混淆了。然后你会得到非常无效的服务方法,比如 saveProject (用户不希望项目被保存,她只是希望下次登录时它就在那里。这是理所当然的,因此它在服务中没有位置API)。这种基于实现的 API 导致了几层几乎为空的包装方法。

存储库等是构建此服务层的一种方式。

这种面向用户目标的 API 方法还倾向于清理视图(显示元素的内容可以通过少量服务调用获取)和控制器(一个动作包括正常的清理和通常单个方法调用)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-26
    • 1970-01-01
    • 1970-01-01
    • 2011-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多