devxlogo

A Pure Object-oriented Domain Model by a DB Guy, Part 2

A Pure Object-oriented Domain Model by a DB Guy, Part 2

n the previous article (Part 1, see left column) I showed the diagram illustrated in Figure 1. However, I didn’t say very much about it, so now is a good time to do so.

Figure 1 A static view of the domain model, old version

As you can see in Figure 1, I had defined two abstract (MustInherit) base classes in the Jnsk.Domain assembly. The base classes were called EntityCollectionBase and EntityBase. The idea behind them was that all my domain classes would inherit from one or the other. Classes that describe a single entity, such as a single customer, would inherit from EntityBase. Classes that describe a collection of rows would inherit from EntityCollectionBase. This way the domain classes gathered under a common feature set, so a lot of functionality is reused and policies are forced. The base classes were implementations of the Layer Supertype[2] pattern.

One of the changes
As I said, it’s been a long time since part one was written. I have not spent all of that time thinking about this architecture (or resting), but some thoughts did occur to me. One of them was to drop EntityCollectionBase. This wasn’t an easy decision, and I’m definitely not sure that it will be this way forever. Anyway, I found myself in one of those situations where I couldn’t see which way to go. After a while I found out that I was stuck. In order to get going, I had to make a decision.

I decided to drop EntityCollectionBase for the following reasons:

+        Complexity added without any real benefits
At least no real benefits for the moment. I found out that I was just adding dummy code for creating the typesafeness of the collections. Since I’m (sometimes) a bit XP[3]-influenced, nowadays I don’t like to add stuff on beforehand that I only might need later. I also think that simple is beautiful is a very important principle for design.

In a way, the DataTable of ADO.NET is quite nice as far as this goes as it describes both a row and rows. (However, as I said in the previous article, there are other problems with it.)

+        ReadOnly-problem
At first I couldn’t find a really good solution for how to expose a read only collection from when a customer has a collection of orders. The main reason for the problem was that in my early tests I inherited from CollectionBase. There is a ReadOnlyCollectionBase to inherit from instead, but I didn’t want any such restrictions internally in the Customer class, only externally. Using ArrayList, for example, gives a simple solution to the problem. Just use ArrayList.ReadOnly(theListToExpose) and you’re done.

On the other hand, I could skip CollectionBase and instead implement the necessary interfaces myself. That was actually my plan. I mean, just to start with CollectionBase, but change later on. Have a look at .NET CollectionGen[4] by Chris Sells et al.

+        Increased serialization problems
I started to suspect that I couldn’t manage to stay with plain serialization for moving domain model objects between remoting end points, and would need to write custom serialization code, even for pretty simple object models. By simplifying the solution and not relying on events (or similar) constructs, I have at least simplified the serialization problem.

+        I started to think that I needed a new toplevel class
EntityBase and EntityCollectionBase had a lot in common, so I started to think that they should both inherit from a new superclass. On the other hand, at the same time that felt a bit unnatural, and also like over-design. I decided this was a sign that I needed another solution.

+        Sometimes I need an ArrayList, sometimes a Hashtable, and so on
So, in reality I would probably need not just one EntityCollectionBase, but rather several different flavors. And then there was the problem of whether I should create one OrderCollection that inherits from EntityCollectionBase and one that inherits from another custom superclass and so on. One base class dropped, one left
So, there’s only one base class left to discuss, namely the EntityBase. Let’s take a closer look at the surviving base class.

There is a lot of general code in the base class, so when you write the specific subclasses you don’t have to write much code, just describe the specifics. Even though I can’t discuss all of it now, I’d like to briefly mention all the public methods and properties of the base class. In this way I hope to give you an idea of the functionality with which the base class helps the custom domain classes and also give you an idea of what I’ll be discussing in future articles. (As you might imagine there’s a wealth of protected some of those are Overridable (virtual) and private members too, but I’ll save that for the time being.)

EntityBase

The EntityBase class is shown in Figure 3.

Figure 3 The EntityBase class

Key

This (ReadOnly) property will return the primary key of the object (or rather, row) in the database. I’ll simplify for now and assume that all tables in the database have a single column for the primary key and are of the same data type, for example UNIQUE IDENTIFIER (GUID).A Main Design Goal
A main design goal is to keep it simple for the packages to the left. If you take a look at Figure 5, you will see what I mean by to the left.

Figure 5 Overview of the new architecture

What I mean is that it’s important to expose consumers to as simple an interface as possible. Just following a few protocols when writing the classes in the persistence layer is better than the client programmer having to follow a lot. .

A good example of this is where the Persistence layer has to deal with the IPersistable interface for instantiating EntityBase subclasses, while consumers don’t have to think about it at all when they create new instances. Consumers just use New().

Having said that, let’s now discuss the subclasses a little. Once again, I will only discuss them briefly today and go into more detail in future articles.

Delving into an Entity Subclass
So far, we have discussed some of the general features of the new architecture. Now I’d like to investigate how to use the base class. In Figure 6 you can see a subclass (Customer) to EntityBase.

Figure 6 An example of a subclass to EntityBase

To implement the Customer class, I actually don’t need any private fields at all. What I do need is to define an enum for each property to be exposed, as well as some constants for setting offsets in an Object array that the EntityBase class takes care of. So, all fields that Customer exposes as properties are stored in an Object array. That array is very small for unexpanded objects, but grows when the objects are expanded. (This is not only to deal with more properties, but also to deal with both old and current values.) That was a very quick and condensed explanation of some of the inner workings of the EntityBase class. I will have to focus more on this in a future article.

I also have to write some code for each property. However, I have generalized it quite a bit to avoid code bloat. In Listing 1 you can see an example of a property implementation in the Customer class.

Public Property Name() As String

The Changes
I have touched on a couple of the changes I decided to make since writing part 1 (see left column) and here they are summarized:

No EntityCollectionBase

Well, this one has been discussed quite a lot by now, so let’s move on to the next change instead.

Everything via interfaces

You don’t have to use EntityBase at all. You can use interfaces instead. This is important if, for example, you want to use a superclass other than EntityBase.

Encapsulate Collection refactoring

Earlier, the order collection was fully accessible as a property in the Customer class. Now I have applied the Encapsulate Collection[9] refactoring. This means that consumers to the Customer class will only get a ReadOnly collection when the Orders property is used. AddOrder() is used on the Customer class for adding orders. Therefore the Customer class controls the collection (or whichever datatype that is used internally.)

IsValid() As Boolean

I have changed the Validate() signature over and over again, but now I have decided that it will return a Boolean and not raise an exception when a business rule has been broken. A broken business rule isn’t an exceptional happening. This gives a little smoother interface, but when you call to persist an object that isn’t valid you get an exception anyway. Then you have done something exceptional.

I also changed the name to IsValid().IsValidated() is not found as I don’t think it is needed as a public method any more.

No events about dirty going upwards

Earlier, all objects told their parents when they got dirty. I have now turned the responsibility for tracking changed children around, so the parent has to ask the children instead, if it wants to know.

One good thing about this is that it’s no longer a problem if HasDirtyChild() returns True too often. This was a problem before because the child object that was dirty might have become clean, so to speak. However, it wasn’t

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist