Browse DevX
Sign up for e-mail newsletters from DevX


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

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

Unit of Work

As you saw in Listing 1, the Service layer method adds information to a Unit of Work. If you compare this to Fowler’s description of Unit of Work [5], my usage of it is pretty different. Instead of gathering the information about, say, a changed property directly when it happens, I don’t gather any information until I’m preparing to contact the database. Anyway, the purpose is more or less the same, i.e. to gather information about what should be done against the database in one logical unit of work.

The main reason why I didn’t follow Unit of Work according to the way Fowler [5] discusses it is that I couldn’t come up with a good solution for how to move the Unit of Work instance over a possible network between Consumer and Service layer. Of course, it’s easily done explicitly as a parameter, but I wanted a more automatic solution. That made me decide that the solution we are about to discuss is a suitable one.

Let’s investigate my implementation of the Unit of Work a little closer. You can find my variant (or rather variants) in Figure 2.

Figure 2 The Unit or Work classes.

I think the purpose of BeginTransaction(), AddSprocCall(), AddParameter() and EndTransaction() in Figure 2 is pretty obvious. Execute() is used for the second stage when it’s time to execute all the commands whose information has been gathered when no resultset is expected (or at least not wanted) in return. ExecuteReturnDataReader() is used when you expect and want to deal with resultsets.

As you saw in Figure 2, there are currently two implementations of the MustInherit (abstract in C#) UnitOfWork class. The ScriptUnitOfWork is similar to what I wrote about in my book [7]. It creates a single SQL batch/script that is sent to the database in one go via one single ADO.NET IDbCommand. The CommandUnitOfWork will use one ADO.NET IDbCommand for each stored procedure call instead.

You might be wondering when to use each implementation. I’ll get back to that in a later article. For now, the current architecture is almost completely untuned and performance characteristics haven’t been sorted out. There are also other reasons for choosing one or the other, but it’s pretty much a matter of performance.

I think it’s very important to say a few words about the transaction handling when the Unit of Work is used. First of all, I had no explicit call to EndTransaction() in Listing 1. That is okay, because the Unit of Work will implicitly decide to end transactions that it has started. The second important thing is that I don’t say CommitTransaction() or RollbackTransaction(), but EndTransaction() (explicitly or implicitly). The reason for this is that the Unit of Work itself will decide which way to go. I can think of situations when you want to be in control of the transaction outcome, but in my case this is usually taken care of in the stored procedures.

Also note that BeginTransaction() isn’t starting a transaction. It’s just indicating that a transaction should be started at a certain place in the sequence of commands.

I could even skip explicitly calling BeginTransaction() and let the Unit of Work do that if there is more than one stored procedure call to execute. And if there is just one stored procedure call, but it still needs an explicit transaction, it’s the responsibility of the stored procedure itself. If I went this route, it would mean that I had to add a mechanism for dealing with less common situations. For example, if I don’t want a transaction to start before the first stored procedure call but only later. I will stay with the less automatic solution for now. Remember, I think it’s okay to place high requirements regarding transaction programming and such on the programmer of the Business and Data tiers.

There's something more I have to comment on. Namely, the Unit of Work isn’t trying to be smart about the transaction code as regards using the correct transaction isolation level and avoiding risks for deadlocks. That is completely up to the developer. We’ll discuss this at length in another article. You’ll also find a lot about it in my book [7].

Another thing to think about concerning Unit of Work is where to hold on to the instance. As you saw in Listing 1, I held the instance at the method scope. You could also hold on to it at the instance level (of the Service Layer object) if you find that appropriate for the specific use case. Note, however, that if you use Just In Time Activation (JITA) in Enterprise Services or Single-call in Remoting, this won’t work without paying extra attention and overhead.

A database access helper

One very important consequence that comes for free is that your Unit of Work will become a helper for database access. All of the code that works with ADO.NET will be encapsulated here (there is an exception, but more about this in a minute).

I don’t think you should underestimate the value of a helper for database access - I personally find them very beneficial. So even if you don’t think the Unit of Work is anything for you, you should think again before you skip using some sort of helper that encapsulates ADO.NET. As I said, ADO.NET is great, but I'm pretty sure that it’s not the final data access API from Microsoft. What's more you also don’t want to write similar code against ADO.NET over and over again, but instead generalize it as much as possible, or at least as much as is suitable. (That goes for everything, of course.)

OK, let’s get back to the scenario. We were looking at what the code for saving an order looked like in the Service layer. Now it’s time to investigate the code in the Persistence Access layer.

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