ore than a year has now passed since I wrote my book (.NET Enterprise Design with Visual Basic .NET and SQL Server 2000. The book discusses solutions to common design problems for large-scale applications, so several pages are spent discussing an architecture that can be used as a starting point for a new application. A year is a long time in the software industry, and it’s very long if you consider that the .NET Framework was in beta when my book was published.
I’m not saying that the architecture in the book (let’s call it the old one from now on) is bad. Not at all. But there are a few things that I’m thinking about changing for future applications. To summarize the changes, I’d like to use a domain model that is more object-oriented than in the old architecture (and thereby increase, for example, maintainability). What I’m trying to create is a highly effective hybrid between object-orientation and a relational database.
The series of articles that I’m starting now invites you to participate in my exploration. Perhaps I might end up with the conclusion that using datasets (typically typed) is the way to go after all, or I will find that my new architecture is more suitable for many situations. Kind of frightening, but also stimulating not to know myself where we will end up.
Sidebar what about db-guy?
You might be wondering what I mean by db-guy in the title? Well, I have a habit of being in the middle all the time. I’d like to give you just a few examples. When I worked part-time as a teacher at the university, I was too much of an entrepreneur, while at the same time in my business perhaps a bit too much of an academic. When I’m with a crowd of SQL Server-guys, I’m more of a .NET-guy. So, when I’m writing about .NET and object-orientation, I’m probably pretty much of a db-guy. Perhaps my next series of articles will be called A db-design by an OO-guy.
Why am I telling you all this? I strongly believe that my being a db-guy is an advantage as I write this article, so that I don’t lose the focus on the database while building a pure object-oriented domain model.
Inspiration for a New Architecture
Before we start investigating the changes I’m considering, I’d like to tell you what prompted me to start this work. First of all, for the last, say, eight years I have been testing several architectures and I’m not done yet, of course. When it’s time for a new project (with at least some similarities to the old project), I think it’s wise to start with the current favorite architecture, but also to think about what could be improved. That’s definitely one reason why I started this work, but there are other reasons too.
A few months ago I read the manuscript of Martin Fowler’s forthcoming book, Patterns of Enterprise Application Architecture. Fowler’s book discusses enterprise patterns and I think it will be a must-read almost on a par with Gamma et al’s Design Patterns. At last we will have a common vocabulary for enterprise patterns too. Fowler’s book also prompted me to try the domain model pattern again and not just work with the Table Module pattern, which is the name Fowler uses for the pattern of using datasets. (The careful reader will notice my usage of the word again above. I have tried a pure object-oriented domain model several times in the past when working with VB6 and COM+ and time after time I found it to be problematic because of performance reasons.)
I also like not to be too dependent on ADO.NET in my bulk code and as a consequence I ensure that I use a data access helper to encapsulate most of the ADO.NET operations. The new architecture goes a step further by also letting go of the datasets in the bulk code. The only layer that will know about ADO.NET at all will be the persistence layer and even that one will see very little from ADO.NET because of a data access helper. I also like not to put more knowledge about the database schema than is absolutely necessary in my middle tier, which is yet one more reason for the new architecture.
Sidebar Is there a single answer regarding architectures?
No, it all depends. What I mean is that you need different architectures for different applications. I think the new architecture which I’m starting to discuss in this article is a little more suited to more advanced applications than the old one. I also think the new architecture gives more maintainable applications, but there are drawbacks too, of course. For example the old architecture was a little bit more oriented to RAD (Rapid Application Development). We will return to this subject in more depth later .
It’s time to tell you a little about the new architecture proposal, but first I think I have to do one more thing. For those that haven’t read the book and for those that have read it, but have forgotten about the old architecture, let’s take a quick look.
Overview of My Old Architecture
For those of you familiar with Windows DNA, you will find the old architecture to be pretty similar to it. Figure 1 shows the diagram.
Figure 1 – Tiers and layers in the old architecture
The different layers have different responsibilities and to get an idea of what each layer does, I’d like to tell you a bit about them.
- The consumer layer: The consumer layer typically deals with presentation issues, such as painting window forms or Web forms. For batch programs, the consumer layer is radically different, of course.
- The consumer helper layer: This layer hides all the complexity of the application layer and it is adapted to the specific consumer layer implementation. That is, if you use Web forms for the consumer layer, the consumer helper layer will provide the functionality adapted to Web forms. This is also the place for generic consumer code, hiding factories, consumer-side caching and so on.
- The application layer: The application layer has one class for each use case in the application and each method is usually very bulky. That is, each method does a lot of work at the server-side in just one call to make transactions as short as possible and to reduce roundtrips. This is also the layer to use enterprise services if you need distributed transactions, for example.
- The domain layer: This layer is responsible for business rules best implemented in the business tier and not in the data tier. This layer focuses on concepts or entities rather than use cases. Note that this layer is often skipped when data is only fetched.
- The persistence layer: The persistence layer encapsulates the data tier, and the more knowledge about the database that is only kept here (and not in the domain layer for example) the better.
- The public stored procedures layer: The only way to access the database is through the public stored procedures. The public stored procedures are somewhat use case centric.
- The private stored procedures layer: And finally, the private stored procedures are again entity oriented to generalize code and also to be a good placeholder for business rules that should be implemented in the data tier but can’t be declared such as constraints.
A Few More Comments on the Old Architecture
Worth mentioning is that my old architecture is more database centric than Windows DNA since I think that all database access should pass by stored procedures. I also tend to let the classes in the domain and the persistence layer only have shared (static) methods since they don’t need any instance data. What is not different from Windows DNA is that I use datasets for holding data and for letting data travel between tiers, and then the other classes are operating on the data in the datasets. (OK, Windows DNA was before .NET and used ADO Recordsets instead.)
That was a really quick glimpse at the old architecture. What I really wanted to point out is that datasets are used for holding data while classes in different layers are used for acting on the data in the datasets.
Overview of the New Architecture
Is this like starting all over again and forgetting everything in my book? Definitely not. Everything in the book is still valid but I’m just building on it. For instance, the BatchCommand pattern (or Unit of Work as Fowler calls it) is very important, but let’s not move too fast right now. Instead, I’d like to give an overview of the new architecture, shown in Figure 2.
Figure 2 – Tiers and layers in the new architecture
I’m aware that it might be a mistake to allow the Consumer tier to see the Domain model after serialization, instead of just receiving DTOs (Data Transfer Objects, but for the moment I’d like to try this out as a starting point, especially for less advanced applications. For advanced applications it’s probably better to save this refactoring and go for DTOs directly. So please hold back the flaming mails about this
As you see, on the surface the new architecture is very similar to the old one. The only real difference is that the Domain layer (or rather model) is known by Consumer layer, Consumer helper layer, Application layer and Persistence layer. Instead of using system provided containers (such as datasets) for holding the data, I am now using custom domain classes to do this. To be honest, the old architecture would also have an assembly known by all the other layers if you are using typed datasets, so the old architecture with typed datasets would look more like as shown in Figure 3.
Figure 3 – Tiers and layers (with strongly typed datasets) in the old architecture
First I thought that I would change the name of the Application layer to Service layer, but I haven’t decided on that yet. OK, Fowler uses the name Service layer, but on the other hand Microsoft is currently talking about Process layer, so I think I’ll stay with Application layer for the time being. I have also learned that service layer means completely different things to different developers.
So, the purposes of all the layers are the same as they are in the old architecture. It’s just that the Domain layer (or model) is slightly changed and takes on a much greater responsibility. Its classes become the main part of the programming model, which are greatly interacted with in both the Consumer tier and the Business tier.
That was a quick look at the new architecture proposal. I think we should already take a look at some details here in the introduction of the article series to see the old and the new architecture side by side.
One of the best things about writing a book is that I have received a lot of interesting emails containing feedback. Some have asked what I think about solving relationships in the middle tier. That is, how to build the navigation from a customer to all his orders and from a specific order to all the items of that order and so on. My immediate answer was that typed datasets help with just that. That’s true of course, but you normally navigate via foreign keys then in the middle tier too. Unfortunately, knowledge about the database schema is spread all over the place. Another problem is that you will often have two separate typed datasets between which you want to navigate. I mean, you don’t have one single model that is completely navigable, but you have several small models, which will probably overlap each other.
Let’s take quick example on how to navigate in the domain models. In the old architecture I would normally use datasets. For the example here I will discuss customers and orders which I’m sure all of you can identify with. So, in Figure 4 there is a typed dataset with two datatables called Customers and Orders. I have set up a relationship (which is called CustomersOrders) via the foreign key in the Orders datatable (which is called Customer_Id).
Figure 4 – A dataset with two datatables
(Figure 4 shows one way of building the dataset. There are others of course, but let’s stay with this for now.)
Assume that the consumer has received the typed dataset CustomersAndOrdersDS in the variable called theCustomersAndOrders. The navigation code then looks like in Listing 1.
Dim aCustomer As CustomersAndOrdersDS.CustomersRowDim anOrder As CustomersAndOrdersDS.OrdersRowFor Each aCustomer In theCustomersAndOrders.Customers For Each anOrder In aCustomer.GetChildRows("CustomersOrders") Console.WriteLine(anOrder.OrderNo) NextNext
Listing 1 – Navigation in the old architecture The New Architecture
In the new architecture, I have built a small framework myself for my domain classes and the persistence layer. Instead of using datasets, I inherit from my custom base classes EntityCollectionBase for collections and EntityBase for classes describing distinct entities. In Figure 5, you can see that Customer and Order inherits from EntityBase and CustomerCollection and OrderCollection inherits from EntityCollectionBase. The base classes help by providing general functionality and policies to the subclasses.
Figure 5 – Overview of part of the framework for the new architecture
The base classes implement a couple of custom interfaces too and I’m only showing public members above. That is so as not to complicate the picture more than necessary for the moment.
Before we continue, let me stress that using EntityBase and EntityCollectionBase in themselves won’t make anybody happier. It’s when we start adding custom code to the domain model that it gets exciting.
Let’s take a look at a code snippet for how to navigate customers and their orders in the new architecture. There is an example in Listing 2. This time the consumer has received a CustomerCollection and the variable is called aCustomerCollection.
Dim aCustomer As CustomerDim anOrder As OrderFor Each aCustomer In aCustomerCollection For Each anOrder In aCustomer.Orders Console.WriteLine(anOrder.OrderNo) NextNext
Listing 2 – Navigation code in the new architecture
As you saw, the two architectures are very simple when it comes to navigation in a part of the domain model. Where can we find some differences between the architectures? Well, let’s start by comparing the interface of a class in the Application layer. There is a typical class from the old architecture in Figure 6.
Figure 6 – A class from the Application layer from the old architecture
The same class in the new architecture could look like as shown in Figure 7.
Figure 7 – A class from the Application layer from the new architecture
As you saw in Figure 6, the old architecture mostly uses primitive types (and datasets) for the parameters. The new architecture uses custom classes. Note especially the usage of the Id-parameter in the old architecture. Once again this means that the structure of the database is used everywhere.
One problem with the old architecture that has been bugging me for years (since I’ve had the same problem with earlier tries too) is that the business rules have been set-based and evaluated kind of late in the scenarios. That is, it has been possible to add values to columns and rows that later on proved to be wrong. The validation was on all rows at once (if the scenario was one where the end-user could add several orders in a row, for example). It would be nice to be able to express and check simple rules on the row-level and column-level (or object-level and property-level if you so wish). With datasets you can set up some constraints, but they are not flexible enough for the generic case in my opinion.
One related problem is that in my old architecture, the rules were separated from the data. One solution for this is to use the pattern that Reynolds and Watson present in their book .NET Enterprise Development in VB.NET, which is to inherit from a dataset and then you can put the validation code in the sub-class.
So, let’s compare the checking of rules. Checking a simple rule in the old architecture could look as shown in Listing 3.
For Each aCustomer In theCustomersAndOrders.Customers If aCustomer.Name = "Volvo" Then Throw New ApplicationException _ ("The customer may not be named Volvo!") End IfNext
Listing 3 – Checking a rule in the old architecture
And let’s do the same thing in the new architecture, where the data is contained together with the rules, in Listing 4.
Public Property Name() As String Get Return _name End Get Set(ByVal Value As String) If Value = "Volvo" Then Throw New ApplicationException _ ("The customer may not be named Volvo!") Else _name = Value End If End SetEnd Property
Listing 4 – Checking a rule in the new architecture
As you saw above, it’s very easy to put rules at the object-level instead of at the set-level (I mean on several rows at a time) which is the way to do it in the old architecture. (Please notice that the code in Listing 4 well, that goes for Listing 3 too represents a na
Advantages and disadvantages
I’m sure you can feel my optimism for this new architecture, but without sounding like a salesman or as if I am discussing a cure-all solution, let’s concentrate for a while on the negative aspects. I have already mentioned a few drawbacks with the new architecture, now let’s discuss them and a few more a little more closely.
Less efficient when it comes to RAD (Rapid Application Development)
Visual Studio .NET provides quite a lot of help with adding RAD features if you use the Table Module pattern. If you go for something similar to my new architecture, you will have to create some tools of your own to help deal with some of the repetitive tasks.
You will find that in the short run at least, the new architecture will require you to write more code than if you use typed datasets. This goes partly along with the preceding paragraph.
If you go to MSDN for example, you will find plenty of examples of how to use datasets inside out. The same goes for books too. If you try to find .NET samples for how to write your own object-oriented domain model in .NET, it’s an opposite situation.
As I said, it might be a big disadvantage to have so much coupling to the domain model. It’s perhaps better to only expose DTOs to the left of the application layer, but I think that it’s good for some systems without DTOs, and bad for others.
Connected to the coupling problem comes the headache of added versioning problems. If the domain model changes frequently, the consumers are affected too.
You might ask yourself after a while whether you should solve customer problems or write infrastructure code. MS provides you with the infrastructure of datasets and it’s done. If you go for writing a pure domain model, you will have to deal with a lot of complexity. You might end up having a lot of fun, but not solve any customer problems at all.
On the other hand, I think it’s often nice to have full control on your own if the task is manageable, especially for situations when the infrastructure is otherwise changed each and every year. I guess some of you are thinking: How na