« Back
in coldfusion events read.

How To Implement Event Listeners in ColdFusion.

I explore the idea of bringing event driven programming to ColdFusion 10+ in this post because it's something that's not natively supported in ColdFusion and I wanted to reduce the amount of code I had to write and increase visibility.

What started this was that we had an Excel cfc that did some pretty complicated parsing. When we needed another Excel parser that kept most of the same logic except tweaked for another client, it was copy and pasted and certain methods were modified. As you can imagine this lead to duplicated code which needed to be maintained more than once and was a bit convoluted.

So I thought, while it was parsing, what if my derivative Excel cfc inherited all the goodies of the original and only dealt with the customizations on the cells it needed to for that client.

I would like to present here a event driven solution using marriage as an example.

Architecture of the Event Emitter

  • UserController.cfc this is the controller in an MVC pattern where the main interactions and remote calls happen. It will take a person named "Min" and marry her to Tony.
  • UserService.cfc this is the marriage service and handles the business logic on the User entities (Min and Tony).

The page runner instantiates an instance of UserController.cfc and calls the method Execute(). UserController.cfc then calls UserService to perform the marryTonyTo() method and dumps the results.

We want UserController.cfc to listen to any interesting events and perform its own special actions based on what the UserService does. It doesn't care about the rest.

User Service - The Event Emitter

The important bits are as follows:

Getting a reference to the caller

The controller will create an instance of this UserService and pass itself in so that the UserService has a context of who called it.

public UserService function init(required struct caller){  
        this._caller = caller;
        return this;
    }

The main method is marryTonyTo(). When Tony gets married, he will collect all the gifts from the relatives and store them in his Inventory. Any cash gets deposited into his bank account. The business logic also dictates that he loses his freedom and the tradition is that he also change his last name to his partner's last name. Great, all business logic on the User entity is encapsulated.

Broadcasting the Important Event

The controller though is interested in when cash gets deposited into Tony's bank account so it will listen in on that event which gets emitted when Tony receives cash.

this._user = emit(this._user,"onReceivedCash");  

You don't have to return a value here, I just happened to set the user state to whatever gets returned to from the controller for demonstration purposes.

Emitting the Event

The event emitter is the private function at the end:

private struct function emit(required struct evt, required string method){  
        //If the calling cfc has a method to handle an event such as "onReceivedCash", then we call it.
        if(structKeyExists(this._caller,method)){
            return invoke(this._caller,method,[evt]);
        }

    }

Since the UserService has a reference to the caller, it can check if the controller has a method to handle this event. If it does, then it calls that method and passes in the relevant object that the controller might be interested in. The controller only need to implement onReceivedCash if it's interested when Tony receives cash.

Here is the whole UserService.cfc. I've hardcoded the user Tony for brevity purposes. It should really stand as a Person type on its own.

component {

    this._caller = {};

    //Explicitly setting user to Tony, this should be a User object. This example is given for brevity.
    this._user = {
        firstName = "Tony",
        lastName = "Truong",
        hasFreedom = true,
        bankBalance = 1000.00,
        giftInventory = ArrayNew(1)
    };

    public UserService function init(required struct caller){
        this._caller = caller;
        return this;
    }

    public struct function marryTonyTo(required struct partner, required array gifts){
        this._user.lastName = partner.Lastname;
        this._user.hasFreedom = false;
        storeGiftsFromRelatives(gifts);

        return this._user;
    }


    private void function storeGiftsFromRelatives(required array gifts){

        for(var gift in gifts){

            if(gift.type=="stuff")
            {
                ArrayAppend(this._user.giftInventory,gift.value);               
            } 

            else if (gift.type == "cash")
            {

                //Increase Tony's bank balance and let the calling cfc know that the event "onReceivedCash" happened because Tony cares about money
                this._user.bankBalance = this._user.bankBalance + gift.value;               
                this._user = emit(this._user,"onReceivedCash");

            }           
        }       
    }

    private struct function emit(required struct evt, required string method){
        //If the calling cfc has a method to handle an event such as "onReceivedCash", then we call it.
        if(structKeyExists(this._caller,method)){
            return invoke(this._caller,method,[evt]);
        }

    }
}

The Controller with Event Listener

I've hard coded the gifts we can receive and the partner for demonstration purposes as well:

    this._partner = {
        firstName = "Min",
        lastName = "Liu",
        hasFreedom = true,
        bankBalance = 0,
        giftInventory = ArrayNew(1)
    };

    this._gifts = [
        {type="stuff",  value = "Toaster"},
        {type="stuff",  value = "Letter of Condolences"},
        {type="cash",   value = 10000},
        {type="stuff",  value = "Ball & Chainz"}
    ];

So as it stands, before Tony marries Min, she has $0 in the bank account and they have 1 generous relative who's about to give them $10,000. Not that Tony is any more wealthy as he only has $1000 in his bank account.

When the controller is instantiated, it knows it's going to use the UserService. So it passes its context to the UserService (so that the user service can call its event).

public UserController function init(){  
        this._userService = new UserService(this);
        return this;
    }

The main method whether called remotely or not calls the UserService to perform the marriage and dumps the results when things are done.

public void function Execute(){  
        var result=this._userService.marryTonyTo(this._partner,this._gifts);
        writeDump(result);
        writeDump(this._partner);
    }

Implement the Event Listener

The controller can choose to listen to any events that the UserService emits by implementing the name of the event as a method.

public struct function onReceivedCash(required struct evt){  
        //The evt received is the User, in this case Tony.
        this._partner.bankBalance += evt.bankBalance;
        evt.bankBalance = 0; //such is life 

        return evt;
    }

This does not break the execution loop in the UserService when it's stockpiling the gifts.

As per tradition, any money Tony gets into his account immediate goes to his partner, because that's how it typically goes right? Well...at least he got that letter of condolences in his inventory.

Here's the complete controller:

component {

    this._userService = {};

    //Statically define user struct like the one in UserService for brevity
    this._partner = {
        firstName = "Min",
        lastName = "Liu",
        hasFreedom = true,
        bankBalance = 0,
        giftInventory = ArrayNew(1)
    };

    this._gifts = [
        {type="stuff",  value = "Toaster"},
        {type="stuff",  value = "Letter of Condolences"},
        {type="cash",   value = 10000},
        {type="stuff",  value = "Ball & Chainz"}
    ];



    public UserController function init(){
        this._userService = new UserService(this);
        return this;
    }

    /*
        Partner struct is also of type User like in the UserService.cfc
    */
    public void function Execute(){
        var result=this._userService.marryTonyTo(this._partner,this._gifts);
        writeDump(result);
        writeDump(this._partner);
    }

    public struct function onReceivedCash(required struct evt){
        //The evt received is the User, in this case Tony.
        this._partner.bankBalance += evt.bankBalance;
        evt.bankBalance = 0; //such is life 

        return evt;
    }

}

To run it we just make a test cfm file:

<cfscript>  
    controller = new UserController();

    controller.Execute();

</cfscript>  

With the final results:

Event Emitter Results

  • Tony's last name started as Truong and was changed to Liu
  • All the gift items were added to his inventory
  • All his money was transferred to his partner's account
  • Last but not least, he lost his freedom

Of course this is all just for demonstration purposes =].

comments powered by Disqus