1

I'm trying to refactor all of my CFCs to avoid using SESSION and APPLICATION variables (not an easy task).

However, in this application, SESSION variables are used in every database call, since different logged in users may be accessing different databases and schemas:

<cfquery name="qEmployees" datasource="#SESSION.DataSourceName#">
    SELECT *
    FROM #SESSION.DatabaseSchema#.Employees
</cfquery>

I don't want to go through the trouble of passing these two SESSION variables to every method call that accesses the database. This is especially the case since I don't want to pass DSNs and Schema Names in remote AJAX calls.

What is best practice for doing this - for all Scopes that shouldn't be used in CFCs?

Eric Belair
  • 10,574
  • 13
  • 75
  • 116
  • 2
    If it is more than just a dsn, you might look into facades http://www.coldfusiondesignpatterns.org/wiki/Facade * http://www.cfgears.com/index.cfm/2009/12/3/Using-a-Session-Facade-in-ColdFusion * http://www.pbell.com/index.cfm/2007/2/17/SiteUser-instead-of-SessionFacade – Leigh Jul 13 '11 at 16:24
  • Seems to me the answer will depend on where your CFCs are being created: if in the session scope, then try Sean Coyne's approach, passing in the session dsn/schema at onSessionStart(). If they're in the application scope, then BittersweetRyan is right that the dsn/schema will vary depending on the session (user) so you will have to pass those values in for each call (via the remote facade for security). – CfSimplicity Jul 15 '11 at 07:35

5 Answers5

2

I think that since the datasource truly is variable I'd pass it into every function as an optional parameter and set the default value to a variables scoped dsn attribute. I'd set the variables scoped DSN in the CFC's constructor. That way you only have to pass in the DSN for the AJAX calls.

<cffunction name="doFoo" access="remote"...>
    <cfargument name="dsn" type="String" required="false" default="#variables.datasource#" />
</cffunction>

I'd use the session scope of your app to store the users dsn name and use that var to pass to the AJAX call.

bittersweetryan
  • 3,383
  • 5
  • 28
  • 42
  • The datasource and schema values are only variable between sessions - not within one session. This seems like overkill. – Eric Belair Jul 13 '11 at 19:19
  • Eric, can you clarify? When a session ends the datasource and schema may change for the next session? Is this for a load balancing type situation? – bittersweetryan Jul 15 '11 at 03:25
  • The schema and database are based on login. Each login creates a new session. This allows one website to allow different "types" of users. – Eric Belair Jul 18 '11 at 16:02
2

You should create an "init" method that will serve as a constructor for your CFC. You can then instantiate the CFCs and store them in a shared scope, most likely the application scope. From here, to use this CFC via AJAX, I typically will create a remote facade. Basically this is another CFC that will directly access the CFC instance in the application scope. It will implement the methods you need to access via Ajax, expose them using access="remote" giving your application access to the access="public" methods from the actual CFC. In this case it is generally accepted that the remote facade can access the application scope directly as part of the design pattern.

A simple example:

example.cfc:

<cfcomponent output="false">
    <cffunction name="init" access="public" output="false" returntype="any">
        <cfargument name="dsn" type="string" required="true" />
        <cfset variables.dsn = arguments.dsn />
        <cfreturn this />
    </cffunction>
    <cffunction name="doStuff" access="public" output="false" returntype="query">
        <cfset var q = "" />
        <cfquery name="q" datasource="#variables.dsn#">
        select stuff from tblStuff
        </cfquery>
        <cfreturn q />
    </cffunction>
</cfcomponent>

In your Application.cfc onApplicationStart() method:

<cfset application.example = createObject("component","example").init(dsn = "somedsn") />

remote.cfc:

<cfcomponent output="false">
    <cffunction name="doStuff" access="remote" returntype="query">
        <cfreturn application.example.doStuff() />
    </cffunction>
</cfcomponent>
Sean Coyne
  • 3,864
  • 21
  • 24
  • I'm sorry if I misunderstand, but, doesn't this fly in the face of what I'm trying to do? I'm trying to avoid accessing the APPLICATION and SESSION scopes within CFCs. – Eric Belair Jul 13 '11 at 19:23
  • As I noted: In this case it is generally accepted that the remote facade can access the application scope directly as part of the design pattern. In this case you are creating a remote facade to an instance of the object. – Sean Coyne Jul 14 '11 at 14:47
  • It removes the dependency on the shared scope from your actual object, which is what you are trying to do. No more need to access the session scope directly from your object. Only your remote facade depends on the shared scope, additionally, it only needs to access the shared scope to talk to the instance of the object you created. Ray Camden wrote a blog post recently where he uses this same method in an example. You can see it here: http://www.coldfusionjedi.com/index.cfm/2011/7/8/Some-thoughts-on-working-with-CFCs-remotely – Sean Coyne Jul 14 '11 at 14:53
  • 1
    Additionally, you could also use ColdSpring to manage and create the remote facade for you. – Sean Coyne Jul 14 '11 at 14:54
  • Good approach but doesn't address the need for the DSN/Schema to vary for each session. Would be fine though if you stored the CFC(s) in the session scope rather than the application scope. So application.example would be session.example, created in onSessionStart rather than onApplicationStart. The dsn/schema values passed in the init() could then be session-specific. If you don't want copies of your CFCs in every session then you'll need to follow bittersweetRyan's advice and pass the session vars in to every call to your application scoped cfcs. – CfSimplicity Jul 15 '11 at 07:46
1

Can you set your datasource variables in the onRequest or onRequestStart functions in your Application.cfc

<cffunction name="onSessionStart">
    <cfset session.dsn = _users_personal_dsn_ />
</cffunction>


<cffunction name="onRequestStart" >
   <cfset dsn = "#session.dsn#" />
</cffunction>

<cfquery name="qEmployees" datasource="#dsn#">
    SELECT *
    FROM #SESSION.DatabaseSchema#.Employees
</cfquery>

etc.

not sure if that will work [not tested - actually feels a bit sloppy] -sean

Sean Kimball
  • 4,506
  • 9
  • 42
  • 73
  • Do you mean making copies of the SESSION variables in the REQUEST Scope? So if SESSION.DataSourceName is set to "customersX", in onRequestStart I set REQUEST.DataSourceName to SESSION.DataSourceName and just use REQUEST.DataSourceName in all of my CFQUERY tags? I actually like this idea - it's similar to how I use UDFs (Ben Nadel's model of storing the UDFs in the APPLICATION Scope, and then copying it to the REQUEST Scope in onRequestStart. – Eric Belair Jul 13 '11 at 19:21
  • If anyone sees something wrong with this implementation, please let me know, because this is the way I'm leaning. – Eric Belair Jul 13 '11 at 19:51
  • I'd say this is worse than using app or session scoped vars, because now the dsn is not scoped at all, meaning it could more easily be overwritten accidentally! Surely the reason you want to avoid app or session in your CFCs is so that they are encapsulated: ie all variables are explicitly passed in and out, rather than depending on external scopes (app, session, request, variables) being available. – CfSimplicity Jul 15 '11 at 07:57
1

The scope you choose (for any variation of this question, not just for DSNs) should be based on whether the lifetime of the value is the same as the lifetime of the scope.

In our application, the DSN is just set once in the lifetime of the application, so we have an application.config struct that gets created (parsed from a file) in onApplicationStart, and within it is application.config.dsn

If your value really does change between sessions, but not over the life of a session, go ahead and use the session scope.

If your value could change for any given request, but not in the middle of a request, put it in the request scope.

That said, still heed ryan's advice and add optional arguments that only default to this value: being flexible is always the best.

0

My suggestion for this is to create a base class and then have your components that need database access extend that component. It doesn't have to be in the immediate parent hierarchy but somewhere down the line.

They goal is to do two things, keep the cfc abstracted from the main program and keep it easily configurable. This accomplishes both.

So your CFC that queries the database would look something like this :

<cfcomponent extends="DataAccessBase">
<cffunction name="myFunction" access="public" returntype="string">
    <cfquery datasource="#getDSN()#" name="qStuff">select * from table</cfquery>
</cffunction>

The key above is the extends="DataAccessBase" portion. This adds the layer of abstraction where you can control the data access at one configurable point, but it's not tied to the application itself, leaving the component abstracted from where it's implemented.

Your DataAccessBase.cfc could look something like this:

<cfcomponent>
<cffunction name="loadSettings">
    <cfparam name="request.settings" default="#structNew()#">
    <cfparam name="request.settigns.loaded" default="false">
    <cfif request.settings.loaded eq false>
        <!--- load settings from resource bundle etc --->
        <cfset request.settings.dsn     = 'myDSN'>
        <cfset request.settings.loaded  = true>
    </cfif>
</cffunction>
<cffunction name="getDsn" access="public" returntype="string">
    <cfset loadSettings()>
    <cfreturn request.settings.dsn>
</cffunction>

You can of course get more intricate with how you configure and store the settings etc, but that's out of scope of the question I think. :)

I don't see any reason to pass the DSN with every method call. Yes, it works, but it's not necessary. The components are developed with a built-in assumption of the datastructure so you know that it is not going to change from a addItem() call to a updateItem() call, thus its duplication of work which means additional points of failure. :P

Make sense?

Nate
  • 2,881
  • 1
  • 14
  • 14