3

I am trying to separate my domain model and associated business logic from my persistence model, particularly such that the domain model has no dependency on the ORM. One apparent way to do that was to make an ORM-coupled subclass of my POCO domain model object, with persistence layer specific properties and logic in that subclass. In this way, I can easily test my domain model, and write domain services that know nothing about my persistence layer. Using private setters, I can write behavior-based methods and enforce invariants.

The argument was made however that this is a flawed approach and that my DAOs should not be structurally tied to my domain model. I am trying to understand if this is so and why. For instance, I quickly found this article that makes a similar argument--but found that what I had already done to address problems I saw answered 85-90% of the objections, and is in fact very similar to the author's #2 solution (though that doesn't even separate the two into subclass/superclass).

This can be accomplished partly just by using POCOs with EF Core, but I wanted to enforce the decoupling somewhat by having my domain model and services in its own project/assembly with no dependency on EF. My application service layer however, which will know about the persistence layer and the domain model, can depend on both in its own project/assembly. Thus, I can have the superclass (domain model) in one project and the coupled subclass (EF entity) in another, as a boundary to control dependencies.

What's wrong with this approach?

Here's part of a domain model class, for what it's worth:

namespace App.Core
{
    public class Event
    {
        public String Title { get; private set; }
        public DateTimeOffset Start { get; private set; }
        public DateTimeOffset End { get; private set; }
        public Boolean IsShownOnCalendar { get; private set; }
        public Team InitiatingTeam { get; private set; }
        public Person InitiatingPerson { get; private set; }

        public IEnumerable<Tag> Tags { get; }
        public IEnumerable<TagAction> TagActions { get; }
        public IEnumerable<Artifact> Artifacts { get; }

        protected Event() { }

        // ... behavior methods, business logic...
    }
}

And part of the subclass which is mapped to a DB via EF:

namespace App.Repository.Entities
{
    public class Event : App.Core.Event
    {
        public int Id { get; private set; }
        public int InitiatingTeamId { get; private set; }
        public int InitiatingPersonId { get; private set; }
        public DateTimeOffset LastModified { get; private set; }
    }
}

This started as a Code Review question about a Builder pattern to support this structure, but the whole thing was arguably to make this somewhat-locked-down model arrangement work...so is there a larger reason that it shouldn't be made to work?

6
  • I like this question, though I guess some people here will think it is too broad.
    – Doc Brown
    Aug 23, 2019 at 20:27
  • 1
    Vaughn Vernon has a blogpost that sort of discusses this topic. The separated interface approach he describes is more or less (if you are careful with the design) the same thing as deriving from the domain object. Aug 25, 2019 at 13:26
  • @FilipMilovanović Thanks! Yes it is similar. I'm annoyed though that his first objection to the interface design is that of naming collision--and I don't think I'm as smart as him and I figured that out: different namespaces with the same name (that's what they're for). His second approach solves the problem of clients inspecting state directly, but inverts the dependency direction--the domain object can see and use the persistence-layer-specific parts of the state object, and if any persistence dependencies have to be added the domain model is now dependent, which is what I wanted to avoid.
    – S'pht'Kr
    Aug 26, 2019 at 12:47
  • His objection is less about the naming collision and more about his desire to for model elements to reflect the ubiquitous language - I've seen some of his tweets and I get the impression that he doesn't like the I-prefix convention; however, IMO, the argument is somewhat superfluous, and that convention is so well established in the C# community that people may find it confusing if it is not followed. Aug 26, 2019 at 18:01
  • You are absolutely right about the dependency structure in the second approach, though, which is why I didn't refer you to it (but it's an option to consider). There are potentially ways around it - say, you could introduce an interface in between - however, it's likely to be more trouble then it's worth (and it would be, in a birds eye view, essentially the same thing as to the first approach, but with an extra level of indirection and added complexity). Aug 26, 2019 at 18:01

2 Answers 2

1

I found an implementation impact reason not to do this for Entity Framework. If you make your EF-coupled entities a subclass of your domain model, you can't have (or I didn't see a way to have) subclass relationships among entities work right for EF.

That is, if I have:

Core.Event <-- Entities.Event

But I want a subclass of Event called OnlineOnlyEvent that expresses a domain concern, the subclass has to inherit from Core.Event...

Core.Event <-- Entities.Event
    ^
    |
Core.OnlineOnlyEvent

So if EF is mapped to Entities.Event as the entity, then a new class Entities.OnlineOnlyEvent can't derive from both, it has to derive from Core.OnlineOnlyEvent, so now EF won't see a subclass relationship among its entities--they are orthoganal.

Core.Event <-- Entities.Event
    ^
    |
Core.OnlineOnlyEvent <-- Entities.OnlineOnlyEvent

"I am your father's brother's nephew's cousin's former roommate." "What's that make us?" "Absolutely nothing!"

So they'll become completely separate tables with no heirarchy relationship--so I would be unable to do TPH for this in EF Core (I suppose I could see it as a way to force TPC in EF Core...sort of).

Since the whole purpose of what I was doing was to isolate the entities for EF, this makes the solution non-viable, though for an implementation reason and not for a more structural/architectural reason.

Notably, the notion that "you shouldn't do this because you should not have such a strong relationship between your domain model and persistence model" does not seem to hold water. Not only is the widespread naive practice to use the domain model as the persistence model directly, but even the oft quoted Vladimir Khorikov states:

For those of you who might wonder why bother with this at all and not just separate the domain model into domain and persistence models and keep the domain model encapsulated this way: it doesn’t work out well. In complex applications, the amount of effort required to build a separate persistence model doesn’t justify the improvements in terms of purity. The effort is too large, the benefits are too small.

The only use case where it’s reasonable is with legacy databases. Trying to bridge the gap between such database’s structure and the domain model is almost impossible, so you are pretty much forced into building a separate persistence model. In all other cases, consider relying on the plain ORM and accepting its shortcomings if any.

The general sentiment of which I have come to very heartily agree with, after this long and mostly futile attempt to have my cake and eat it too.

1
  • +1 for the "SpaceBalls" quote. And the bit about just accepting the short comings of an ORM and not fighting it also makes me feel better about voting for this question. Feb 2, 2020 at 21:37
1

No

The argument was made however that this is a flawed approach and that my DAOs should not be structurally tied to my domain model. I am trying to understand if this is so and why.

Because you cannot inherently rely on your domain and entity models to match. In a comment on a (now deleted) answer you said:

Appreciate the response, but find my self disagreeing with the reasons. "The two rarely match": I find this to not be true in practice, especially if starting the data structure from scratch I will be making them match

Just because two things happen to look alike (in a particular way) does not mean they should share a tangible connection in that likeness.

Changing the topic for a simpler example, just because Animal and User both have a string Name property, does not mean that you're required to abstract this into a common interface. There's no inherently relationship between the name of an animal and that of a user, even though they both have a name.

It's possible that in the future, one of these will change (e.g. we will store users' first and last names separately) and the other will not (animals have one name). Had we made a shared interface, we would be stuck having to untangle this shared commonality as the two names did not have as much in common as we initially thought they did.


The same argument applies to your use case of entities and domain objects. Just because they match today, doesn't mean they will match tomorrow. Let's use a simple example of a person:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string NationalIdentificationNumber { get; set; }
}

public class PersonEntity : Person { }

This works the way you expect it to. EF will use PersonEntity's properties (including inherited ones) and will store everything.

I'll give you two examples of why this is a problematic situation.

1. What happens when you no longer want to store all data in the database?

Suppose the requirements change, and you're expected to always fetch a person's latest personal information from a government API, using the person's national identificaton number. This means that you'll only store the person's NIN in the database, and the other properties are retrieved from a different source at a later stage.

In the above setup, you can't do that. EF will still store all the properties, and there's no way to put an ignore attribute on an inherited property.

Even if you tell EF to ignore certain properties (I suspect some type of configuration option exists), it makes no sense to inherit from a class whose properties you then want to hide - it's a strong code smell of using bad inheritance strategy.

Instead, you shouldn't have been using inheritance, but simply two separate classes (which happen to have the same properties):

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string NationalIdentificationNumber { get; set; }
}

public class PersonEntity
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string NationalIdentificationNumber { get; set; }
}

Now, you are able to alter one without affecting the other, e.g. when the database no longer stores the entire domain model:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string NationalIdentificationNumber { get; set; }
}

public class PersonEntity
{
    public string NationalIdentificationNumber { get; set; }
}

2. What if you want to store your data differently?

You don't always want to handle your data (i.e. your domain model) the same way as you store it (i.e. your entity). Granted, EF tries its best to ensure you don't have to worry about conversions by handling all handleable types for you, but there may be cases where you want the stored data to be different from the domain data.

For the sake of example, let's use a simple case of wanting to store a record's status as a FK to a table, but in your domain you wish to use an enum. If you don't like the example, swap it out for one you like better, the technical argument remains the same.

Using inheritance:

public class Record
{
    public MyStatusEnum Status { get; set; }
}

public class RecordEntity : Record
{
    public int StatusEntityId { get; set; }
    public virtual RecordStatusEntity StatusEntity { get; set; }
}

We get into the same problem as before: you're trying to derive from a class but then selectively hide some of the base class' properties, which is still a strong code smell of using bad inheritance strategy.


To summarize

Yes, it initially seems a bit silly to create two classes with exactly the same properties. Developers have a natural urge to want to merge the two as gracefully as they can.

But don't fall into the trap of thinking that because two things look alike today, that they therefore represent the same thing or will always change in the same way at the same time.

There are two different purposes here (domain representation vs persistence storage), and these purposes need to be represented separately, because tying them together means that it will cost extra time and effort when they have to separate in the future.

Think of it this way: since you and a coworker always sit next to each other when working, and it's easier to build one bench than it is to build two desk chairs, it makes sense to build one bench for the two of you, right?
But that decision will become a costly decision when one of you moves to another location and the other doesn't, as you're now either going to have to build an additional desk chair (and thus have a bench + chair, which is one seat too many in total) or removing the bench and building two desk chairs.

In either case, the resulting task is inefficient, compared to when you had built two desk chairs to begin with. That decision would've meant that you and your coworker would not be needlessly tied together just because you happened to sit next to them at the time the decision was made.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.