Database design 我如何创建一个架构,允许许多客户使用一个网站,每个网站都有不同的功能?

Database design 我如何创建一个架构,允许许多客户使用一个网站,每个网站都有不同的功能?,database-design,web-applications,configuration,coldfusion,Database Design,Web Applications,Configuration,Coldfusion,我继承了一个庞大的应用程序。这是一个被许多客户用于订单处理的网站。这是旧的,过时的,需要一些认真的更新。最困难的是,它最初是在考虑一种类型的客户机的情况下创建的,然后,随着新客户机的添加,代码被数百条基本上为每个客户机打开或关闭功能的IF语句所破坏。想象一下这样的情景(用ColdFusion编写): 因此,对于每个页面请求,我都会查找xml。如果存在,我将每个值复制到客户机类实例的属性中: <cfcomponent hint="Represents configurable per

我继承了一个庞大的应用程序。这是一个被许多客户用于订单处理的网站。这是旧的,过时的,需要一些认真的更新。最困难的是,它最初是在考虑一种类型的客户机的情况下创建的,然后,随着新客户机的添加,代码被数百条基本上为每个客户机打开或关闭功能的IF语句所破坏。想象一下这样的情景(用ColdFusion编写):

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

<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>
我的目标是保持代码整洁,避免在需要编辑的地方到处出现数百条IF语句

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


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

我会将客户机配置设置存储在数据库中。这将使更改设置变得更容易,并且如果他们想要选择不同的配置,也允许他们更改设置(现在可能不是这样,但要提前计划)。想象一下,拥有不同层次的功能,每一层的成本都不同。允许客户维护自己的帐户并相应支付。我知道这超出了您的问题范围,但这只是一个示例,说明您如何解决不断变化的需求

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

session.userInfo.configurations.cart = cart1.cfm
session.userInfo.configurations.paymentMethod = PayPal
session.userInfo.configurations.maxSubCatLevels = 2
etc.

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

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

stuClient = {
  cart_url = "cart1.cfm",
  other_url = "other2.cfm",
  ...
}
我还希望将整个内容存储在会话范围中,这样就不必在每个页面请求上重新查询DB/XML文件。我假设您已经在使用会话来存储clientID

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

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

然后在每个页面中,您可以使用类似


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

然而,您描述的问题通常被称为“多坦能”体系结构。应用程序通过您描述的过程,回顾性地修改这些特性,这并不罕见;结局很少好!不过,通过谷歌搜索这个术语,你可能会得到一些乐趣

根据我的经验,你必须选择你想要复杂度存在的地方——目前,它存在于代码中,而这是它最糟糕的生存地方。复杂的代码更难维护,包含更多的bug,更难更改/扩展,并且使开发人员感到愤怒

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

首先,我想介绍一下“”的概念。因此,在您使用的CART示例中,我会考虑用客户端ID命名解决方案中的所有变量元素。因此,而不是“CART3.CFM”,它将是“CARMICKE.CFM”。这将大大减少XML的消耗量

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

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


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

我将提供一个其他人解释的例子。这里最简单的解决方案是用一个简单的方法调用每个“设置”来替换所有这些条件。这是一种经过实践验证的处理您的情况的技术。即使您不使用会话,您也可以使用多种方法来缓存数据或使数据可用,而无需每次以更基本的方式访问数据库。下面是application.cfc、settings.cfc和一个模板
<a href="#REQUEST.Client.getCartLink()#">Shopping Cart</a>
session.userInfo.configurations.cart = cart1.cfm
session.userInfo.configurations.paymentMethod = PayPal
session.userInfo.configurations.maxSubCatLevels = 2
etc.
stuClient = {
  cart_url = "cart1.cfm",
  other_url = "other2.cfm",
  ...
}
 <cffunction name="getLink" returntype="String" output="false">
        <cfargument name="linktype" type="String" required="true" hint="e.g. 'cart_url'"/>

        <cfreturn VARIABLES.stuClient.CartLink>
    </cffunction>
//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>