1

Background

I'm currently working on a project using domain-driven design and Dapper as my ORM. The entities are naturally slightly different than the tables in which they are stored (e.g. _id is protected rather than public, collections are read-only, etc.), and I'm struggling to find a good approach for restoring the entities inside my repositories (e.g. mapping the ID column value to the _id field or populating backing collections) without exposing entity methods for those database-specific details.

That is to say, I don't want to end up having a SetId(int id) method inside my entities just to accommodate restoring their non-public _ids when queried from the database, but I'm struggling to see where I should put the mapping logic and how I could implement it in a maintainable fashion.

What I've tried

Static CreateFrom entity method

I considered making a CreateFrom method on my entities to have access to these non-public members, but this could potentially allow creating an invalid entity inside the domain on top of involving implementation details that aren't relevant to the domain.

MapFrom extension method inside infrastructure

I then figured I could create an extension method that does the same, but inside the infrastructure layer where I actually have to do the mapping. The problem here is that I would need an instance of an entity to 'populate' it using the extension method, and that's confusing.

Private constructor and forced mapping

I thought that perhaps I could add a private constructor that can be called with a CreateEmpty extension method inside my infrastructure layer, in order to populate it using MapFrom, but this would again allow for invalid entity states to be persisted.

Using Entity Framework (Core) is not an option

I know EF is powerful enough to do the mapping for me, but unfortunately I'm currently limited to Dapper. How did people organise their codebases before things like EF or NHibernate existed?

JansthcirlU
  • 225
  • 1
  • 6
  • whats the essential problem here? dapper cant map to protected fields? – Ewan Mar 21 '23 at 10:45
  • 1
    https://stackoverflow.com/questions/15299946/mapping-private-attributes-of-domain-entities-with-dapper-dot-net – Ewan Mar 21 '23 at 10:58
  • @Ewan thanks, I didn't think to check SO first, but both approaches seem pretty hard to maintain, factory because the entity and the record are tightly coupled, and reflection, well, because it's reflection... That said, I think I'm gonna go with the factory approach, thanks! – JansthcirlU Mar 21 '23 at 12:05
  • What about having a `SetId` method with scope "internal"? If your repos are in a different assembly, you can use `InternalsVisibleTo` to expose `SetId` to them. – Doc Brown Mar 21 '23 at 14:35
  • I’d create an internal constructor to enforce a valid state that includes the Id and expose it with InternalsVisibleTo. – Rik D Mar 21 '23 at 17:56
  • There is no such thing as "preventing invalid entities from being created". Anyone with the power to write the code to call a constructor also has the power to... well... mess up a lot of things. Instead your goal should be to _configure_ your system such that "invalid entities" will not be created. This often means factory methods. Furthermore, it doesn't matter if an "invalid entity" is created so long as you cannot _do_ anything with it. Validation should be enforced when _doing_ something. This comes with the added benefit that the "context" for when an invariant is enforced is clear. – user3347715 Mar 22 '23 at 18:07
  • @king-side-slide I disagree, constructors can contain validation just like factory methods. There's no point in validating an invalid entity's actions if you could nip all of that in the bud by simply disallowing the invalid entity to exist in the first place. – JansthcirlU Mar 23 '23 at 13:30

1 Answers1

1

What I would do is a combination of both approaches(CreateFrom and MapFrom). Let me explain.

but this could potentially allow creating an invalid entity inside the domain

This suggests that your entities are a bit useless. One of the points of DDD is making illegal states irrepresentable. This means that in order to preserve domain invariants your entities should expose constructor of factory method (CreateFrom) and perform all needed validation.

Naturally, this constructor/factory method should not accept your DB entity as a parameter since the domain layer should be the core of your application free from all infrastructure dependencies. This is the reason why on your infrastructure layer you have to have MapFrom method that calls constructor/factory method from of your domain level in order to create always valid domain entity.

Bohdan Stupak
  • 388
  • 1
  • 4