Architecture – Addressing Primary Keys Outside Business Domain

Architecture

In almost all circumstances, primary keys are not a part of your business domain. Sure, you may have some important user-facing objects with unique indices (UserName for users or OrderNumber for orders) but in most cases, there is no business need to overtly identify domain objects by a single value or set of values, to anyone but perhaps an administrative user. Even in those exceptional cases, especially if you are using global unique identifiers (GUID), you will like or want to employ an alternate key rather than expose the primary key itself.

So, if my understanding of domain-driven design is accurate, primary keys need not and thus should not be exposed, and good riddance. They're ugly and cramp my style. But if we choose not to include primary keys in the domain model, there are consequences:

  1. Naively, data transfer objects (DTO) that derive exclusively from combinations of domain models will not have primary keys
  2. Incoming DTO's will not have a primary key

So, is it safe to say that if you are really going to stay pure and eliminate primary keys in your domain model, you should be prepared to be able to handle every request in terms of unique indices on that primary key?

Put in another way, which of the following solutions is the correct approach to dealing with identifying particular objects after removing PK in domain models?

  1. Being able to identify the objects you need to deal with by other attributes
  2. Getting the primary key back in the DTO; ie, eliminating the PK when mapping from persistence to domain, then recombining the PK when mapping from domain to DTO?

EDIT: Let's make this concrete.

Say my domain model is VoIPProvider which includes fields like Name, Description, URL, as well as references like ProviderType, PhysicalAddress, and Transactions.

Now let's say I want to build a web service that will allow privileged users to manage VoIPProviders.

Perhaps a user-friendly ID is useless in this case; after all, VoIP providers are companies whose names tend to be distinct in the computer sense and even distinct enough in the human sense for business reasons. So it may be enough to say that a unique VoIPProvider is completely determined by (Name, URL). So now let's say I need a method PUT api/providers/voip so that privileged users can update VoIP providers. They send up a VoIPProviderDTO, which includes many but not all of the fields from the VoIPProvider, including some flattening potentially. However, I can't read their minds, and they still need to tell me which provider we are talking about.

It seems I have 2 (maybe 3) options:

  1. Include a primary key or alternate key in my domain model and send it to the DTO, and vice versa
  2. Identify the provider we care about via the unique index, like (Name, Url)
  3. Introduce some sort of intermediate object that can always map between persistence layer, domain, and DTO in a way that does not expose implementation details about the persistence layer – say by introducing an in-memory temporary identifier when going from domain to DTO and back,

Best Answer

This is the way how we solve this (since more than 15 years, when even the term "domain driven design" was not invented):

  • when mapping the domain model to a database implementation or a class model in a specific programming language, you have a simple, consistent rule like "for each domain object mapped to a relational table, the primary key is "TablenameID".
  • this primary key is completely artificial, it has always the same type, and no business meaning - just a surrogate key
  • the "graphical version" of your domain model (the one you use to talk to your domain experts) does not contain primary keys. You don't expose them directly to the experts (but you expose them to anyone who is actually implementing code for the system).

So whenever you need a primary key for technical purposes (like mapping relations to a database), you have one available, but as long as you don't want to "see it", change your level of abstraction to the "domain experts model". And you don't have to maintain "two models" (one with PKs and one without); instead, maintain only a model without PKs and use a code generator to create the DDL for your DB, which adds the PK automatically according to the mapping rules.

Note that this does not forbid to add any "business keys" like an additional "OrderNumber", besides the surrogate OrderID. Technically these business keys become alternate keys when mapping to your database. Just avoid using these for creating references to other tables, always prefer using the surrogate keys if possible, this will make things a hell lot easier.

To your comment: using a surrogate key for identifying records is no business-related operation, it is a purely technical operation. To make this clear, look at your example: as long as you don't define additional unique-contraints, it would be possible to have two VoIPProvider objects with the same combination of (name,url), but different VoIPProviderIDs.

Related Topic