Overview#
Singleton sub-resources are a hybrid between a simple resource property and a full-fledged resource. They store data that is conceptually part of a parent resource but kept in a separate, isolated entity to address concerns such as size, security, volatility, or differing access patterns.
Motivation — why split data into a singleton sub-resource#
Common reasons to separate a component from its parent resource:
- Size or Complexity: The component may be much larger or more complex than the rest of the resource (e.g., binary object payloads, very large lists such as ACLs). Returning that data with every parent fetch is inefficient.
- Security: The component may have stricter or different access controls than the parent (e.g., employee compensation vs. general profile data).
- Volatility / Access patterns: The component may change far more frequently than the rest of the parent (e.g., frequently-updated location data). Separating it reduces write contention and enables independent scaling.
Concept#
A singleton sub-resource behaves like a mix of property and resource:
- It is implicitly created with the parent (behaves like a property for creation).
- It is addressable and modifiable via its own identifier and standard resource operations like
get
andupdate
(behaves like a resource for retrieval and mutation). - It is a single instance per parent (not a collection).
Example concept: move
Driver.location
from a simple attribute to adrivers/{id}/location
singleton sub-resource.
Implementation details#
Supported methods (summary)#
Standard method | Acts like a… |
---|---|
Get<Singleton> | Separate resource |
Update<Singleton> | Separate resource |
Create<Singleton> | Property (implicit) |
Delete<Singleton> | Property (cascades) |
List<Singletons> | n/a |
GET and UPDATE#
get
andupdate
behave like for any other resource: the singleton is addressable and modifiable via its own path (e.g.,GET /drivers/1/location
,PATCH /drivers/1/location
).- Partial updates (field masks / PATCH semantics) are appropriate for
update
operations.
Example
GET /drivers/1/location
DriverLocation { lat: 40.741718, long: -74.004159 }
PATCH /drivers/1/location
{ driverLocation: { lat: 40.742 }, fieldMask: ['lat'] }
DriverLocation { lat: 40.742, long: -74.004159 }
CREATE (implicit)#
- The singleton sub-resource exists by virtue of the parent resource’s existence. There is no separate explicit
Create
call to instantiate the singleton. - You cannot atomically create the parent and initialize the singleton in one RPC; changes to the singleton must be made by separate calls after the parent exists.
- Therefore, choose reasonable defaults for the singleton so it is useful when implicitly initialized.
Example
POST /drivers { ... }
Driver { id: "drivers/1", ... }
GET /drivers/1/location
DriverLocation { lat: ..., long: ... }
DELETE (cascade)#
- Deleting the parent resource must remove the singleton sub-resource (cascade behavior). The singleton acts like a property with respect to deletion.
- For non-instant deletes (LROs) or soft-deletes, the singleton should remain until the parent deletion completes and then be removed.
Example
GET /drivers/12345/location
DriverLocation { lat: ..., long: ... }
DELETE /drivers/12345
200 OK
GET /drivers/12345/location
404 Not Found
Warning: If cascading deletion surprises consumers, the singleton pattern may not be a good fit.
Resetting#
- The API may provide a
reset
method to atomically restore the singleton to its reasonable defaults (the state it would have had when the parent was created). reset
preserves the existence of the sub-resource while clearing or restoring its values.
Example
GET /drivers/1/location
DriverLocation { lat: 40.741718, long: -74.004159 }
POST /drivers/1/location:reset
DriverLocation { lat: null, long: null }
Hierarchy rules#
- Required parents: A singleton sub-resource must always be attached to a parent resource — it must not be a root-level/global singleton. Global singletons create write contention and act like a global lock.
- Full-resource parents: A singleton sub-resource should not be the parent of another singleton sub-resource. If you need multiple related singletons, make them siblings of the parent rather than nesting singletons.
Trade-offs#
Atomicity: You lose the ability to perform atomic operations that simultaneously modify both the parent and the singleton. Because creation acts like a property and updates act like an independent resource, there is no single RPC that can safely lock and change both at once. This is an intentional trade-off to gain isolation, scalability, and separate security.
Exactly one sub-resource: The singleton pattern allows only one instance of the sub-resource per parent. It is not a collection and cannot represent multiple child items of the same type.
Exercises (no answers provided)#
- How big does attribute data need to get before it makes sense to split it off into a separate singleton sub-resource? What considerations go into making your decision?
- Why do singleton sub-resources only support two of the standard methods (get and update)?
- Why do singleton sub-resources support a custom reset method rather than repurposing the standard delete method to accomplish the same goal?
- Why can’t singleton sub-resources be children of other singleton sub-resources in the resource hierarchy?
Short summary#
Singleton sub-resources let you isolate an important, single-instance component of a parent resource when doing so improves performance, security, or correctness. They are implicitly created with the parent, are addressable and mutable via their own get
/update
endpoints, cascade on parent deletion, and optionally support a reset
operation — at the cost of losing single-call atomic updates across parent and sub-resource and constraining you to exactly one sub-instance per parent.