4

我继承了一个应用程序的怪物。这是一个被许多客户用于订单处理的网站。它很旧,过时了,需要一些认真的更新。最困难的部分是它最初是为一种类型的客户端创建的,然后,随着新客户端的添加,代码被数百个 IF 语句混为一谈,这些语句基本上打开或关闭每个客户端的功能。想象一下这样的事情(用 ColdFusion 编写):

<cfif clientId EQ "MIKE">
    <a href="cart1.cfm">Shopping Cart</a>
<cfelseif clientId EQ "JOE">
    <a href="cart2.cfm">Shopping Cart</a>
<cfelseif clientId EQ "BILL"
    OR clientId EQ "JILL"
    OR clientId EQ "RAY">
    <a href="cart3.cfm">Shopping Cart</a>
<cfelse>
    <a href="cart.cfm">Shopping Cart</a>
</cfif>

以上是我在整个网站上处理的内容的一个很好的 CLEAN 版本。

因此,我正在尝试重构网站以允许更轻松的特定于客户端的配置 - 基本上是为每个客户端提供或隐藏功能。

我从我认为是一个简单、干净的解决方案开始,但我担心它可能会变得难以维护。

基本上,我将为每个客户提供一个 Web 根目录之外的目录,用于存储他们的文件——可能是网站上使用的文档等。这个目录类似于C:\clients\MIKE\. 在这个目录中,我存储了一个 xml 文件——我们称之为 config.xml。我创建的第一个 config.xml 文件具有以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <cart>
        <url>cart1.cfm</url>
    </cart>
</root>

因此,对于每个页面请求,我都会查找 xml。如果存在,我将每个值复制到Client类实例的属性中:

<cfcomponent
    hint="Represents configurable per-Client settings stored in a local Xml file">
    <cfscript>
        VARIABLES.CartLink = "";
    </cfscript>

    <cffunction name="init" return="Client" output="false">
        <cfargument name="clientId" type="String" required="true" hint="i.e. 'MIKE'"/>

        <cfscript>
            var _clientXml = XmlNew();

            THIS = setClientId(ARGUMENTS.clientId);

            _clientXml = read();

            if  (   StructKeyExists(_clientXml.XmlRoot, "cart")
                &&  StructKeyExists(_clientXml.XmlRoot["cart"], "url")
                )
                setCartLink(_clientXml.XmlRoot["cart"]["url"].XmlText);

            return THIS;
        </cfscript>
    </cffunction>

    <cffunction name="getCartLink" returntype="String" output="false">
        <cfreturn VARIABLES.CartLink />
    </cffunction>

    <cffunction name="setCartLink" returntype="Void" output="false">
        <cfargument name="cartLink" type="String" required="true" />

        <cfset VARIABLES.CartLink = Trim(ARGUMENTS.cartLink) />
    </cffunction>

    <cfscript>
        function getXmlFilePath() {
            return APPLICATION.ClientFilePath
                & "\" & getClientId() & "\config.xml";
        }
    </cfscript>

    <cffunction name="read" access="public" output="false" returntype="xml">
        <cfscript>
            var _clientXml = XmlNew();
            var _fileContents = "";

            _clientXml.XmlRoot = XmlElemNew(_clientXml, "root");

            if  (FileExists(getXmlFilePath()))
                _fileContents = FileRead(getXmlFilePath());

            if  (IsXml(_fileContents))
                _clientXml = XmlParse(_fileContents);

            return _clientXml;
        </cfscript>
    </cffunction>
</cfcomponent>

使用上面的示例 xml,当用户在“MIKE”客户端下登录时,REQUEST 范围内的 Client 实例的属性值为“cart1.cfm” cartLink

现在我可以简单地查找该值并使用它来填充锚标记:

<a href="#REQUEST.Client.getCartLink()#">Shopping Cart</a>

我的目标是保持代码干净,避免在需要编辑的地方到处出现数百个 IF 语句。

但是,当我现在看到这个设计时,我意识到这可能更像是一场维护噩梦。该网站目前有大约 30-40 个客户。所以,现在,我需要维护 30-40 个 xml 文件,每个配置属性都有任意数量的节点。此外,每当添加新功能时,我都必须使用新属性的新 getter/setter 方法更新客户端类。

我不想让事情变得更糟。任何想法将不胜感激。

4

5 回答 5

4

首先,我不知道 ColdFusion,所以我不能给你代码。

但是,您所描述的问题通常被称为“多租户”架构。应用程序经历您描述的过程并回顾性地破解这些功能并不罕见;它很少有好的结局!不过,您在谷歌上搜索这个词可能会很开心。

以我的经验,你必须选择你希望复杂性存在的地方——目前,它存在于代码中,这是它存在的最糟糕的地方。复杂的代码更难维护,包含更多错误,更难更改/扩展,并使开发人员生气。

将复杂性作为数据/配置进行管理是一种更好的方法——它显着降低了代码库的复杂性,并且通常更易于管理。你已经在这条路上迈出了第一步,但我会稍微完善一下。

首先,我要介绍“约定优于配置”的概念。因此,在您使用的购物车示例中,我会考虑使用客户端 ID 命名解决方案中的所有变量元素。所以不是“cart3.cfm”,而是“cartMike.cfm”。这应该会显着减少 XML 转换的数量。

其次,您可能想引入默认值的概念。在大多数情况下,80% 的设置在客户端之间是通用的,只有 20% 需要客户端特定的设置。不必管理所有这些设置,而是引入默认值;如果没有特定于客户端的默认值,请使用默认文件中的那个。

第三,您可能想要介绍客户类型 - 黄金/白银/青铜、免费/中小企业/企业等等。这将需要为每种客户端类型创建默认配置,但可能会增加默认配置文件的命中率。

最后,您需要考虑应用程序生命周期——新客户的供应过程应该是什么?它主要是一项技术任务吗?在这种情况下,我会继续使用 XML 文件,因为它们可以在版本控制中轻松管理。如果它更像是一项“业务”任务,您希望将其包装成一个对用户更友好的流程,但是您需要能够验证配置。例如,如果“cart”的配置设置是“/Mike/cart.cfm”,您需要确保该文件确实存在。您可能还需要版本控制和从开发人员转移到测试环境到生产环境的机制。

于 2012-10-19T13:56:20.267 回答
1

我会将客户端配置设置存储在数据库中。这将使更改设置变得更容易,如果他们想选择不同的配置,还允许他们更改设置(现在可能不是这种情况,但请提前计划)。想象一下,拥有不同层次的功能,每一个都花费不同的金额。允许客户维护自己的帐户并相应地付款。我知道这超出了您的问题范围,而只是您如何解决不断变化的需求的一个示例。

通常,在用户登录或访问站点的某个部分后,从数据库中提取站点配置后,我会看到站点配置存储在该用户的会话范围结构中。

session.userInfo.configurations.cart = cart1.cfm
session.userInfo.configurations.paymentMethod = PayPal
session.userInfo.configurations.maxSubCatLevels = 2
etc.
于 2012-10-19T13:43:11.780 回答
1

我宁愿将数据存储在数据库中而不是 XML 文件。更新(您可以自己构建一个 CMS 来完成)比加载文件更容易。

然后我想将所有这些存储为一个大结构,例如:

stuClient = {
  cart_url = "cart1.cfm",
  other_url = "other2.cfm",
  ...
}

我还宁愿将整个内容存储在会话范围内,这样您就不必在每个页面请求上重新查询 DB / XML 文件。我假设您已经在使用 session 来存储 clientID。

因此,您最终可以做的是,在 onApplicationStart 中,一次获取所有客户端的所有数据,并将其存储在应用程序范围内。这部分实际上是可选的,因为然后,在 onSessionStart 中,只获取与该客户端相关的数据(可能是查询的查询,如果您已经开始将它存储在应用程序中),并将其转换为结构。

因此,在您的代码中,您只需根据需要引用 stuClient.cart_url 。而不是在每个请求上初始化您的 CFC,而是在每个会话上都这样做。用数据结构初始化。

然后在每个页面中,您可以使用类似<a href="#Client.getLink('cart_url')#">

 <cffunction name="getLink" returntype="String" output="false">
        <cfargument name="linktype" type="String" required="true" hint="e.g. 'cart_url'"/>

        <cfreturn VARIABLES.stuClient.CartLink>
    </cffunction>
于 2012-10-19T13:45:24.073 回答
0

我将提供一个例子来解释其他人的解释。这里最简单的解决方案是让您用每个“设置”的简单方法调用来替换所有这些条件。这是一种久经考验的、经过验证的技术,可用于处理您的情况。即使您不使用会话,您也可以有多种方法来缓存或让数据可用,而无需每次都以更基本的方式访问数据库。下面我有 application.cfc、settings.cfc 和一个模板。

//In your application.cfc onSessionStart (hopefully you are using sessions)
session.settings = createObject("component", "settings").init(clientID);

//In your settings.cfc 
//saving keystrokes with cfscript style functions
<cfscript>
function init(clientID){
   variables.clientID = arguments.clientID;
   setAllUserSettings();
   return this;
} 

function setAllUserSettings(){
   //query the database for a particular user 
   //you can keep the query in memory or set each row value to something
   //let's say you wanted to use the QoQ method
   variables.settings = qryAllUserSettings();       
}
</cfscript>

<cffunction name="getSetting">
    <cfarguments name="type" />
    //start query of query to get a particular setting
    //for our user
    <cfquery name="qry" dbtype="query">
         select value
         from variables.settings
         where setting = #arguments.type#
    </cfquery>

    <cfreturn qry.value />
</cffunction>

<cffunction name="qryAllUserSettings">
   <cfset var qry = "" />
   //we'll get every possible setting for this user
   <cfquery name="qry" datasource="dsn">
        select setting,value
        from tblSettings
        where clientID = variables.clientID
   </cfquery>
   <cfreturn qry />
</cffunction>

//In your ColdFusion Template
<a href="#session.settings.getSetting(type="cart")">Shopping Cart</a>
于 2012-10-19T15:43:51.593 回答
0

为了重申之前所说的,我会在数据库中保存信息。ColdFusion 使您能够创建独立的应用程序,这些应用程序可以使用具有多个 Application.cfc 或 .cfm 文件的相同基本代码,您需要做的就是确保每个文件都有一个唯一的 this.name 变量并将它放在单独的客户文件夹。然后在应用程序启动时读取客户端的特定设置,然后将它们缓存在应用程序范围内。

ColdFusion(特别是企业版)正是为此目的而设计的,网上有很多信息可以提供帮助。搜索管理多个应用程序以及 application.cfc 如何帮助您执行此操作。

于 2012-10-21T10:41:21.893 回答