Brain Kotek wrote a lengthy and informative comment to my last blog post. I started composing a response as a comment of my own, but it got very long, and I think that both Brian's comment and my response contribute greatly to the series, so I've turned my response into a blog post. I'm including Brian's comments in quotes throughout this post.
I was going to describe my Abstract Gateway Object in this post, but during a conversation with a fellow developer it was suggested that I should take a moment to describe a Concrete Service Object, as there was still a bit of confusion in his mind about how I use the Abstract Service Object.
To recap a bit, I have an Abstract Service Object and it is used as an extension point for most of my Concrete Service Objects. Perhaps a bit more of a definition is in order.
- The Abstract Service Object
- Is never instantiated as an object.
- Cannot be used as is.
- Is only ever used as a base object for Concrete Service Objects.
- There is only one Abstract Service Object, called AbstractService.cfc.
- Does not have any Transfer classes associated with it.
- Concrete Service Objects
- Are instantiated as objects.
- Methods on them are called by Controllers, other Concrete Service Objects and Business Objects.
- Most extend AbstractService.cfc.
- There are many Concrete Service Objects, e.g., UserService.cfc, ProductService.cfc, ReviewService.cfc, etc.
- Have one "main" Transfer class associated with them, but can interact with others via code specific to the Concrete Service.
In the previous post in the series I discussed how I base most of my objects on Abstract Objects, which allows me to eliminate a lot of duplicate code. I then took a look at one method in my AbstractService object to demonstrate this. In this post I'm going to look at the rest of the methods in the AbstractService, so be prepared for a lot of code.
Let's start with the Init() method and the Configure() method:
<cfargument name="AppConfig" type="any" required="true" />
<cfset variables.Instance = StructNew() />
<cfset variables.Instance.AppConfig = arguments.AppConfig />
<cfset Configure() />
<cfreturn this />
<cffunction name="Configure" access="Public" returntype="void" output="false" hint="I am run by the Init() method">
In the previous post in the series I discussed the four types of objects that comprise my model. As I was creating instances of those object types I realized that they did a lot of the same things. For example, my UserService needed methods like getUser(), updateUser(), deleteUser() and listUsers(), while my ProductService needed getProduct(), updateProduct(), deleteProduct() and listProducts(). Not only do they need to do similar things, but they pretty much do them the same way. Now that's a lot of code duplication.
So I created an AbstractService, which each of my concrete Service Objects extend. My AbstractService has methods like Get(), Update(), Delete() and GetList(). For the most part, the code required for getUser() is identical to the code required for getProduct(), with the only difference being the business objects with which they interact (User and Product, respectively), so I was able to write a single, parameterized method, which will work for most of my Service Objects.
I think that the best place to start is with the high level architecture of my model. I have four types of objects in my model:
- Service Objects
- Gateway Objects
- Business Objects
- Utility Objects
Note that these names are not necessarily the generally accepted or "proper" names for these types of objects in the Object Oriented community. These are terms that I have chosen to use, and I will define what I mean by them.
I've now been using Transfer for about a year, and I've built up a bit of a standard way that I use it in my apps. I'm not suggesting that this is the best way (by any means), but I thought it would be worth posting about it to share my experiences and learn from others. So I'm starting a new series, simply called "How I Use Transfer".
I'm not sure how to best go about describing all that I do. A lot of it is really architectural and isn't specific to Transfer. To a certain extent Transfer could be switched out for another ORM without requiring a lot of code changes. So I suppose this is also about my approach to OO design with ColdFusion. Like others, I have found this to be a long and hard learning curve, and I still have a long way to go. It may be that much of what I'm about to document will change over the next several months, as I learn other ways of doing things, but hopefully it will still be of value, particularly to anyone just starting down this road.
So again, I'm not trying to document any kind of best practices. I already know that there are ideas I've read on other people's blogs that I'd like to implement but haven't yet. This is a snapshot, a point in time, something I'll probably revisit months or years from now and laugh about. But if others are going through the same thing as me, then maybe we can all learn together.
In my next post I intend to describe a high level overview of the architecture of my model.