Time to Rethink the (Table Data) Gateway?
I have already argued that that encapsulating property access using an IBO makes sense whether you are returning one record or many. But in this post I wanted to look at some of the other outstanding questions I had about Gateways, to look at some non-CF references and to ask whether anyone could shed some light on how we ended up with this “convention” in the CF world.
The first question you have to answer when deciding to use Gateways is what exactly they should include. I’ve put together some possible ways of distinguishing what goes into a Gateway below using the examples of whether they should only be used for “get” methods, whether they should be used for all “filter” based methods or whether they should be used for all methods operating on more than one record.
Get Only
One approach to gateways is to use them to handle all “get” methods that could return more than one record – and nothing else. With this approach, getbyUserbyIDList(“3,4,7”), getList(“where Price > 12”) and getUserSignupReport(“01/02/2005”,”01/02/2006”) are all example of methods you might call against your gateway.
All Filter Methods
Of course, all SQL queries are based on some kind of “where” filter (except for a getAll() method), but I am using “filter method” to describe a method that is designed to return a fundamentally indeterminate number of records (orders so far this year, number of users who are between 20 and 25, number of products for under $100, etc.).
I am contrasting that with methods that are designed to return a known number of records or 1/0 records per requested value. Examples of methods that would NOT be included in the Gateway would be delete users 23 and 41, get product where SKU is ARA3112 or get user where email = test@test.com (assuming product SKU and user email are defined/constrained as unique attributes that will only ever return 0 or 1 record per value in your particular application).
So, with all filter methods in a gateway it might have methods like deleteAbandonedCarts(), getAllUsers(), and getCheapProducts(“$50”).
All Multi-Record Methods
In this case, gateways would handle everything except for a single record create, read, update and delete. So saveProductPrices(ProductCollection) and getUserbyIDList(“7,3”) would be added to all of the methods in the “all filter methods” approach (unless you used an iterator in the service layer to save and/or get every record one at a time making multiple calls against the DAO – could make sense for saves in some use cases).
I would be interested to hear how people handle saving multiple records, deleting based on filters, inserting multiple records and the other methods that could be argued either way to see how strong the consensus is for exactly what should go in a gateway.
History
Martin Fowler explored the the (Table Data) Gateway in his Patters of Enterprise Architecture book (a summary of the patterns is available online). In his reference architecture the Gateway handles ALL database interactions (replacing the DAO) and is referenced directly from the business object (not a service layer).
If you look at the core J2EE patterns, DAOyou will see that the intent of the DAO there seems very similar to the intent of Fowlers Gateway pattern. The most obvious distinction is that the DAO explicitly uses a transfer object to interact with the business object whereas it isn’t clear what parameters/data types a Gateway uses to interact with a business object.
Some time ago there was an interesting articlecomparing the two approaches. I think the original author was right that the two patterns are fundamentally similar and that the main difference is that Fowlers Gateway is simply less prescriptive about the method signatures. The only definitive difference is that a DAO should use a transfer object whereas it is not clear whether a Gateway should or not.
I actually like active record patterns. Why should a business object use a transfer object (especially in CF where object instantiation is not inexpensive) when it can simply pass itself to the DAO to work with directly? The biggest benefit of a TO is that it will usually have just the properties that need to be saved and no others, allowing the DAO to know which properties to persist, but a fairly simple explicit metadata convention can get you the same benefits without the additional object I may start to use TOs here down the line, but for now I find them to be overkill for my needs (and I notice that most CF apps I see don’t use TO’s between the business object and DAO but rather pass the business object via the service layer AS a transfer object).
It seems to me that at some point, someone latched onto the distinction between a DAO and a Gateway as whether get operations returned an object (DAO) or maybe didn’t (Gateway). I don’t think there is anything wrong with such an approach (apart from the fact that you now have your naked recordsets running round the application with no way to easily vary the getting of the properties other than through some kind of hacky loop I the service layer which doesn’t scale well in terms of maintainability for complex solutions). I do think it is time to revisit the Gateway to see if it is really as useful as its current ubiquity in the CF world would suggest.
What do you think?



Sorry, this isn't quite this-post specific, but I had a thought about your IBOs. All of the IBO data i gotten via getters() that then either call other methods or query columns or what have you (the idea behind the getter() is that I dont have to know what is going on underneath).
Now, let's say I have two pages: one lists all employees by name and one that lists all employees by name and has their birthdays. I assume these are actually two different queries as one doesn't need birthday to be returned. But, are they using the same IBO? Or does each use case of a query have its own IBO class?
I assume the IBO that uses the birthdays would have some sort of get( "birthday" ) method. If they use the same IBO, What happens when this is called on the query that did not return birthday records. Does it thrown an error? Return empty string?
Thanks!
I have a posting on partially loaded objects which I believe are valid and useful. See:
http://www.pbell.com/index.cfm/2006/9/17/Partially...
Basically, I have one IBO class per business object (User, Invoice, Product, etc.).
Each page would use a different service method. One might be UserService.getUserListwithBirthdays() and another might be UserService.getUserListwithContactDetails(). Both call the same BaseService.getbyFilter() using different filters and ATTRIBUTE LISTS. One would include the age attribute, the other wouldn't. DAO converts age to its dependency and includes the DoB field in one query but not the other.
Behavior of unloaded properties is configurable. Empty string, null data type appropriate value (0 for number, null for string, etc.), return a "INVALID ATTRIBUTE" string, or throw an error of some kind.
If you wanted to, you could just call a fully loaded IBO, cache it in app scope (using DAO caching - something I'll add shortly) you could do that as well just create a UserService.getLoadedUserList() with all of the attributes in the attribute name list.
This'll make more sense when I clean and republish code - going to publish whole set as LightBase. Personal stuff going on and moving this w/e but will get code done ASAP!
Let me know if any more qns!
The row table gateway:
http://www.martinfowler.com/eaaCatalog/rowDataGate...
I'm not generally a fan of a single method configurably returning different data types, but there is nothing wrong with that approach and I think there are use cases where it is absolutely the right way to go.
I don't have a need to do that here as I am happy with always getting my nicely encapsulated IBO which you could then pull a query or struct from if you really had that need, so I wouldn't have a need to consider that approach, but if it works for your use case . . . !
That sounds good to me. Thanks for the answers.