This made Brian Carr and myself sit way back in our chairs and go "Hmmm... How can we solution this problem given our architecture?". The piece of software we're developing is built around SOA - our front end user interface is strictly ExtJS that communicates over ReSTful web services to our platform. Not a lick of CF on the front end. Shortly after those requirement gathering meetings we buckled down to analyze the problem at hand.
Here were some of our main considerations:
- ExtJS grids come with their own dirty/clean mechanism but that only existed for grids. Our client's requirement existed for any form control, not just grids. That option was out of the picture, especially in a SOA.
- Even if that functionality existed for all form controls, how would the platform handle saving these data points simplistically with minimal overhead and a clean API? In a traditional application that doesn't use data mappers or ORM, you'd usually have a post page that inserted/saved everything about that entity - for instance a User. You'd have a web form that when posted, would update all 20 fields even though all you needed to update was one email address. In an enterprise level environment this is a HUGE waste both in sending data over the pipe as well as causing unnecessary database overhead.
- We already had our own ActiveRecord implementation in place so handling crud operations from our domain model was extremely simple.
- We already implemented the Observer Pattern that I've detail here, to handle saving composites among other things.
- At a systems level, we wanted to make small, light and quick web service requests to our platform whenever a form field was updated.
- It must be transactional.
We knew that when it came to persisting an object singularly, it was solid. The api was such that these objects could save itself, load itself etc. We also knew that via our Observer Pattern implementation that when an object saved itself and there were any composite relationships attached to that instance, any of those composites would also save itself. So the only two problems where this:
- How do we logically group disparate objects in memory that are part of a unit of work - like a front end data grid or a group of data separated by tabs - to ensure that the underlying objects that encapsulated these data points can be saved, deleted or rollbacked (removed from memory) when needed. All while using our ActiveRecord and Observer patterns?
- ALL of this must be managed across multiple independent http requests to the platform.
As usual Brian and I banged our heads against the wall trying to come up with a solid solution. Then it happened... enter the Unit of Work design pattern by Martin Fowler.
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
Let's jump right in. I won't go over the exact implementation that we used in our software, but I'll provide a quick overview of this design pattern and how you might get started with it.
As always, here is the class diagram first.
So you'll see here that we've got:
- An IPersistent interface that has save and delete that returns a string for example purposes.
- A UnitOfWork (UoW) object.
- that has 3 fields to store the new, delete and changed stack. Which all should take an array of objects, preferably strongly typed if your language can support it.
- methods for adding to each stack.
- a method for committing and deleting the UoW.
- a get() method that will pull out an object from the UoW registry.
The idea is simple really. The UoW acts as a container/wrapper for disparate objects that will need to take part in a single logical transaction.
Here is the code for the UnitOfWork object:
Pretty straight forward here. Class members are created to hold objects (stack) and methods are there to throw objects onto a stack. Notice how the entirety of commit() is transactional - it is all or nothing. We iterate over all the stacks and call the appropriate crud method. Also the get() method will pull out the object from the registry based on hashcode. You can do this however based on how you uniquely identify objects in your system.
Here's the scratch code:
Which produces:
In this test we:
- created three users
- displayed the unique hashcodes for each user
- assign user1 and user2 to to the UoW new stack.
- assign user3 to the UoW delete stack.
- then call commit() on the UoW.
Cool? On uow.commit() we've executed the appropriate database method to handle persistence and printed the save/delete messages to the screen. Of course in a real implementation this would be whatever persistence mechanism you have in place - DAO, ActiveRecord, data mappers, whatever.
Here are a couple key things to consider that this post didn't cover:
- You will most likely need a singleton object, usually called the UnitOfWorkManager, that is responsible for managing your UnitOfWork objects. This plays into the feature of supporting UoW across multiple HTTP requests.
- You will need a method on the UoW object that will remove objects from certain stacks altogether. This is for situations that perhaps the user clicks a "new user" object and a new Use robjects get registered. However he/she decides that a new user is no longer needed so they subsequently remove it but there are still other data changes that need to be persisted for that UoW. Don't get this confused for rollback() or registerDeleted().
This is one of those design patterns that you can find yourself getting really deep and start customizing like crazy. Be careful for that, make sure to keep it simple...
Hopefully you can use this as your jumping off point... Happy UnitOfWorking!
-Micky
References
No comments:
Post a Comment