我继承了一个应用程序的怪物。这是一个许多客户用于订单处理的网站。它陈旧,过时,需要一些认真的更新。最困难的部分是它最初是在创建一种类型的客户端时创建的,然后,随着新客户端的添加,代码被大量的IF语句混淆,这些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-root存储他们的文件 - 也许是网站上使用的文件等。这个目录就像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实例将具有属性cartLink
的值“cart1.cfm”。
现在我可以简单地查找该值并使用它来填充锚标记:
<a href="#REQUEST.Client.getCartLink()#">Shopping Cart</a>
我的目标是保持代码清洁,并避免在需要编辑的地方发布数百个IF语句。
但是,当我现在看这个设计时,我意识到这可能会成为更多的维护噩梦。该网站目前有大约30-40个客户。所以,现在,我需要维护30-40个xml文件,每个配置属性都有任意数量的节点。此外,每次添加新功能时,我都必须使用新的getter / setter方法为新属性更新客户端类。
我不想让这更糟。任何想法都将不胜感激。
答案 0 :(得分:4)
首先,我不知道ColdFusion,所以我不能给你代码。
但是,您所描述的问题通常称为“多租户”架构。应用程序通过您描述的过程并不常见,追溯性地破解这些功能;它很少结束!不过,你可能会在谷歌上搜索这个词。
根据我的经验,你必须选择你希望复杂性存在的地方 - 目前,它存在于代码中,而这是它生活的最糟糕的地方。复杂的代码难以维护,包含更多错误,更难更改/扩展,并使开发人员生气。
管理复杂性作为数据/配置是一种更好的方式 - 它可以显着降低代码库的复杂性,并且通常更易于管理。你迈出了这条道路的第一步,但我会稍微改进一下。
首先,我将介绍“convention over configuration”的概念。因此,在您使用的购物车示例中,我会考虑使用客户端ID命名解决方案中的所有可变元素。因此,它不是“cart3.cfm”,而是“cartMike.cfm”。这应该会大大减少XML的数量。
其次,您可能想要介绍默认值的概念。在大多数情况下,80%的设置在客户端是通用的,只有20%需要客户端特定的设置。而不是必须管理所有这些设置,引入默认值;如果没有特定于客户端的默认值,请使用默认文件中的默认值。
第三,您可能想介绍客户类型 - 金/银/铜,免费/中小企业/企业,等等。这需要为每个客户端类型创建默认配置,但可能会提高默认配置文件的命中率。
最后,您需要考虑应用程序生命周期 - 新客户端的配置过程应该是什么?它主要是一项技术任务吗?在这种情况下,我将继续使用XML文件,因为它们可以在版本控制中轻松管理。如果它更像是一个“业务”任务,您希望将其包装到更加用户友好的过程中,但您需要能够验证配置。例如,如果“cart”的配置设置为“/Mike/cart.cfm”,则需要确保该文件实际存在。您可能还需要版本控制和从开发人员转移到测试环境的机制。
答案 1 :(得分:1)
我会将客户端配置设置存储在数据库中。这将使更容易更改设置,并允许他们更改设置,如果他们想要选择不同的配置(现在可能不是这样,但提前计划)。想象一下,有不同的功能层,每个功能成本不同。允许客户维护自己的帐户并相应支付。我知道这超出了你的问题范围,只是一个如何解决不断变化的需求的例子。
通常我会在用户登录或访问网站的某个部分后从数据库中提取后,查看该用户的会话范围结构中存储的网站配置。
session.userInfo.configurations.cart = cart1.cfm
session.userInfo.configurations.paymentMethod = PayPal
session.userInfo.configurations.maxSubCatLevels = 2
etc.
答案 2 :(得分: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>
答案 3 :(得分: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>
答案 4 :(得分:0)
重申先前已经说过的话,我会记下数据库中的信息。 ColdFusion让您可以创建独立的应用程序,可以将相同的基本代码与多个Application.cfc或.cfm文件一起使用,您需要做的就是确保每个文件都有一个唯一的this.name变量并将其放在一个单独的客户文件夹。然后在应用程序启动时读取客户端特定的设置,然后将它们缓存在应用程序范围内。
ColdFusion(特别是企业版)的设计完全是为了这个目的,网上有很多可以提供帮助的信息。搜索管理多个应用程序以及application.cfc如何帮助您完成此任务。