It's possible (even probable) that I'm just not fully grokking the concept of a "unit of work." Basically, I see it as sort of a broad transaction used in an object-oriented environment. Start the unit of work, interact with the objects, commit or roll back. But how does this break down to the actual transactions on the data stores behind those objects?
In a system with a single DB and an ORM (such as NHibernate) it's easy. The transaction can be maintained through the ORM. But what about a system where the custom domain models are obscuring many disparate data sources? And not all of those data sources are relational databases? (There's a lot done on the file system around here.)
Right now I'm stuck on the idea that "you simply can't maintain a transaction across a SQL2005 DB, a SQL2000 DB, a DB2 DB, and the file system all in the same 'atomic' business operation." So for now it's the responsibility of the developers on the team (who generally work independently of each other) to maintain transactions manually in the code. Each DB can have proper transactions on it, but the business operation as a whole is manually checked and balanced every significant step of the way.
However, with increasing complexity in the domain and standard developer turnover, this approach will become increasingly difficult and error-prone over time.
Does anybody have any advice or examples of how a domain like this might best be addressed, or how it has been addressed before? The actual "domain" in this case is still very much in its infancy, evolving as a prototype to one day expand and consume/replace a large ecosystem of disparate legacy applications. So there's plenty of room for re-designing and re-factoring.
For reference, a 10,000-foot view of the design I'm currently aiming for is: A large collection of small as-dumb-as-possible client applications calling a central message-based service. The service is the entryway into the "domain core" and can be thought of as one big MVC-style application. Requests are made to the service (much like "actions") which are picked up by handlers (much like "controllers"). Anything procedural goes there. They interact with the models, which contain all the business rules. The models publish events which listeners ("services"? this part is still cloudy in the design and subject to improvement) pick up and handle by interacting with repositories (database x, database y, file system, email, any external resource). All merrily dependency-injected accordingly.
Sorry for all the verbosity :) But if anybody has any advice, I'd love to hear it. Even (especially) if that advice is "your design is bad, try this instead..." Thanks!
Unit of Work is the concept related to the effective implementation of the repository pattern. non-generic repository pattern, generic repository pattern. Unit of Work is referred to as a single transaction that involves multiple operations of insert/update/delete and so on.
You can create three types of data connections to an external data source: query data only, submit data only, or both query and submit data.
If used in combination with calculated columns, multiple data sources can minimize or eliminate the need to create database table joins in an external data access tool. Using multiple data sources also enables measure allocation. For example, suppose your product, customer, and order data is stored in a set of tables.
I previously worked on a system that could accomplish this, and it is fairly straightforward. Since you project is in its early stages, perhaps this could be useful information for you. Unfortunately, I no longer have access to the code, but am still comfortable in describing how it worked.
What I had done was built my repositories using a generic repository pattern implementation. The base repository type would always be references by the services and UoW. For sake of discussion, we will call it BaseRepository. "T" would be restricted to IEntity implementations, which denoted a Domain Object. From the BaseRepository, I had created another set of base classes for compositing, such as SqlBaseRepository, XmlBaseRepository, etc.
The UoW only cares that something is of the type BaseRepository, which is where the core functionality would exist. Basic CUD (of CRUD) would be represented, providing the equivalents for Creates, Updates, and Deletes. What each of these would do would be to create a delegate and place it in a queue inside the UoW, also passing along information about what type of transaction it was going to be, and the appropriate data required to complete it. The UoW would start maintaining a list of what repositories were going to need to be involved in the transaction, but still did not care what type it was. Effictively, queueing up here is like enlisting in a transaction.
The BaseRepository defined an abstract method called something like .ApplyChange(). Once .Commit() was called on the UoW, it would create a TransactionScope() and start calling the delagates in the list, passing back the information to .ApplyChange(). The actual implementation of .ApplyChange() exists in the specific repository base, i.e. the SqlRepositoryBase, etc. and could be overridden by the implementation, as well.
Where it got tricky, for me at least, was rolling back. I only dealt with a single database, but sometimes had file-based changes that were made. I added a .RevertChange() method and started tracking the original state and modified states so that I could basically apply a reverse-delta to get back to where I was on the file stack.
I wish that I could be more specific on the implementation, but it has been over a year since I have seen the code now. I can tell you that the basis for the original code was borne from the book, .NET Domain-Driven Design with C#: Problem - Design - Solution, by Tim McCarthy. A large amount of my repository implementation was based on his examples, with a large majority of my customization coming in on the UoWs and their implementation.
I hope that helps, somewhat! :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With