10

I have a “best-practices” question in regards to the correct way to instance CFCs that all need to talk to each other in a given project.

Let’s say for example you have a web application that has a bunch of different modules in it:

  • Online Calendar
  • Online Store
  • Blog
  • File Manager (uploading/downloading/processing files)
  • User Accounts

Each of these modules is nicely organized so that the functions that pertain to each module are contained within separate CFC files:

  • Calendar.cfc
  • Store.cfc
  • Blog.cfc
  • Files.cfc
  • Users.cfc

Each CFC contains functions appropriate for that particular module. For example, the Users.cfc contains functions pertaining to logging users on/off, updating account info etc… Sometimes a CFC might need to reference a function in another CFC, for example, if the store (Store.cfc) needs to get information from a customer (Users.cfc). However, I'm not sure of the correct way to accomplish this. There are a couple ways that I've been playing with to allow my CFC's to reference each other:

Method 1: Within a CFC, instance the other CFC’s that you’re going to need:

<!--- Store.cfc --->
<cfcomponent>

<!--- instance all the CFC’s we will need here --->
<cfset usersCFC = CreateObject("component","users") />
<cfset filesCFC = CreateObject("component","files") />

<cffunction name="storeAction">

     <cfset var customerInfo = usersCFC.getUser(1) />

This approach seems to work most of the time unless some of the instanced CFC’s also instance the CFC’s that instance them. For example: If Users.cfc instances Files.cfc and Files.cfc also instances Users.cfc. I’ve run into problems with occasional dreaded NULL NULL errors with this probably because of some type of infinite recursion issue.

Method 2: Instance any needed CFCs inside a CFC’s function scope (this seems to prevent the recursion issues):

<!--- Store.cfc --->
<cfcomponent>

     <cffunction name="storeAction">

          <!--- create a struct to keep all this function’s variables --->
           <cfset var local = structNew() />

          <!--- instance all the CFC’s we will need here --->
           <cfset local.usersCFC = CreateObject("component","users") />
           <cfset local.filesCFC = CreateObject("component","files") />

          <cfset var customerInfo = local.usersCFC.getUser(1) />

My concern with this approach is that it may not be as efficient in terms of memory and processing efficiency because you wind up instancing the same CFC’s multiple times for each function that needs it. However it does solve the problem from method 1 of infinite recursion by isolating the CFCs to their respective function scopes.

One thing I thought of based on things I've seen online and articles on object oriented programming is to take advantage of a “Base.cfc” which uses the “extends” property of the cfcompontent tag to instance all of the CFC's in the application. However, I've never tested this type of setup before and I'm not sure if this is the ideal way to allow all my CFCs to talk to each other especially since I believe using extends overwrites functions if any of them share a common function name (e.g. "init()").

<!--- Base.cfc --->
<cfcomponent extends="calendar store blog users files">

What is the correct "best-practices" method for solving this type of problem?

Fish Below the Ice
  • 1,265
  • 13
  • 23
Dave L
  • 2,465
  • 1
  • 14
  • 20
  • I would only reference other cfc's in the functions that need them, not as a global variable within the cfc. That would be your method 2. – Dan Bracuk Nov 07 '14 at 17:29
  • 1
    David, the 2 answers below don't really answer your question RE efficient memory and processing. In fact, by introducing additional CFCs (beans, service factory etc) they may exacerbate any problems you are having. They _are_ however what the community currently considers "best practice." for working with CFCs that require other objects. As for memory, you should test the size of your objects. Instantiating a CFC from within a CFC using the local scope is common - unless your objects have very large data bits in them it will work on fairly high traffic applications. But testing is your friend. – Mark A Kruger Nov 07 '14 at 18:06
  • @DanBracuk, Why would you recommend that services be instantiated inside a method call? If you do that, EVERY time the method is called you would need to instantiate the related service....that is the complete opposite of what one would need. – Scott Stroz Nov 07 '14 at 18:10
  • This is a problem that has been solved....several times over...by people smarter than you and I. John Whish's answer is really the only acceptable one. If your application has gotten to the point where you have dependency issues like you describe, you really need to use something to manage them. Writing a new one from scratch would be nothing more than re-inventing the wheel. – Scott Stroz Nov 07 '14 at 18:13
  • Everything Stroz and Whish say. – Joe Rinehart Nov 07 '14 at 18:17

2 Answers2

16

If each of your CFC instances are intended to be singletons (i.e. you only need one instance of it in your application), then you definitely want to looking into Dependancy Injection. There are three main Dependancy Injection frameworks for CF; ColdSpring, WireBox and DI/1.

I'd suggest you look at DI/1 or WireBox as ColdSpring hasn't been updated for a while.

The wiki page for DI/1 is here: https://github.com/framework-one/di1/wiki/Getting-Started-with-Inject-One

Wirebox wiki page is here: http://wiki.coldbox.org/wiki/WireBox.cfm

Essentially what these frameworks do is to create (instantiate) your CFCs (beans) and then handles the dependancies they have on each other. So when you need to get your instantiated CFC it's already wired up and ready to go.

Dependancy Injection is also sometimes called IoC (inversion of control) and is a common design pattern used in many languages.

Hope that helps and good luck!

John Whish
  • 2,926
  • 15
  • 20
  • Thanks for the detailed info. I'm totally new to the concept of "Dependancy Injection" so I definitely will start with the web sites Wirebox and DI/1 and soak up as much information as I can. The concept itself looks a little intimidating so If anyone can recommend a good "dummy's guide" to Dependency Injection please let me know. – Dave L Nov 08 '14 at 17:17
  • @DavidLevin I started out doing DI with ColdSpring which requires quite a bit of config with XML so completely understand that it can be intimidating at first glance! I'm not sure I can give you a good guide in 400 odd characters that would answer all your questions. Feel free to contact me off list. – John Whish Nov 09 '14 at 21:00
  • 1
    @DavidLevin - I managed to hit return and save the comment and then was not allowed to edit after 5 mins, so here's the rest of what I wanted to say... :) In your example, when you ask the DI framework for an instance of Store, it will return the Store object with users and files instances already created and 'injected'. How the DI framework knows what the dependencies are varies a bit between the 3 DI frameworks I mentioned so I can't really give you a good guide in 400 odd characters that would answer all your questions. – John Whish Nov 09 '14 at 21:08
1

If your cfcs not related to each other the base.cfc concept does not fit. The inheritance is for classes have common things that can inherit from each other. For example if you have User.cfc and you want to added new cfc called customer.cfc I would inherit from User and override some functionality or add some without touching the actual user.cfc. So, back to your question, since the CFC are not related or have common between each other and to avoid cross referencing, I will create serviceFactory holds instances of cfcs like this

    component name="ServiceFactory"
    {
     function init(){
    return this;
     }

     public User function getUserService(){
    return new User();
     }

     public Calendar function getCalendar(){
    return new Calendar(); 
     } 
     }

and referencing it by

serviceFactory= new ServiceFactory();
userService = serviceFactory.getUserService();

Keep in mind this approach works only if you have sort of another CFC to manage your logic

you can do the same for all other services. If your functions are static you can save your services in application scope and instantiate it only one time (like singleton).

The third option you have is DI(dependency Injection) framework

Click
  • 123
  • 5