For some reason Martin Fowler's thoughts on encapsulation in an
article he wrote called "Getter Eradicator" came barreling into my mind earlier today as I was thinking about modeling and OO design.
He says:
For me, the point of encapsulation isn't really about hiding the data, but in hiding design decisions, particularly in areas where those decisions may have to change. The internal data representation is one example of this, but not the only one and not always the best one. The protocol used to communicate with an external data store is a good example of encapsulation - one that's more about the messages to that store than it is about any data representation.
He highlites one of my recently adopted approaches to design... I'm starting to move further and further away from very busy services to simple services, factories, and very rich "beans". In fact, my "beans" are less and less simply TOs with get/set methods and more and more very robust objects that do far more than carry data around. Design decisions are hidden behind methods on these objects and encapsulation is maintained by composite objects.
So, for example, my User object would have a save() method. Save() itself does nothing but call UserDAO.save(this). After discussing this for hours and hours with folks like Mark Mandel and Sean Corfield, I'm becoming ever more convinced that this is an appropriate approach.
Working on cf.Objective()'s website is what really got me going down this path. If I can ask av Attendee if they've checked in with the hotel, then Attendee.isCheckedIn() is a reasonable method on my Attendee object. Furthermore, if they say "No." then I can say "Dude, you'd best go check in." The RegistrationDesk does all the heavy lifting, but Person.checkInToHotel() is the call that effectively gets the ball rolling.
That's a huge oversimplification and it would take a whole book to get all the thoughts, points, conversations and information down... but it really got me thinking about real-world modeling and what it means to have a robust business object in my application.
There ya have it...
Laterz!
Comments
Yeah - I've been singing this song for a while. I really like being able to put more into the beans and less into the service layer. It IS just a small semantic difference (UserService.save(User) gets replaced with User.save()) but I find the latter a little easier to work with as it keeps me "thinking in objects" rather than "thinking in procedural service layers which pass structs around that we happen to call beans and wrap with behaviorless getters and setters".
Only real question I have is whether to keep UserService.save() for remote calls to keep the facade simple or whether your remote facade should call a UserService.new() or UserService.getbywhatever() and then User.load(values) and then User.save(). Not something I've played with as I don't have remote calls to my apps yet. Any thoughts?
Posted By Peter Bell / Posted At 3/21/07 6:20 PM
The only issue that I might have with this is that it then requires your beans to do a lot more work to manage their relationships with other beans - assuming I understand correctly.
In your example, your Person bean would have to know about and be able to communicate with your RegistrationDesk bean (I'm assuming RegistrationDesk is a bean).
I'm not Mark or Sean, but that kind of raises my eyebrows a little bit. The more interaction is required, the fatter my beans become. No one likes an obese bean...right?
Is there more to this path that I'm not seeing?
Posted By Rob Wilkerson / Posted At 3/22/07 12:04 PM
But if your Person depends on your RegistrationDesk then that is just life. Putting that into PersonService and RegistrationDeskService doesn't lesser the dependencies - it just moves them from two files (the two beans) to four (the beans and their service classes or managers).
To get a feel for the kind of programming Jared is talking about (taken to the extreme) check out SmallTalk or for that matter a lot of Ruby code. In SmallTalk you have Person that combines aspects of both PersonService and PersonBean.
There are arguments both ways, but it is a valid approach to programming that has been pretty widely used outside of the CF and Java worlds (and it's an approach some Java programmers take too).
Posted By Peter Bell / Posted At 3/22/07 12:31 PM
Good point. :-)
Is there any additional danger of code duplication using this method? I'm also interested in the ability to extend by providing remote API access. Is it more or less dangerous to provide direct access to the bean rather than to a service layer? In re-reading your earlier comment, I guess I'm sort of echoing your question, but I'll leave it anyway.
The fat-service-skinny-bean approach makes sense in a lot of ways and provides a convenient front-end access layer, but it's a bit easier to think about things in the skinny-or-no-service-fat -bean methodology.
Posted By Rob Wilkerson / Posted At 3/22/07 12:37 PM
I see it this way:
User.save() calls UserDAO.save(this) internally. Why? Because it's the most efficient and syntactically accurate way to represent the action. However, the key here is that User.save() simply saves the data that's in the user... HOW it does it is irrelevant to code outside the User class itself. As long as you've been observing Separation of Concerns and, internally, your User class is relying on some other class to actually DO the saving, there's no violation of encapsulation. In the end, the number of objects being used to accomplish the work is the same and, therefore, you haven't ADDED any dependencies to your code.
Also note that the User object doesn't manage it's dependencies. In my case, My UserService (though a factory would be a better place to handle this) handles creation of a User, so my application code calls getUserService().getUser(). getUser() can take an ID argument and return a populated user, or it can simply return an empty User. In either case, UserService injects some objects into the User, like the DAOs, Gateways, and so on, that the User might need to do the tasks it should.
So the User doesn't manage it's relationships, they're assigned externally. That said, this may NOT be the best approach for all situations... or even for this one. It's just what I did while I was experimenting with the idea.
I'm also coming to the point where I see IoC's limitations and am thikinking that IoC is a great idea but not ALWAYS the answer. Sometimes you're just as well off having your Factories nice and static so they will inject things into other objects in the way and in the order you want them to... though this is something I see working better for transient BOs (things you might want per-request or in the session scope and more than one instance of which may exist at a time).
Posted By Jared Rypka-Hauer / Posted At 3/22/07 1:18 PM
I'm on board with your approach, Jared. Peter Bell has been a big influence for moving in this direction, I must admit. I can actually see a bit of a schism coming between favouring a rech domain model over heavy service layer. Can I ask how you are injecting your composites into you BOs? I recently produced a "domain-centric" template for Brian Rinaldi's Illudium PU-36 Code Generator. My business objects are injected with a singleton DAO in the current incantation, but I'm considering a Factory method approach instead.
One benefit I find in using a "domain-centric" model is that you can define semantic methods like execute() objects modeled from verbs. For example, with a a keyword search form, I use search.setKeywords(form.keywords). Then, result = search.execute() to return a recordset. And, of course, search.save(getInstance()) to store the result. Where getInstance() is a private method of the BO that returns the variables.instance
struct. I originally used search.save(this), but found it disconcerting to send the entire BO as an argument to a dependent object. Meaning, if the DAO is part of the BO, then using BO.save(this) sends the DAO into itself. Passing the variables.instance struct is like passing a cheap TO between the BO and DAO.
Posted By Paul Marcotte / Posted At 3/31/07 12:15 PM
I do something similar to what Jared does. I have both a UserService.getByUniquePropertyValueList() so I can get a collection of 1 or more users by any unique key - ID, email address, whatever. I also have a UserService.new() as I like the idea of a special method to encapsulate the creation. And I put both of these methods in BaseService so the same code works for Users, Products, Articles, Pages, Categoris and all other business objects - keeping my code nice and dry.
I personally use LightWire for DI of the DAO and a Metadata bean into my User transient so the BaseService.new() is the only place in my business object code that is factory aware and it just calls LightWire.getTransient("User") and LightWire handles the DI so I can see all of my configuration, wiring and dependencies for both transients and singletons in my single BeanConfig.cfc configuration bean.
All that said, Rob still makes a fair point. For remote apps you often have to support a richer service layer API as often you can't pass a rich business object over the wire. In practice, I plan to handle this using a thin "base remote facade" which provides a rich external API and maps that to actions on the business object. That said, I wouldn't be opposed to duplicating methods and having UserService.save() and User.save() as they are both just trivial delegates (UserDAO is doing the actual saving) so while I try to keep my code DRY, having more than one delegate to the same code to provide a richer API is an acceptable trade off between richness of API and clean code.
For example, even though getByID() is just a special case of getByUniquePropertyValueList() where the property value list is of IDs and where there is only one element in the list, I provide the method to make this common use case easier to call (of course, it just parameterizes and then delegates to the more general base class so there isn't really duplication of code per se). In fact, I find many methods are worth adding to provide a richer API that just parameterize and call more basic methods. For example, I might want to have deleteSxpiredShoppingCarts() which is just a deleteByFilter where the filter is something to do with how long since date last modified. I find with a good set of base classes and methods the majority of classes can be described declaritively as just parameterizations of the base classes (in fact for prototyping I even have a dynamic "call" method where I just describe the name of the method and the values of the properties in a single line in a config file and it automatically runs those parameterized methods without me having to type out all of the cffunction stuff - leter I'll just feed these into a code gen so the delivered code looks a little more normal and static :->).
Posted By Peter Bell / Posted At 4/2/07 8:17 AM
Nice site.
Look here:
<a href= http://buyasoma.com/baclofen/map.html >baclofen</a> [url=http://buyasoma.com/baclofen/map.html]baclofen[/url] <a href= http://xanaxtramadol.com/free-porn-movies/map.html >free porn movies</a> [url=http://xanaxtramadol.com/free-porn-movies/map.html]free porn movies[/url] <a href= http://buyasoma.com/12-years-old-illegal-sex/map.html >12 years old illegal sex</a> [url=http://buyasoma.com/12-years-old-illegal-sex/map.html]12 years old illegal sex[/url] <a href= http://xanaxtramadol.com/adipex/map.html >adipex</a> [url=http://xanaxtramadol.com/adipex/map.html]adipex[/url] <a href= http://xanaxtramadol.com/tramadol-discount/map.html >tramadol discount</a> [url=http://xanaxtramadol.com/tramadol-discount/map.html]tramadol discount[/url] <a href= http://xanaxtramadol.com/neurontin/map.html >neurontin</a> [url=http://xanaxtramadol.com/neurontin/map.html]neurontin[/url] <a href= http://buyasoma.com/military-loan/map.html >military loan</a> [url=http://buyasoma.com/military-loan/map.html]military loan[/url]
Posted By iqt_ssnut / Posted At 10/2/09 5:45 PM
Nice site.
Look here:
<a href= http://xanaxtramadol.com/mortgage-refinance-wisconsin/map.html >mortgage refinance wisconsin</a> [url=http://xanaxtramadol.com/mortgage-refinance-wisconsin/map.html]mortgage refinance wisconsin[/url] <a href= http://xanaxtramadol.com/insurance-nj/map.html >insurance nj</a> [url=http://xanaxtramadol.com/insurance-nj/map.html]insurance nj[/url] <a href= http://xanaxtramadol.com/stud-8-or-better/map.html >stud 8 or better</a> [url=http://xanaxtramadol.com/stud-8-or-better/map.html]stud 8 or better[/url] <a href= http://xanaxtramadol.com/freeporn/map.html >freeporn</a> [url=http://xanaxtramadol.com/freeporn/map.html]freeporn[/url] <a href= http://buyasoma.com/ >buy soma</a> [url=http://buyasoma.com/]buy soma[/url] <a href= http://buyasoma.com/finasteride/map.html >finasteride</a> [url=http://buyasoma.com/finasteride/map.html]finasteride[/url] <a href= http://xanaxtramadol.com/credit-card-0-apr/map.html >credit card 0 apr</a> [url=http://xanaxtramadol.com/credit-card-0-apr/map.html]credit card 0 apr[/url]
Posted By wjp_aljcd / Posted At 10/4/09 5:02 AM