防止SQL蠕变的最佳做法是什么?

时间:2010-09-23 04:16:26

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

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

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

我的案例背后有一个相当长的故事。如前所述,我正在开发一个PHP / MySQL / AJAX网站。我使用OOP和MVC设计原则设计它 - 使用模型,视图和控制器。我设法将视图元素(例如标记和样式)完全限制在View中,并使它们可以相当容易地重复使用。我以为我用SQL代码做了同样的事情。但随着工作的进展,模型需要进行一些严肃的重构才变得非常清楚。

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

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

我知道在良好的MVC中,您希望将SQL与Controller或View完全分开。但是在这一点上,在我看来,将SQL放在代码中所需的位置并且不打算将它和数据库代码深埋在模型中会更好。这些查询只被使用一次,为什么要打扰它们呢?

将SQL与Controller或View分开是否很重要?这样做会得到什么?允许它传播会导致什么损失?你是如何解决这个问题的?

编辑每个请求,这里有关于我的模型的更多细节。

它有两个部分。 Tables部分和Queries部分。 Tables部分包含域对象 - 主要设计为类对象的包装器,类对象是数据库中表的精确类似物。例如,可能存在包含字段Fooidname的数据库表type。将有一个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),其中包含字段idfooID和{ {1}}然后会有一个bar表格对象(FooBar)与上面的class FooBarTable完全相同。

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

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

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

在原始设计期间,我认为不会有太多查询,我认为它们会成为可以重复使用的东西。由于可能有几个地方我想要Bar的日期顺序。但它并没有那么成功。它们的方式多于预期,其中大多数都是一次性的,在一些View或Command中使用过一次,然后再也没有。我还认为查询可能会封装相当复杂的SQL,最好将它们作为对象,以便我始终确保为它们提供所需的数据,这将是一个相对消毒的环境,可以在其中测试SQL查询本身。但同样,它并没有那么成功。其中大多数都包含非常简单的SQL。

3 个答案:

答案 0 :(得分:3)

为什么不使用repositories?在我看来,这将是一种封装SQL的简单方法。您当前的方法似乎不必要地复杂化。

MVC上下文中存储库使用的NerdDinner教程has a good example;即使它不在PHP中,希望它能让你了解这种模式是如何工作的。

答案 1 :(得分:3)

如果不知道你正在做什么样的事情,就不可能提出好的建议,但很明显,某些事情可能是非常错误的。

从你所说的话来看,你认为你的模型需要一些重大的重构,这听起来是对的。事实上,它听起来需要一些严肃的重新设计(意味着你的控制器和视图用来访问它的API会发生变化)。

一些想法:

你说:

  

我发现保持SQL的方式   模型是封装每一个   单个SQL查询在自己的Query中   宾语。然后我需要打电话   视图或控制器中的一些SQL   将通过a访问查询   厂。没有SQL代码   Controller或View.Controller或View。

这让我觉得你错过了这一点。你的模型应该不只是一堆样板代码,所以SQL不会(喘气!)显示在某个控制器中。您的模型应该是域对象的模型 - 您所描述的只是SQL的低效代理。

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

答案 2 :(得分:2)

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

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

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

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

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

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

诸如存储库之类的东西是构建这个服务层的一种方式。

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