Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with:

Exercise List: Building The Domain Model (Saucy)

By Ben Nadel on

Click here to view the current Online Demo

Click here to view / download the current Code Base

After having started coding the domain model under the heavy influence of database design and implementation, I had a bit of a "ah-ha!" moment; for a second, I started to see what some of the others were talking about in terms of object composition. I tried to really release myself from the hold of the database and purely think about what objects define the problem space that is Exercise List.

I started to come up with ColdFusion components that I never thought I would need to instantiate, but somehow it makes sense; instead of referring to foreign keys within a record, I am referring to foreign "objects" which then have some sort of ID getter method. For example, as I discussed in my previous post, instead of creating an object that almost exactly modelled my el_exercise_joint_jn database table, I started to think in terms of an object that would compose four other objects that modelled the various aspects of the relationship. I think this was a huge mental barrier for me to break through, and hopefully, I am now on a better track.

Sitting down and rethinking it, here is what I have now come up with in terms of a domain model:

CFC.cfc

This is just a base ColdFusion component that the other components will extend. It simply has some utility methods.

Exercise.cfc

This is the primary domain object that models our Exercise. It contains a few simple values as well as an array of JointAspect.cfc instances.

ExerciseService.cfc

This is the utility object for exercises. It knows how to read, save, and delete a given exercise. It also knows how to search exercises and find related exercises. The one part that I really started to get fuzzy on was the whole "JointAspect" creation. To me, JointAspects really have no meaning outside of the context of an exercise; they are not an object that should ever really exist on their own. Therefore, I am giving the ExerciseService.cfc ColdFusion component the functionality to populate a JointAspect.cfc instance. When you think about application "responsibilities", I feel that setting up these aspects is really only ever the responsibility of the Exercise service class and not of some JointAspect service class.

I have read a lot about the whole idea behind a Gateway object and a Data Access Object (DAO) in object oriented programming (and I am sure I misunderstood it all), but I didn't see the need for it in this application. As such, I put the CRUD actions as well as the gateway actions into this Service object. Furthermore, I don't really see the need to break out Create and Update into different methods; to me, this is not the best form of encapsulation. I feel like create vs. update is an implementation idea that should be hidden from the end user. As such, I have wrapped up this functionality into a Save() method which does the create vs. update checking within it.

Joint.cfc

This models a joint and only has simple ID and name values.

JointService.cfc

This is the service class for the joint objects. It knows how to create a new instance of the Joint.cfc object as well as returning a query of all joints.

JointAction.cfc

This models a joint action and only has simple ID and name values.

JointActionService.cfc

This is the service class for Joint Action objects. It knows how to create a new instance of the JointAction.cfc object as well as returning a query of all joint actions.

MovementPlane.cfc

This models a movement plane and only has simple ID and name values.

MovementPlaneService.cfc

This is the service class for Movement Plane objects. It knows how to create a new instance of the MovementPlane.cfc object as well as returning a query of all movement planes.

MovementSymmetry.cfc

This models a movement symmetry and only has simple ID and name values.

MovementSymmetryService.cfc

This is the service class for Movement Symmetry objects. It knows how to create a new instance of the MovementSymmetry.cfc object as well as returning a query of all movement symmetries.

JointAspect.cfc

This is the object that pulls four of the above objects together - Joint, Joint Action, Movement Plane, and Movement Symmetry. It doesn't need to keep track of any exercise ID because it will never exist outside of the context of an Exercise object which is how it will be referenced when being created or updated.

JointAspectService.cfc

This is the service class for the Joint Aspect object. This class was a little uncomfortable for me; it can create a new instance of the JointAspect.cfc object, but I didn't want it to try and populate the composed objects (Joint, Joint Action, Movement Plane, Movement Symmetry) - I wanted that to be the responsibility of the ExerciseService.cfc utility class. But, within this class, I have to, for the first time, tackle the idea of an undefined property; what if we have a JointAspect.cfc that is not fully defined (for example, Movement Symmetry is not set). In the database, this is modelled by a zeroed-out foreign key. But, what does that mean in terms of objects? Does it mean the value is an empty string rather than a CFC instance? Does it mean it has a CFC instance that is not initialized?

I didn't know how to tackle this, so I decided that a JointAspect.cfc should always contain a valid CFC instance for each of it's composed objects. If a property is not set, then the composed object will just return Zero for the ID and empty string for the name. This way, I can keep the code that utilized the JointAspect.cfc as uniform as possible and I never have to worry about checking data types returned from the various JointAspect.cfc getter methods.

I am not sure how I feel about this decision, but it was the best idea that I could come up with.

ServiceFactory.cfc

The service factory is basically like the hub of an object based hub-and-spoke model; it creates and caches all of the singleton objects which are the other "Service" objects defined above. It then has a getter method for Service class retrieval. All of the other service classes (ex. JointService.cfc, JointActionService.cfc) are then passed a reference to this Service Factory during their initialization. This is done so that one service class can refer to another service class without having to have every single service reference passed in.

I figure the ServiceFactory.cfc is going to be created and then cached within the APPLICATION scope. This will, in turn, persist all the other singleton service objects in the application.

Ok, so this object oriented programming approach is such a huge departure for me from my standard mentality, but I think maybe I am starting to see the light at the end of the very long, dark tunnel. I am starting to be able to think about Objects outside of the context of the database. Having thought out the objects, I would still not change the database model at all; I am not sure why thinking in terms of objects would ever change my database structure. The database structure is based on normalized data relationships, not on object models. My gut feeling tells me that if objects every influenced my database design, then I didn't fully understand the application data when I built the database schema.

The next step is to start integrating these ColdFusion components into my code. Of course, any an all feedback is hugely appreciated as I really don't know what I am doing.


 
 
 

 
Domain Objects Facilitate The Relationship Between Domain Objects  
 
 
 


Reader Comments

Hi Ben,

I'm enjoying this series! Explorations into OO and domain modeling total fascinate me.

One observation about your ServiceFactory CFC cfcs, since the ServiceFactory is not creating new instances of a service when the GetService("ServiceName") method is called, that class acts more like a ServiceLocater than a factory. And CFC.cfc is more like a factory, because it is actually creating object instances. Gets the job done though, so the names are irrelevant.

Keep it up!

Paul

@Paul,

I don't really know what any of the proper names are. In my readings I have seen so many names for so many things and I think that a lot of them are used interchangably. I can't keep it all straight.

That being said, I am happy that you are enjoying the series. I really want to wrap my head around this stuff. I hope it gets me somewhere.

I have something similar to your CFC.cfc called abstractCFC.cfc

It contains:

isSingleton(): returns true if the component should be treated as a singleton

getInstanceID(): returns a uuid for the instance

getScope(): returns the the scope the current instance is referenced within (APPLICATION, SESSION, or REQUEST)

killInstance(): removes the instance from my instance and event tracking lists.

Utility functions (to simplify my code)

newEvent(id,{attributes}): used to create a new instance of an event object

listenFor("event list"): registers the instance as a listener for one or more events, called automatically when the instance is created

getRequestManager(): returns a pointer to the request manager - used to get/set information unique to the current request, and contains the factory methods for any instances to be created in the request scope, tracks the instances created in the request scope, and routes events to instances in the request scope that are listening for the event.

getSessionManager(): same except for the session

getApplicationManager(): same except for the application

getInstance("component"): either creates a new instance, or returns an existing one (singleton)

Logic of getInstance()
Ask the request manager if it has an instance of the current object, if it does, ask if it is a singleton,
- if yes-yes - return the existing singleton.
- if yes-no - return a new instance registered in the request scope
- if no-no - repeat the process for the sessionManager, then the applicationManager

If there are no instances of the requested component in the three scopes, call the factory method of each Manager starting with the Request Manager, until you get an instance

If no instances returned by any of the factory methods, return an error object.

@Steve,

Is this part of some greater framework? I am curious as to what the Event stuff is? Is this to be used in conjunction with some sort of MVC framework where communication is done via events.

I am cautious about the scope-related methods (getScope() and killInstance()). Does this require the CFC instance to have to know about where it is being stored?

I have to say, Steve, between this, your session proxy management, your data validation and CMS stuff, you have a very interesting set of solutions working over there. I can't wait till you get some more stuff up on Reality Storm so I can mooch of your insight.

@Ben

I guess I am making a framework for myself, more for geeking satisfaction than anything else.

getScope() and killInstance() tie in to my getInstance() function.

Whenever a new instance is created, it is created through a scopeManager, and is registered within the manager.

Essentially in each manager I have a struct
instances["componentname"]["instanceUUID"] = reference to instance.
(note the component name is the package name with the . replaced with _)

When I call getScope(), I ask each scope manager if it is tracking the current instance (identified by it's instanceUUID) by doing a struct key exists on the above structure.

To prevent wierdness, I've designed my system so that an instance can only ever exist in one scope at a time, also I try to limit the instances referenced by the application and session manager's to singletons.

I used something similar for the events. When a new instance is created, I call the instance's listenFor() function, and add a reference to the instance for each event to the structure:
listeners["eventname"] = reference to listening instance

(I mistyped earlier, the listenFor() function doesn't take any arguments.)

When I announce an event (o_event.announce()) it is automatically passed to each manager (starting with the request manager) and through each manager routed to the listening instances.

I use the event approach when I don't need a response from the listening instance. e.g. my logFacade (all events) and my SearchFacade and RSSFacade listen for Publish events.

I plan to make good use of the new CFTHREAD tag in my event model.

The killInstance() function removes the instance references from the sturctures I mentioned above (I haven't needed to use this yet)

I've been working with Coldfusion on and off since 98, and for the last few years I haven't been doing much coding, and thought that the release of Coldfusion 8 was a good reason to start playing with all this again.

I kept coming across your site when I was researching CF8's new functionality, so thought I'd start commenting.

I hope to get my ReaityStorm site up again early in the new year.

@Steve,

This is all cool stuff. I am glad that you are back in the game with ColdFusion 8. As of late, I have become a huge fan of event listening. I haven't done much with it in ColdFusion, but I have been really enjoying it in Flash ActionScript. It just makes communication work so well. It's funny, you'd think that the fact that I like it so much in Flash would mean I would love it in ColdFusion. I have to start playing around with that stuff.

I appreciate that you have started participating in my blog :)

Started playing with cfthread to see how it would work with me event managers.

At first I put it in my event broadcaster function, so each listening object that responds to the event receives it's own thread...

The performance was horrible.

After a bit of poking around I realized that CFTHREAD makes a deep copy of each variable/object passed to it, this makes sense, but was killing performance

So I've moved CFTHREAD in to the listening objects, and only pass the thread simple values (strings, integers etc.) that seems to work.

I now use threads for updating my logfile (all events), search-engine (contentPublish event), and RSS feed (contentPublish event, new entity) .

@Steve,

That sounds good. Yeah, I think keeping it to simple values is good. I am not 100% sure about this concept of deep copy variable passing. What if you pass in the #SESSION# object or the #APPLICATION# object? I doubt that these get duplicated, but rather passed by reference. I have not tested this, but that is what my gut tells me.