Browse DevX
Sign up for e-mail newsletters from DevX


A Pure Object-oriented Domain Model by a DB Guy, Part 5 : Page 8

This is the fifth part in a series of articles by Jimmy Nilsson on a new architecture for enterprise applications in .NET. The new architecture is purely object-oriented with maintainability as the number one goal, while still focusing on roundtrips and the data access code to get good performance. In this article, Jimmy will discuss the new architecture from a persistence access layer perspective.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Why Not Persistence Access?
As you will have seen by now, and as I said earlier on, the Domain Model [5] is unaware of persistence access. Instead, the Service Layer [5] uses Data Mappers [5] for instantiating the Domain Model or for moving the data from the Domain Model to the database. One early decision I took with this new architecture was just this, that is, to move the persistence access code out from the Domain Model itself. I still think it was a good idea, but the main drawback was that I had to add a way to reach some of the private data of the objects, as you saw in Listing 8.

As you see, I thought I was getting a couple of advantages also. Firstly, with this solution I can instantiate a deep object model with a single roundtrip to the database. For example, I can instantiate a customer, the orders of that customer and the items for each order with just a single stored procedure call (that in turn executes three separate SELECT statements) and a single IDataReader if I think it's a good idea in certain specific situations. (For most situations Lazy Load [5] is used here.) There are solutions to achieve the same thing when having persistence access inside of the domain objects, but you have to use pretty twisted solutions for level three and beyond, which I dont think is such a good idea.

The purpose of the Lazy Load pattern [5] is to delay loading objects in an object graph that you might not need at all or only need later on.

You might wonder how I achieve Lazy Load when the domain model isn't supposed to know about persistence. Thats a very good point. The current solution I use is to let some of the Service Layer classes implement the IExpander [2] interface. Then the Domain Model instance will have an IExpander instance that helps with the lazy loading. This solution is not only used for expanding a collapsed object, but also for lazy loading of sub-collections, for example.

I also believe that portability and testability will increase (I will discuss testability much more next time), and in a way this solution follows the Single Responsibility Principle (SRP) [8].

Multiple Statements
So far, we have only discussed very simple cases when all the stored procedure calls were added to the Unit of Work from a single Data Mapper method. Its just as simple to call a couple more Data Mapper methods just to add to the very same Unit of Work instance. No real difference there. The only thing to think about is that you might have to keep the Unit of Work instance at the instance level of the Service layer object, as I discussed earlier. And of course, you have to take care with resultsets so your handler is prepared for what is coming.

What is trickier is if you find that you need several Unit of Works, but have to work with one single transaction. First of all, this is something I think you should try to avoid if possible, but there will probably be situations when you need to have this option. For instance, you might first need to fetch a value from the database, and depending upon what the value was (and perhaps some other logic), you should call one or the other stored procedure afterwards. If/when you get into this situation, you can then easily solve it by adding an option to somehow read/write the connection and the transaction objects of the Unit of Work. Don't then forget to take care when implicitly ending transactions. You should probably skip implicit ending of transactions in this case.

Enterprise Services transactions are an alternative, but unfortunately declarative transactions currently also mean distributed transactions. In my opinion distributed transactions should be used when distributed transactions are needed, and only then.

If you need to be able to use several Unit of Work instances within the same Service Layer method for example, but not within a single transaction, this is also simply solved. Just do it, so to speak. No extra complexity is added, and as long as a transaction doesnt have to span several Unit of Work instances, you dont have to be as careful at all. Just make sure that it is correct that you dont execute both of the Unit of Work instances within one single transaction. Remember, the number one goal of transactions is correctness, and nothing else.

Is the Persistence Access only for Databases?
No, not at all. Some data can be found in files, of course, and some via some Web Services, for example. The Persistence Access can and should hide most of the details, but we have to think more about how to solve the details. Currently, I think that we should add more Unit of Work implementations as necessary.

Do you remember when I talked about letting one database transaction span several Unit of Work-instances? Perhaps instead of chaining several Unit of Work-instances, we should use the Command [9] pattern here, possibly in a way combined with the Strategy pattern [9]. I havent worked through this yet, but right now it feels that it will fit nicely into the picture.

The purpose of the Command [9] pattern is to make it possible to bunch together several commands and execute them one after the other. Its pretty common that each command knows how to undo itself. The Strategy [9] pattern is used when you want to easily switch between different algorithms for a certain task.

Worth mentioning is that the Command pattern comes in as a handy solution for creating a solution for compensating to simulate transactions for files and Web Services. We can use this if we dont want to wait for standards and implementations of those standards for Web Services transactions at least.

The Compensating Resource Manager (CRM) of Enterprise Services is another solution to this problem. Its very appropriate here so I will consider that one too when I investigate this further.

My Blog

I will use my blog [10] for discussing this article series, among other things. Make sure you direct your RSS reader to the blog, if you want to follow the development of the architecture a bit closer than by just reading the articles.


[1] A pure object-oriented domain model by a db-guy. Part 1: Introduction

[2] A pure object-oriented domain model by a db-guy. Part 2: One base class


[3] A pure object-oriented domain model by a db-guy. Part 3: The consumer perspective


[4] A pure object-oriented domain model by a db-guy. Part 4: The Application layer perspective


[5] Martin Fowler; Patterns of Enterprise Application Architecture; Addison-Wesley 2002; ISBN 0‑321‑12742‑0

[6] John Vlissides; Pattern Hatching; Addison-Wesley 1998; ISBN 0-201-43293-5

[7] Jimmy Nilsson; .NET Enterprise Design with Visual Basic .NET and SQL Server 2000; Sams 2001; ISBN 0-672-32233-1

[8] Robert C. Martin; Agile Software Development. Principles, Patterns and Practices; Prentice Hall 2002; ISBN 0-13-597444-5

[9] Erich Gamma et al; Design Patterns; Addison-Wesley 1994; ISBN 0-201-63361-2

[10] Jimmy Nilssons weblog; http://www.jnsk.se/weblog/

[11] http://www.c2.com/cgi/wiki?MockObject


Jimmy Nilsson is a developer with more than 14 years of experience and also the author of ".NET Enterprise Design with Visual Basic .NET and SQL Server 2000." Reach Jimmy at www.jnsk.se and Jimmy.Nilsson@jnsk.se.
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date