Advantages Of Using UUIDs
I started switching over to UUIDs in 2019 and would never choose to go back. When I have to go back to old projects that use integers, all the hasstles come back to me, so I thought I'd make this post to record of all them as they come to me.
When you use auto-incrementing integers for your unique IDs, you feel that you have to immediately save an object to the database before returning it so that it is "complete" and has it's ID. When you use UUIDs, you know that you have a unique identifier without having to save to the database, so you can choose to return it immediately without saving to the database. This comes to mind most noticeably when I'm dealing with creating a lot of objects and I would rather save them all to the database at once. You can get around this by adding more functions, perhaps a create() and a createMultiple() etc, but I prefer just to have one create() method and then choose what to do with the created objects, from the controller.
A lot of systems use a "flag" column on the database to say whether the object has been deleted or not, or maybe a nullable timestamp. This works as a "quick hack" but prevents you from having data-integrity through foreign keys. E.g. your delete is not going to trigger a cascading delete, or an error to be thrown from RESTRICT.
For me, the ideal solution if you want to have "soft deletes" is to create separate tables for your "deleted" data. This keeps it away from the data you really care about, and can actually boost performance. Best of all, you can keep your referential integrity. Since you use UUIDs instead of auto incrementing integers, there is no "gap" or fragmentation in your primary key that might be taken at a later date (such as if your auto increment resets from a reboot). If you used integers, then there is a high chance that you could have a conflict and will have to put in a lot more effort ensuring that moving rows between your active and deleted tables does not have issues.
Mistakes Become Obvious (Same IDs on Different Tables)
If all your tables are using integers, then some mistakes become obvious sooner. For example, I was working on a model that was accidentally hydrating using the incorrect class name. Initially this went unseen because all the operations still continues to "work". E.g. loading id 3 from the wrong table still returned an object, and calling delete() on that still worked. When I started putting in foreign keys to lock down the database because I was seeing strange behaviour, I found this bug, but this would have become obvious sooner without the FKs because my IDs would not be the same between tables.
Multi-Master Delayed Synchronization
I was recently working on a project where there needed to be two "master" APIs. E.g. there were two of the same deployed codebase, with their own databases, and they needed to both be able to work "offline" without being able to talk to each other in case the internet cut out (which it often did). Hence they both needed to be able to write objects to their databases and sync up with each other later. The only way to do this was to use UUIDs for th unique identifiers as integers would inevitably clash. This allowed a very simple codebase and deployment with little stress.
Identifiers of resources are often put in the URL. For example consider the following URL for viewing a particular resource:
Now if that resource's identifier was an auto-incrementing integer, it would be pretty obvious to someone that they might be able to just adjust the number to view a different resource. With UUIDs, they will not be able to guess any of the other resource's identifiers. It would not be good to rely on this from a security perspective, but it is worth noting and the principal could prove useful in niche circumstances.
More On The Way
As I remember more, I will be sure to put them here.
First published: 29th April 2020