Journaled Domain Models via the Command Pattern, or, It's All Just CRUD pt. II
Posted by Jeremy Voorhis Tue, 27 Jun 2006 22:00:00 GMT
I just read Jason Watkins’ followup to Jim Greer’s post about CRUD and I think Jason’s claim that simplicity is lost is dubious. State-changing methods like Ticket.close! give us a simple interface to send a command to a domain object, but they may be hiding any amount of complexity. When we introduce the TicketClosure, we let our graph of model instances tell us directly based solely on their existence. That is what enables domain operations to be reduced to simple CRUD, where each of the four primary verbs are universally understood.
Also, if we step back one level, we are reminded that a verb is also a noun on paper – we talk about performing commands on our primary domain models, but operations, actions, etc. nouns. What we are talking about is journaling state changes on entities within our domain model. If you are unfamiliar with the command pattern, I encourage you to read the Wikipedia entry about the command pattern for an excellent treatment of this design pattern.
Using Jason’s Ticket example, let’s imagine that TicketClosure inherits from TicketCommand. TicketAssignment and TicketPriorityChange could also inherit from TicketCommand, which would expose simple operations such as reassigning a ticket to another developer, etc. The ticket’s state is now a composition of the commands it has received. These commands all expose their own simple CRUD operations, and our Ticket model now supports undo operations by manipulating its journal of commands with simple CRUD.
What other uses can you envision for the command pattern as an integral part of a domain? Any criticisms?

Jeremy,
I think you’ve hit on something useful here. What I would like to see to radically simplify the API for the pattern, is a way to back acts_as_state_machine with the new, CRU Dier domain model. Rather than storing the states as fields on the object which has them, the object has_many states – and the transition between states can be recorded and journaled by recording each new state, without actually deleting the record of the old states.
I wonder if having models/tables for everything really is simpler than using something like acts_as_versioned in concert with acts_as_state_machine?
This takes me back to a learning experiment where I attempted to create my own acts_as_versioned-like system which stored diffs against documents via diff-lcs and composited them on demand. Performance tradeoffs occur here. The field whose deltas were journaled was like a rolling snowball whenever I wanted to recomposite the document. Of course, caching papers over this in most cases.
Returning to the command pattern, one thing I like about it is that each command object should have an
executemethod which applies its operation to the receiving object. Adding this sort of functionality would allow us to perform operations on models in-memory instead of just on database attributes. Specialized command objects are defined by their own classes which can implement any sort of behavior we like. Piecing together a_a_v and a_a_s_m might allow us to do similar things with state transitions, but it still doesn’t give us the same sort of power as a journal of commands (I think).Would you care to post an example? :)
Chris,
I think the interesting thing here is that a lot of these kinds of discussions are around your domain logic. The idea of having a journaled store of your data is one that can be best explained by your domain. I’d thoroughly recommend Evans’ book for this kind of thing.
Essentially though he talks about Entities (things where their identity is important) and aggregates (groupings of entities and value objects that together have rules important for the functioning of that aggregate). Your code can only acccess items within an aggregate via the root entity.
This kind of logic (over whether you can move from one state to another) should (in my preference) be somewhere within the object itself, but perhaps be organised and initiated by the thing saving the item (in Evans’ DDD nomenclature, the repository).
In short, if there’s a reason you need to ‘version’ things and keep track of changes, it’s because it has significance in your domain. In which case, it probably goes beyond the scope of Rails’ funky stuff and probably deserves consideration in it’s own right.
This in turn may answer your final question. Whichever is simpler is perhaps not the most important question in the long run – which is more meaningful in your domain, and where can you exploit your domain language in your code to make it as meaningful as possible for future developers and for using the code itself as a communication tool.
PS: Loving all this recent DDD stuff people are posting :)
This has me totally distracted from my work! Oh I can’t resist…
The current project I’m working on, one of the core domain models has a complex state that can transition in and out of all sorts of things similar to the Ticket example (I’m currently using instance variables to track the state, but they’re wrapped up in nice processed? and rejected? methods, but the implementation is ugly and confusing, plus the finder methods are getting out of hand because I’m having to check 4 fields for is null or is not null and etc!).
This command pattern fits well for my problem. It looks like models that have a kind of ‘workflow’ to it would be suited to this.
I’m still grappling with how this would be implemented though. If you were to play back the commands after you’ve fetched an object from the database, how would you say efficiently fetch all the ‘closed’ tickets? I’d guess you’d have to put an attribute on the Ticket (i.e. select * from tickets where closed) to specify the current state for performance reasons… but you wouldn’t set these attributes directly, your command objects would do this instead? Or does an object have a single absolute state (i.e. where state = ‘closed’), or could an object have more than one type of state (i.e. parellel journals).
Oh the humanity!
I tend to think you may want to cache the result of certain command objects on the ticket itself. This could be done on a callback to the command object, which would belong to the Ticket object. That seems to me to be the easiest way to find all tickets that are closed, etc.
Any other suggestions for getting around ticket rebuilding?