For a long time now I have been trying to think about the real design implications of aspects. The common examples seen are like C++ programs -- grafting objects onto a different paradigm in ways that certainly work and are helpful, but it completely lacks the elegance of Ruby. Design ideas we see now are akin to an elephant with flippers tied to its feet and a snorkel on the end of its trunk compared to a fish.
Recent blog entries, by authors I cannot look up from my browsing history as I am offline as I write this, have spurred some thoughts. Most of these authors have been in the .Net blog space and have been writing largely in response the SOA meme making the rounds there. One gentleman with an Indian sounding name who I will quote properly when I have internet access again (Ramkumar Kothandaraman, ed.), posted a summary of an interesting design that fully embraces the anemic domain model Martin Fowler recently railed against. Udi Dahan responded that he liked the idea, and agreed that "objects shouldn't be at the center of the design." Tack onto this discussions at fall conferences around removing business logic from the business objects -- Pragmatic Dave Thomas giving me the most powerful example of this and I am left wondering where do things belong?
The conceptual duality of objects bothered me for a while -- they are data, they are behavior. They are a way to use globals that makes sure you don't hit the classic problems created by globals. Fields in objects really are just globals -- but globals in a small scope. That said -- they work, and work well. The idea of telling something to do what it knows how to do is a strong idea. That ability to abstract (and leak, yes, yes, i know) is required as complexity in applications increases. I am a bear of very little brain, I like simple things.
There is the key idea I think -- objects are behavior encapsulators, not data encapsulators. They do encapsulate data, but again and again you run into the "evil getter" thing and see lots of weird looking code to get around having to expose the data part of an object. To go back to Ruby -- be default all data is private in an Ruby object, and you must go out of your way to make it accessible outside of the object. Ruby's embrace of closures makes the "tell it, don't ask it" ideal very easy to implement. This is great on a small scale, but what about the large scale?
To take a step further, objects come in a couple of very important varieties. There is the entity, and the utility object. An entity has identity -- when you say "This Customer" you always mean the same customer, even if every other property of that customer changes, the identity is the same. This is fine on a conceptual level but it is implemented identically at the implementation level so the focus gets lost. Lots of tools exist to make sure you keep that focus, Entity Beans being the most powerful. Working with entities then is so much of a pain in the arse that you can *never* forget what is entity and what is utility.
If anything occupies the conceptual center of the world for an application it is the entities. You can point to them, you can refer to them. You interact with them over the phone, or by shipping them, or by updating data about them. They are the things in the world. Everything else congeals around them.
What you do with them changes -- but also most importantly, what they do changes. Change that, how they do what they do changes, what they do rarely changes. A customer buys stuff from you -- it may be a service, it may be a Tonka truck. The workflow, the details of how it is done, etc all change, but the essential thing that they do does not change. It is almost a Platonic ideal.
So, if we leave the entity as the center of the world and build a domain model around what they can do (not how they do it) we have a core. To get specific, implement the enitity model but leave all the operations abstract . Model the relations, interactions, responsibilities, but don't care about how they do it yet. Just provide hooks to know when they do something. Make each entity an object and use whatever identity scheme you like. Make the operations abstract, or if this is inconvenient make them empty methods.
Now, implement the actual operations in aspects. Delegate operations to rules systems. Delegate operations to interceptor stacks that make sure everything is kosher. Keep this operation implementation clean and workable though, and make the objects do what they know how to do, don't accumulate data and do stuff with it.
Entities become much like event generators then. Customer.buy(Toys.nextAvailable("name = Tonka Truck; price < $50USD");
sets in motion the actual work without the entity model knowing a thing about it. Objects maintain behavior, but much of the behavior is externalized. The concerns about intention and implementation are seperated out. I don't know about the query type argument provided above, the utility of that is an exercise for the reader (iow, my brain hurts and I needed a specifier, whether the idea is good or not I don't know).
Classic concerns are then handled at various places. The top level is retrieval and direction of entities. Access to persistence gateways and direction of the entities is done from the external interfaces:
brian = Customers.find("name=Brian McCallister; url=http://blog.skife.org");
brian.buy(Toys.nextAvailable("name=Tonka Truck; price < $50USD");
Customers and Toys in the above snippet are service endpoints. They are not good service endpoints as they return handles to objects which do stuff, so maybe that needs some more thinking. The find
and buy
operations are not defined in their respective objects, but are intercepted and handled by rules systems, or whatever. They are idealized directives of what the unchanging operations entities are capable of doing.
I have no idea if this design would work well -- I suspect it is too verbose to catch on -- 90% of applications do not have a maintenance lifetime long enough to realize the benefits, but... it is a starting point for discussion at the least.
If I were to use it as the basis for a design right now I woul probably make the highest level client for the system Cocoon FlowScript and handle the UI from there. The FlowScript would interact with the enitities and services which provide access to the entities -- the operations would probably be handled by the interception library Chris Nokleberg and I are working on -- or maybe by Nanning or AspektWerkz, depending on how well they interact with the FlowScript execution contexts -- I suspect not well. FlowScript really lends itself to the high level entity manipulation to set the business rules in operation though. I don't think I would want to try to do this from an action-oriented client. Hmm, need to think about transaction demarcation in FlowScript. Do JTA transactions play nicely with continuations? I doubt it.