Highlighted

Sharing variables between cfcs in a RESTful application

Community Beginner ,
Nov 29, 2016

Copy link to clipboard

Copied

What is the best method to share variables like the following struct among several cfcs in the same directory of a RESTful API?  There are several cfcs but they all use the same generic error codes.

<cfset RESTErrorCodes={

801={detail="Configuration Error",message="Incorrect API Key",type="RestError"},

802={detail="Database Exception",message="No Records Found",type="RestError"},

803={detail="Usage Error",message="Required argument missing",type="RestError"}

}>

Any help would be greatly appreciated!

Adobe Community Professional
Correct answer by BKBK | Adobe Community Professional

You overlooked nothing, I overlooked some. When I looked closer I saw that the problem is a bit more complicated than I at first thought.

If we use init, then it has to be run before any other call to the CFC. Either by the caller or by the CFC itself.

Case 1) Init run by the CFC itself:

If you choose to let the CFC run the init itself, as you have done, then you indeed have to use both lines of code:

<cfset variables.RESTErrorCodes = structNew()>

<cfset init(RESTErrorCodes=application.RESTErrorCodes) />

However, this is not yet optimal, as it still couples the CFC to the application scope.

2) Letting the caller call init:

This is a preferable solution (in an object-oriented sense). But when I look back at your initial question I see a problem I had overlooked. Your CFCs are meant to be RESTful.

This implies calls to them will be stateless. We cannot then expect to carry the result of an init call (the this object) over to the next call. We should therefore think of a solution in which the caller passes an argument, for example, the application-name, to the RESTful CFC. And so we arrive at a possible solution, similar to WolfShade's.

Create a database table, errorCodes, and register it as datasource in the ColdFusion administrator. It will have data as in the following example:

Corresponding to this table will be the CFC errorcodes.cfc, containing the init.

errorcodes.cfc

<cfcomponent>

<cffunction name="init" access="remote" returntype="Struct">

    <cfargument name="appName" type="string" default="myRestApp">

 

    <cfset var RESTErrorCodes = structNew()>

    <!--- For speed and efficiency: My current applicationtimeout value is 2 days, so I cache the query for 2 days --->

    <cfquery name="getErrorCodes" datasource="myDsn" cachedwithin="#createTimespan(2,0,0,0)#">

        select errorkey, errordetail, errormessage, errortype

        from errorCodes

        where applicationName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.appName#">

        order by errorKey

    </cfquery>

    <!--- Assemble the RESTErrorCodes struct --->

    <cfoutput query="getErrorCodes">   

         <cfset RESTErrorCodes[getErrorCodes.errorkey[currentrow]]={detail=getErrorCodes.errordetail[currentrow],message=getErrorCodes.errormessage[currentrow],type=getErrorCodes.errortype[currentrow]}>

    </cfoutput>

<cfreturn RESTErrorCodes>

</cffunction>

</cfcomponent>

All the caller has to do is pass the application's name along with the other arguments in the REST call. The REST CFCs will be something like

<cfcomponent rest="true" restpath="/etc">

<cffunction name="getSomething" access="remote" httpmethod="get" returntype="any" output="false">

<cfargument name="arg1" type="string" required="yes"/>

<cfargument name="arg2" type="numeric" required="yes"/>

<cfargument name="appName" type="string" required="no" />

<cfset var RESTErrorCodes = structNew()>

<!--- Get the error codes for a particular application --->

<cfset var errorCodeObj = createObject("component", "errorCodes")>

<cfset RESTErrorCodes = errorCodeObj.init(arguments.appName)>

<!--- Rest of business code goes here --->

<cfreturn something>

</cffunction>

</cfcomponent>

Views

539

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more

Sharing variables between cfcs in a RESTful application

Community Beginner ,
Nov 29, 2016

Copy link to clipboard

Copied

What is the best method to share variables like the following struct among several cfcs in the same directory of a RESTful API?  There are several cfcs but they all use the same generic error codes.

<cfset RESTErrorCodes={

801={detail="Configuration Error",message="Incorrect API Key",type="RestError"},

802={detail="Database Exception",message="No Records Found",type="RestError"},

803={detail="Usage Error",message="Required argument missing",type="RestError"}

}>

Any help would be greatly appreciated!

Adobe Community Professional
Correct answer by BKBK | Adobe Community Professional

You overlooked nothing, I overlooked some. When I looked closer I saw that the problem is a bit more complicated than I at first thought.

If we use init, then it has to be run before any other call to the CFC. Either by the caller or by the CFC itself.

Case 1) Init run by the CFC itself:

If you choose to let the CFC run the init itself, as you have done, then you indeed have to use both lines of code:

<cfset variables.RESTErrorCodes = structNew()>

<cfset init(RESTErrorCodes=application.RESTErrorCodes) />

However, this is not yet optimal, as it still couples the CFC to the application scope.

2) Letting the caller call init:

This is a preferable solution (in an object-oriented sense). But when I look back at your initial question I see a problem I had overlooked. Your CFCs are meant to be RESTful.

This implies calls to them will be stateless. We cannot then expect to carry the result of an init call (the this object) over to the next call. We should therefore think of a solution in which the caller passes an argument, for example, the application-name, to the RESTful CFC. And so we arrive at a possible solution, similar to WolfShade's.

Create a database table, errorCodes, and register it as datasource in the ColdFusion administrator. It will have data as in the following example:

Corresponding to this table will be the CFC errorcodes.cfc, containing the init.

errorcodes.cfc

<cfcomponent>

<cffunction name="init" access="remote" returntype="Struct">

    <cfargument name="appName" type="string" default="myRestApp">

 

    <cfset var RESTErrorCodes = structNew()>

    <!--- For speed and efficiency: My current applicationtimeout value is 2 days, so I cache the query for 2 days --->

    <cfquery name="getErrorCodes" datasource="myDsn" cachedwithin="#createTimespan(2,0,0,0)#">

        select errorkey, errordetail, errormessage, errortype

        from errorCodes

        where applicationName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.appName#">

        order by errorKey

    </cfquery>

    <!--- Assemble the RESTErrorCodes struct --->

    <cfoutput query="getErrorCodes">   

         <cfset RESTErrorCodes[getErrorCodes.errorkey[currentrow]]={detail=getErrorCodes.errordetail[currentrow],message=getErrorCodes.errormessage[currentrow],type=getErrorCodes.errortype[currentrow]}>

    </cfoutput>

<cfreturn RESTErrorCodes>

</cffunction>

</cfcomponent>

All the caller has to do is pass the application's name along with the other arguments in the REST call. The REST CFCs will be something like

<cfcomponent rest="true" restpath="/etc">

<cffunction name="getSomething" access="remote" httpmethod="get" returntype="any" output="false">

<cfargument name="arg1" type="string" required="yes"/>

<cfargument name="arg2" type="numeric" required="yes"/>

<cfargument name="appName" type="string" required="no" />

<cfset var RESTErrorCodes = structNew()>

<!--- Get the error codes for a particular application --->

<cfset var errorCodeObj = createObject("component", "errorCodes")>

<cfset RESTErrorCodes = errorCodeObj.init(arguments.appName)>

<!--- Rest of business code goes here --->

<cfreturn something>

</cffunction>

</cfcomponent>

Views

540

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Nov 29, 2016 0
Community Beginner ,
Nov 29, 2016

Copy link to clipboard

Copied

The code was cut from my original post.

<cfset RESTErrorCodes={

801={detail="Configuration Error",message="Incorrect API Key",type="RestError"},

802={detail="Database Exception",message="No Records Found",type="RestError"},

803={detail="Usage Error",message="Required argument missing",type="RestError"}

}>

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Nov 29, 2016 0
LEGEND ,
Nov 30, 2016

Copy link to clipboard

Copied

Not sure if this will work, however..

Create a CFC in the same folder as the CFCs you mentioned, and place one function in it, call it SetCodes.  In that function, set the variable as your code does, then return that variable.

In the top of each CFC that needs those codes, set a variable as such (immediately below the opening CFCOMPONENT tag):

<cfset RESTErrorCodes = new component.componentName().SetCodes() />

Each CFC should now have access to a variable called RESTErrorCodes.  If you add, edit, or delete any of the codes in the SetCodes function, this will be reflected in all instances of the variable RESTErrorCodes at the time of use.

HTH,

^_^

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Nov 30, 2016 0
Adobe Community Professional ,
Nov 30, 2016

Copy link to clipboard

Copied

If it applies to every user of the application at all times:

<cfset application.RESTErrorCodes={

801={detail="Configuration Error",message="Incorrect API Key",type="RestError"},

802={detail="Database Exception",message="No Records Found",type="RestError"},

803={detail="Usage Error",message="Required argument missing",type="RestError"}

}>

If it applies to one user, during all the requests in a session:

<cfset session.RESTErrorCodes={

801={detail="Configuration Error",message="Incorrect API Key",type="RestError"},

802={detail="Database Exception",message="No Records Found",type="RestError"},

803={detail="Usage Error",message="Required argument missing",type="RestError"}

}>

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Nov 30, 2016 0
Community Beginner ,
Nov 30, 2016

Copy link to clipboard

Copied

Thanks for taking the time to reply.

It does apply to all users all the time.  To provide just a little more detail... There are about 5 or 6 REST cfcs and the full list of RESTErrorCodes is about 10 items.  I had originally set it up as you show in your first example.  I placed that code in the Application.cfc file in the onApplicationStart() method.  Then the REST cfcs use application.RESTErrorCodes.detail, etc in cfthrow tags.

My concern with this approach was that I had seen in many articles, forums, etc that it was considered bad practice to refer to the application scope within cfcs.  Is this bad practice?  Is there some other way I should handle it?

Thanks again.

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Nov 30, 2016 0
Adobe Community Professional ,
Nov 30, 2016

Copy link to clipboard

Copied

Christopher Simmons_989 wrote:

It does apply to all users all the time. To provide just a little more detail... There are about 5 or 6 REST cfcs and the full list of RESTErrorCodes is about 10 items. I had originally set it up as you show in your first example. I placed that code in the Application.cfc file in the onApplicationStart() method. Then the REST cfcs use application.RESTErrorCodes.detail, etc in cfthrow tags.

Good design, I think.

My concern with this approach was that I had seen in many articles, forums, etc that it was considered bad practice to refer to the application scope within cfcs. Is this bad practice? Is there some other way I should handle it?

'Bad practice' sounds too strong for this particular case. I would just say that such a design is not optimal. That is because it couples the CFCs to the application. This may, for example, make them difficult to extend or unusable by other applications.

You could, ideally, combine the best of both worlds. Proceed with the definition of application.RESTErrorCodes in onApplicationStart. Then ensure that each CFC defines a function through which the RESTErrorCodes variable comes in as an optional argument.

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Nov 30, 2016 0
Community Beginner ,
Dec 01, 2016

Copy link to clipboard

Copied

So could I put an init function in each cfc and initialize the settings in each cfc using the application.RESTErrorCodes like this:

<cfset init(RESTErrorCodes=application.RESTErrorCodes) />

<cffunction name="init">

     <cfargument name="RESTErrorCodes" type="struct">

     <cfif isDefined("arguments.RESTErrorCodes") and isStruct(arguments.RESTErrorCodes)>

          <cfset variables.RESTErrorCodes = arguments.RESTErrorCodes />

     </cfif>

    <cfreturn this />

</cffunction>

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Dec 01, 2016 0
Adobe Community Professional ,
Dec 01, 2016

Copy link to clipboard

Copied

<cfcomponent>

<cfset variables.RESTErrorCodes = structNew()>

<cffunction name="init">

    <cfargument name="RESTErrorCodes" type="struct">  

    <cfif isDefined("arguments.RESTErrorCodes") and isStruct(arguments.RESTErrorCodes)>

        <cfset variables.RESTErrorCodes = arguments.RESTErrorCodes />

    </cfif>

    <cfreturn this />

</cffunction>

</cfcomponent>

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Dec 01, 2016 0
Community Beginner ,
Dec 02, 2016

Copy link to clipboard

Copied

When I use what you suggested in post 7. and call a function to test throwing the custom REST errors I get the error below. 

Element 803 is undefined in a CFML structure referenced as part of an expression.

It seems like the variables.RESTErrorCodes is an empty struct.  When I tried using what I had in post 6 it appears to work.  Am I overlooking something?

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Dec 02, 2016 1
Adobe Community Professional ,
Dec 03, 2016

Copy link to clipboard

Copied

You overlooked nothing, I overlooked some. When I looked closer I saw that the problem is a bit more complicated than I at first thought.

If we use init, then it has to be run before any other call to the CFC. Either by the caller or by the CFC itself.

Case 1) Init run by the CFC itself:

If you choose to let the CFC run the init itself, as you have done, then you indeed have to use both lines of code:

<cfset variables.RESTErrorCodes = structNew()>

<cfset init(RESTErrorCodes=application.RESTErrorCodes) />

However, this is not yet optimal, as it still couples the CFC to the application scope.

2) Letting the caller call init:

This is a preferable solution (in an object-oriented sense). But when I look back at your initial question I see a problem I had overlooked. Your CFCs are meant to be RESTful.

This implies calls to them will be stateless. We cannot then expect to carry the result of an init call (the this object) over to the next call. We should therefore think of a solution in which the caller passes an argument, for example, the application-name, to the RESTful CFC. And so we arrive at a possible solution, similar to WolfShade's.

Create a database table, errorCodes, and register it as datasource in the ColdFusion administrator. It will have data as in the following example:

Corresponding to this table will be the CFC errorcodes.cfc, containing the init.

errorcodes.cfc

<cfcomponent>

<cffunction name="init" access="remote" returntype="Struct">

    <cfargument name="appName" type="string" default="myRestApp">

 

    <cfset var RESTErrorCodes = structNew()>

    <!--- For speed and efficiency: My current applicationtimeout value is 2 days, so I cache the query for 2 days --->

    <cfquery name="getErrorCodes" datasource="myDsn" cachedwithin="#createTimespan(2,0,0,0)#">

        select errorkey, errordetail, errormessage, errortype

        from errorCodes

        where applicationName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.appName#">

        order by errorKey

    </cfquery>

    <!--- Assemble the RESTErrorCodes struct --->

    <cfoutput query="getErrorCodes">   

         <cfset RESTErrorCodes[getErrorCodes.errorkey[currentrow]]={detail=getErrorCodes.errordetail[currentrow],message=getErrorCodes.errormessage[currentrow],type=getErrorCodes.errortype[currentrow]}>

    </cfoutput>

<cfreturn RESTErrorCodes>

</cffunction>

</cfcomponent>

All the caller has to do is pass the application's name along with the other arguments in the REST call. The REST CFCs will be something like

<cfcomponent rest="true" restpath="/etc">

<cffunction name="getSomething" access="remote" httpmethod="get" returntype="any" output="false">

<cfargument name="arg1" type="string" required="yes"/>

<cfargument name="arg2" type="numeric" required="yes"/>

<cfargument name="appName" type="string" required="no" />

<cfset var RESTErrorCodes = structNew()>

<!--- Get the error codes for a particular application --->

<cfset var errorCodeObj = createObject("component", "errorCodes")>

<cfset RESTErrorCodes = errorCodeObj.init(arguments.appName)>

<!--- Rest of business code goes here --->

<cfreturn something>

</cffunction>

</cfcomponent>

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Dec 03, 2016 0