ctxid - DDD context ID Scheme
This is a tiny library for generating and inspecting what can be called
a "Domain Driven Design"-like IDs, that embed metadata like type, owner,
name and timestamp.
Instead of using random UUIDs, this library generates IDs that are
meaningful, readable, sortable and queryable.
Domain structure
The basic structure of the ID is:
// acme/anvil/JM1KHIS4391nNe6
// ^ ^ ^
// | | context id with date
// | type or subdomain-name
// namespace or domain-name
//
// the first 10 chars in the context-id JM1KHIS4391nNe6
// is the date when it was created, and the last 5 characters are random
// to space out collisions (although the length is configurable).
Like domain names, objects can belong to nested domains and subdomains.
The top-level domain is known as a namespace. This is the highest level
that can be used to group all the objects of that top-level domain.
domain/type/context-id
Domain is optional, if not specified, the object can be considered "global"
or "universal" or as not belonging to a specific owner.
/type/context-id/type/context-id
A nested domain or subdomain is just added as a postfix to the ID:
domain/type/context-id/sub-domain/sub-context-id/.../.../...
Example
Let's say your company name is ACME corp and you build anvils,
your namespace would be acme and your type-name will be anvil,
the ID of an anvil could look like this:
const ctx = ctxId({domain: 'acme', type: 'anvil'});
console.log({ctx});
const date = ctxDate('JM1KHIS4391nNe6')
console.log({date})
const info = inspectCtx('acme/anvil/JM1KHIS4391nNe6')
console.log({info})
Output:
{
"info": {
"meta": {
"timestamp": "2023-01-21T18:27:43.596Z",
"ageMs": 494157,
"id": "acme/anvil/JM1KHIS4391nNe6",
"version": "latest",
"hasVersion": false
},
"namespace": "acme",
"type": "anvil",
"id": "JM1KHIS4391nNe6",
"isSequence": false,
"typePointer": "anvil",
"idPointer": "acme/JM1KHIS4391nNe6",
"typePath": "anvil",
"idPath": "acme.JM1KHIS4391nNe6",
"date": "2023-01-21T18:19:29.439Z",
"relativeDate": null,
"domain": {
"id": "acme",
"type": "namespace"
},
"validLength": true,
"length": 3,
"seed": [
1,
50,
24,
41,
6
]
}
}
If you want a list of all the anvils, you can use a query like
startsWith('acme/anvil') or something similar.
Subdomains
Let's say ACME corp has a subsidiary called "Anvil Inc" and
"Heavy Inc", who each manufacture anvils, the IDs of the
anvils could look like this:
const anvilInc = ctxId({type: "subsidiary", domain: "acme"})
const heavyInc = ctxId({type: "subsidiary", domain: "acme"})
const anvil1 = ctxId({type: "anvil", domain: anvilInc})
const anvil2 = ctxId({type: "anvil", domain: heavyInc})
console.log({office1, office2, anvil1, anvil2})
Alternatively, you can just use the names of the subsidiaries
instead of a generated ID since these are already unique
at a name-level:
const anvilInc = 'acme/subsidiary/anvil-inc'
const heavyInc = 'acme/subsidiary/heavy-inc'
const anvil1 = ctxId({type: "anvil", domain: anvilInc})
const anvil2 = ctxId({type: "anvil", domain: heavyInc})
console.log({anvilInc, heavyInc, anvil1, anvil2})
Looking at these IDs, we can see the relationship the object has
to its domain and subdomains, and you can query parts of the ID
to retrieve types of collections inside a domain and having it
sorted chronologically by default.
Benchmark
Machine: Apple M1 Max
Avg. time to generate 1,000,000 IDs: 460ms