I typically use universally unique identifiers (UUIDs), also known as globally unique identifiers (GUIDs), as the public IDs for objects in an application. This has a number of advantages.
I cover this in my .NET book but in summary, GUIDs provide a large random ID that will be unique and can be generated outside of your database. This means that you can generate objects across a distributed system and merge the results without getting conflicts. They are also hard to guess or enumerate and don’t reveal potentially sensitive information.
However, there are drawbacks to using a large random value as the primary key in a database and it can have performance impacts. The approach I use regularly is a hybrid one where the DB PK is still an auto-incrementing integer but this is just an implementation detail and is not exposed on any APIs. A GUID is used for referencing an object on a API request or response.
If you are doing this with Django and its admin interface then there are some things to keep in mind. I’m using 3.2.10 (on Python 3.10 with PostgreSQL) but this should apply to 4.0 too.
Set the ID of Django models to
settings.py. This is the default as of 3.2 so there is nothing to do if you generated the app using this version or later. A 64-bit integer means you won’t quickly run out of rows, which can be a pain to extend later.
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
I then add a
UUIDField to the model. This should have a unique constraint, be indexed and be non-nullable. The default for
False but it’s always good to be explicit.
import uuid guid = models.UUIDField( default=uuid.uuid4, editable=False, null=False, unique=True, db_index=True )
This will generate the UUID in the Django application, unlike the PK that is generated in the DB. If you are using the admin site then you can add it to the
readonly_fields so that you can see it on your models. However, as the UUID is generated fresh every call, the one you see on a create page isn’t actually the one that will be saved. To avoid user confusion you can add a function to only display the GUID if there is a PK from the DB.
readonly_fields = [ "guid_if_saved", ] @admin.display(description="GUID") def guid_if_saved(self, instance): return instance.guid if instance.id is not None else "-"
Only expose GUIDs on your APIs and not the integer IDs. If you are using Django REST framework then this can be achieved by simply omitting
id from the model serializer and specifying
guid in the