Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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,
share|improve this question
1  
Point to ponder: often times communication with domain experts gets impoverished when surrogate PK are used when a good business key exists. It seems we end up working for the ORM framework, instead of the other way around. –  user61852 Aug 27 at 12:10
    
@user61852 well regardless of ORM, even if you really low-level, you still need a primary key in the implementation of the database layer. So I agree that surrogate PK gives you advantages over the actual PK used by a specific persistence mechanism, but if that PK is really representing a business object that is meaningful, it is necessarily unique and therefore has at least one unique business-related property defining it, no? –  tacos_tacos_tacos Aug 27 at 12:48
1  
All of the advantages of surrogates are computer related and none human-related. –  user61852 Aug 27 at 12:56
2  
@user61852: I agree 100% (did I write something different?). For means of communication, use a "business key". Add unique contraints for any business key as well. But avoid the use of business keys to actually implement your database references. –  Doc Brown Aug 27 at 15:25
2  
business keys are eternally unique - until they aren't. If you are using the business keys as primary when this happens then the change in business rules breaks more stuff. –  psr Aug 27 at 21:39

6 Answers 6

up vote 31 down vote accepted

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.

share|improve this answer
    
What about the step of taking the domain object from the returned persistence object (entity, or table row model, or whatever), going to DTO, and taking it back in, and going back to persistence? Is this just done via a surrogate key (ie a business-oriented definition of uniqueness) that requires a resolution on each persistence operation? –  tacos_tacos_tacos Aug 27 at 11:27
1  
@tacos_tacos_tacos: let's stick to your VoIPProvider example. I would actually add a "VoIPProviderID" to your DTO, at least at the "implementation side" (if you have also a graphical version for your domain experts, I would probably not show it there). For updating purposes, the standard way of identifying a specific VoIPProvider should be by the "VoIPProviderID" you retrieved when pulling tha data from the database. If users of your API prefer identification by (name,URL), provide that additionally. ... –  Doc Brown Aug 27 at 11:56
    
... and if performance seems to become a real, measurable problem, you can also consider to cache the mapping (name,url) to VoIPProviderID somewhere. But I would not recommend of implement such an optimization beforehand, prematurely. –  Doc Brown Aug 27 at 11:58
    
All this because surrogate keys are ugly ? –  user61852 Aug 27 at 12:00
1  
Natural keys sometimes seem really compelling but it's too easy to get burned (e.g., "whoops, now I have multiple tenants and that's no longer unique"). –  emodendroket Aug 27 at 18:46

You need to be able to identify many objects by some unique index, and isn't that what a primary key is (or at least implies one is present).

Unique indexes are available so you can further constrain your DB schema, not as a wholesale replacement for PKs. If you're not exposing PKs becuase they;re ugly, but are exposing an unique key instead... you're not actually doing anything different. (I assume you're not getting a PK and an identity column mixed up here?)

share|improve this answer
    
When I want to do an operation involving a specific instance of a domain object that is persisted, I need to be able to include in the DTO either explicitly (via some sort of key, either primary or an alternate user-friendly ID that is unique to that table and might as well be an index on the table) or implicitly (via some combination of fields whose values uniquely identify a specific record)... and furthermore, in a practical sense, I would need to send up in the DTO some form of change tracking if I were to do it the other way (OriginalVal vs NewVal for all fields that identify record),no? –  tacos_tacos_tacos Aug 27 at 11:10
    
isn't the explicit v implicit question the same difference? You can have a PK that spans multiple columns, just like a unique index. I don't see there is any difference between them for your purposes. –  gbjbaanb Aug 27 at 12:04
    
Sure, we could have a PK on multiple columns for instance. But to me it is leaking something about the databaswe (the storage) which should have NOTHING to do with the heart & soul, the business entity. If some tuple of business entity fields happens to span the PK for the DB, then great. But it shouldn't necessarily be the other way around, should it? –  tacos_tacos_tacos Aug 27 at 12:39
    
You're overthinking it. A unique index is just as much an artifact of the DB schema as a PK is. Think of it this way - a PK is just the first (or primary) unique index. its 'special' because you only generally need 1 such index. –  gbjbaanb Aug 27 at 12:43
    
True, but any meaningful domain object should be identifiable strictly from at least one of its business-related fields, no? The fact that this defines an index in the DB is more for performance reasons than to be used to query the DB easily... I would prefer a one-column PK over a 6-column unique index, and they serve different purposes really - a PK (or index with small number of fields) is also available for the DBA/DBD's convenience, right? –  tacos_tacos_tacos Aug 27 at 12:51

Without primary keys in the frontend there is no easy way for the backend to know what you are sending. To fix that you would need a ton of extra work parsing the data, which would hurt performance and probably take more time and be more ugly than attaching a key to every item.

As an example, lets say that I want to edit a message in an app; how would the app know which message I want to edit without a primary key attached? Editing objects happens all the time and doing that without keys is nigh impossible. But if you have objects that aren't supposed to be edited then skip the key if you think that it is distracting, but having primary keys can improve performance here.

share|improve this answer
    
Well, the message is an extreme example, but even then we would know MessageSender, MessageRecipient, TimeSent - that should be unique. –  tacos_tacos_tacos Aug 27 at 11:28
1  
@tacos_tacos_tacos, then how do you create FKs to the other tables? It should be MessageSenderId, which probably maps to a Users table on UserId. You wouldn't want to use the UserName as the key between tables, as that could change and becomes a maintenance nightmare. This is why you generally only join tables using Primary Keys, and not another column (there are certainly exceptions). This db structure still must be enforced. Now you could always go to a CQRS model for your app...in which case the rules change. Especially if you also use Event Sourcing. –  CaffGeek Aug 27 at 21:13

The reason we use a non-business related PK is to ensure that our system has an easy and consistent method of determining what the user wants.

I see you replied with a comment: MessageSender, MessageRecipient, TimeSent (for a message). You can STILL have ambiguity this way (for example, with system generated messages triggering on something that happens often). And how are you going to validate MessageSender and MessageRecipient here? Suppose you validate them using FirstName, Lastname, DateOfBirth, you're going to eventually run into a situation where you have 2 people born on the same day with the exact same name. Not to mention that you will run into a situation where you have a message named tacostacostacos-America-1980-Doc Brown-France-1965-23/5/2014-11:43:54.003UTC+200. That's a monster of a name, and you still have no guarantee you'll only have 1 of those.

the reason we use a primary key is because we KNOW it'll be unique for the lifetime of the software, no matter what data is entered, and we KNOW it's going to be a predictable format (what happens if your above key has a dash in a username? your whole system goes to shit).

You don't need to show your ID to your user. You can hide that (in plain sight if necessary, through the URL).

Another reason why a PK is so useful is something you can deduce from the above: a PK makes it so that you don't have to force the computer to interpret user-generated code. If a Chinese user uses your code and enters a bunch of Chinese characters, your code suddenly doesn't need to be able to work with these internally, but it can simply use the Guid that the system generated. If you have an Arabic user who enters Arabic writing, your system doesn't have to cope with that internally, but can basically ignore that they're there.

As others have said, a Guid is something that can be stored internally in a fixed size. You know what you work with and it's something that can be universally used. You don't need to create design rules on how you create a certain identifier and save it. If your system only takes the first 10 letters of a name, it doesn't see any difference between Michael Guggenheimer and Michael Gugstein, and it will confuse these 2. If you cut it off at any arbitrary length, you can run into confusion. If you limit user input, you can run into issues with user limitations.

When I look at existing systems like Dynamics CRM, they also use the internal key (the PK) for the user to call upon a single record. If a user has a query that doesn't involve the ID, they return an array of possible answers and let the user choose from it. If there is any chance at ambiguity, they'll give the choice to the user.

Finally, it's also abit of security through obscurity. If you don't know the record ID, your only option is to guess it. if the ID is easy to guess (because the information that it's made of is publicly available), anyone can change it. You can even goad the user into changing it through classic CSRF or XSS methods. Now, obviously your security should already have those accounted for and mitigated before ever publishing the live version, but you should still make it harder for potential abuse to happen.

share|improve this answer

When issuing an identifier for an external system, you should only give URIs, or alternatively a key or a set of keys that have the same properties as a URI, rather than exposing the database primary key directly (from here onwards I'll refer to both URI or a key or a set of keys that have the same properties as URI as just URI, in other word a URI below doesn't necessarily mean RFC 3986 URI).

A URI may or may not contain the primary key of the object and it may or may not actually be composed of alternate keys. It doesn't really matter. What matters is that only the system that generates the URI is allowed to split or combine the URI to form an understanding of what the referred object is. External systems should always use the URI as an opaque identifier. It doesn't matter if a human user can identify that one part of the URI is actually a database surrogate key or that it consists of several business keys bunched up together, or that it's actually a base64 of those values. These are irrelevant. What matters is that the external system shouldn't need to be required to understand what the identifier means to use the identifier. External systems should never be required to parse the components within the identifier or combine an identifier with other identifiers to refer to something in your system.

Using GUID fulfills some of this criteria, however identifier like GUID can be difficult to dereference back into the object even within your system, so keys that are opaque to even your system like a GUID should only be employed if client parsing the URI/identifier actually poses a security risk.

Back to your VoIP example, say that a VoIP provider can be uniquely determined either by (VoIPProviderID) or by (Name, URL) or by (GUID). When an external system needs to update the VoIP provider, it can just pass a PUT /provider/by-id/1234 or PUT /provider/foo-voip/bar-domain.com or PUT /3F2504E0-4F89-41D3-9A0C-0305E82C3301 and your system would understand that the external system wants to update the VoIPProvider. These URIs are generated by your system and only your system need to understand that all they means the same thing. The external system should just treat whatever is in the URI as basically a PUT <whatever>.

Suppose that you have the data for different VoIP providers stored in different tables with different schemas (thus completely different set of keys identifies each VoIP provider based on which table they are stored in). When you have a URI, they can be accessed uniformly by the external system, without regards of how your system identify the particular VoIP provider. To the external system it's all just opaque pointer.

When your system uses a URI to refer to objects in such a way, you're not leaking anything regarding how you implement your system. You generate the URI and the client simply passes it back to you.

share|improve this answer

I am going to have to target this wildly inaccurate and naive statement:

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.

Names are terrible as keys, as they are frequently changing. A company may have many names during its lifetime, the company may merge, split, merge again, create a distinct subsidiary for specific tax purposes that has 0 employees but all customers which then hires employees from a completely different subsidiary.

Then we get into the fact that company names are not even remotely unique, as shown by the landmark Apple vs. Apple.

A good object relational mapper or framework should abstract the primary keys away and make these invisible, but they are there, and they will usually be the only way to uniquely identify an object in your database.

For reference, I prefer how django handles this:

class VoipProvider(models.Model):
    name=fields.TextField()
    address=fields.TextField()

class Customer(models.Model):
    name=fields.TextField()
    address=fields.TextField()
    voipProvider=fields.ForeignKeyField(VoipProvider)

In this way, the details of a provider of a customer can be access in code by using:

myCustomer.voipProvider.name #returns the name of the customers VOIP Provider.

While the Primary/Foreign keys aren't visible, they are there, and can be used to access items, but are abstracted away.

share|improve this answer
    
You're absolutely right, but I guess I meant that "in some domains, maybe there is a natural tuple of values that are always unique" If they change, but they're still unique, they still identify a single record. –  tacos_tacos_tacos Aug 28 at 7:15

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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