3

我一直在缓慢但肯定地重构我客户的一个网站,以改进整体业务逻辑并避免重复代码。我一直从事的主要领域之一是基本购物车应用程序中的产品:

  1. 项目搜索,对数据库使用 SQL 查询
  2. 产品详细信息模板
  3. 查看购物车
  4. 结帐(输入地址,确认订单,提交)

围绕产品有许多业务规则,例如:

  • 哪些产品是可见的
  • 某些用户可以查看哪些产品
  • 哪些产品可以订购
  • 可以订购每种产品的格式
  • 等等

多年来,所有这些规则都在整个网站中重复出现——一些在 SQL 查询中,一些在应用程序逻辑中,一些在两者中——并且它们在某些页面上的实现方式是不同的。事实上,一些规则并没有在某些页面上实施(不是设计使然)。

还创建了新的子应用程序,它们具有不同的规则。

因此,无论在何处显示产品,我都实现了“产品”类的使用,该类包含应用程序逻辑中的所有业务规则(即 Product.isViewable()、Product.isOrderable() 等)。

这允许通过扩展 Product 类在每个具有该应用程序特定规则的子应用程序中使用产品数据。

我目前唯一的问题是项目搜索页面,它查询数据库并包含许多“旧”业务规则,使用 ColdFusion 应用程序逻辑(if/else 等)和 SQL 条件的组合。这是 SQL 查询的片段:

            WHERE   (
                    LTRIM(RTRIM(UCASE(STATUS))) = 'ACTIVE'
                    OR UCASE(VIEWABLE_IF_RETIRED_FLAG) = 'Y'
                    )
            <cfif SESSION.User.getSecurityLevel() LT 5>
                AND (
                    UCASE(ORDERABLE_FLAG) = 'Y'
                    OR UCASE(ELECTRONIC_ORDERABLE_FLAG) = 'O'
                    OR UCASE(ELECTRONIC_ORDERABLE_FLAG) = 'V'
                    )
            </cfif>

这些相同的规则在 Product 类中实现:

    function isVisibleIfRetired() {
        return getVisibleIfRetiredFlag() == "Y";
    }

    function isActive() {
        return getStatus() == "ACTIVE";
    }

    function isDigitalViewable() {
        return  UCase(getIsProductOrderableFlag()) == "Y"
            &&  (
                    UCase(getElectronicOrderableStatus()) == "V"
                ||  UCase(getElectronicOrderableStatus()) == "O"
                );
    }

这让我发疯,因为它本质上仍然是重复的代码,如果业务规则发生变化,这两个部分都需要管理。

在某些地方我只显示几个项目(例如“类似项目”小部件以及“查看购物车”),我可以简单地从数据库中选择产品密钥,然后遍历这些值以创建产品实例并使用类中的业务规则来确定是否/如何显示产品。

但是,项目搜索查询最多可以返回 2000 条记录,并且我不能 [实际上] 循环超过 2000 条记录,创建 2000 个实例来根据业务规则确定显示。

有任何想法吗?

4

2 回答 2

2

想一想:如果您要创建一个规则界面会怎样。即一组描述规则的 CFC。如果它们都实现了相同的接口(显式地或通过鸭子类型),那么您可以装饰您的数据访问对象和其他类,例如您的 Product 类。

这些规则的 API 可能类似于(在伪代码中):

component name="product is visible"{

    function ruleAsSqlFragment(){
        // return a chunk of SQL here that you can drop right into your query and would cause the query to be filtered as you wish.
        return "LTRIM(RTRIM(UCASE(STATUS))) = 'ACTIVE'
                OR UCASE(VIEWABLE_IF_RETIRED_FLAG) = 'Y'";
    }

    function validateRule(object){
        // this function might accept an object, validate it according to its rules and return true or false
        return object.isActive() or object.getViewableIfRetired()
    }

}

然后,您可以制作一组这些规则来装饰您的对象:

rules = [isVisibleRule,isSomethingElseRule,etc];

在您的对象中,您可以简单地遍历数组并调用适当的函数来评估规则或生成正确的 SQL 片段。仅当函数或 SQL 语法评估为 true 时,才满足规则。

总的来说,我不确定使用传统的 SQL 方法可以完全避免重复的业务逻辑。毕竟它们都是(SQL 和 CF)不同的语言。

我想另一种选择是定义领域特定语言 (DSL) 并使用它来定义您的规则。然后编写一个组件,可以将这些规则转换为 SQL 或针对对象评估它们。然后找到一种方法,以正确的方式将 DSL 中定义的特定规则关联到组件中,并根据需要对其进行评估。

另一个挑战是您的数据库看起来并没有完全反映您的对象模型,这是非常典型的。如果是这种情况,我很确定你只需要尽可能地隔离这个逻辑。

于 2013-01-14T17:46:06.727 回答
1

从这些业务规则的自动化测试(例如单元测试)开始。如果您听取测试,测试将建议一个对象模型(例如,通过在编写测试时忽略您当前的实现)。

您可能会发现一些(可能是几个)您希望存在的 CFC 来实施这些业务规则。编写您的测试,就好像它们存在一样。

每个规则都应该有一个规范的实现。也就是说,如果您的规则确定哪些产品是可查看的,那么应该使用一种 CFC 方法来确定。该方法可能在内部使用 CFML、SQL、对其他 CFC 方法的调用,等等。关键是需要使用该规则的所有内容都将调用该方法来执行此操作。您的测试会测试该方法以查看是否正确遵循了规则。

如果您还没有开始编写自动化测试,请查看 MXUnit 并阅读一下 TDD 或 BDD。 http://en.wikipedia.org/wiki/Behavior-driven_development

于 2013-01-15T14:59:46.213 回答