Watching Relationship Changes
The Watch API (opens in a new tab) in SpiceDB enables clients to monitor changes made to Relationships within the system.
Watch events are generated when relationships are created, touched, or deleted through the WriteRelationships (opens in a new tab), DeleteRelationships (opens in a new tab) or ImportBulkRelationships (opens in a new tab) APIs.
Calling Watch
To receive watch changes, call the Watch
API.
This is a streaming API that will continually return all updates to relationships from the time at which the API call was made.
from authzed.api.v1 import (
Client, WatchRequest
)
from grpcutil import bearer_token_credentials
client = Client(
"localhost:50051",
bearer_token_credentials("your-token-here"),
)
watcher = client.Watch(WatchRequest{})
for resp in watcher:
# process the update
Receiving historical updates
Historical updates (i.e. relationship changes in the past) can be retrieved by specifying a ZedToken in the WatchRequest
:
from authzed.api.v1 import (
Client, WatchRequest
)
from grpcutil import bearer_token_credentials
client = Client(
"localhost:50051",
bearer_token_credentials("your-token-here"),
)
watcher = client.Watch(WatchRequest(
optional_start_cursor=last_zed_token
))
for resp in watcher:
# process the update
Historical changes can only be requested until the configured garbage collection window on the underlying datastore. This is typically 24 hours, but may differ based on the datastore used.
Ensuring continuous processing
Because Watch is a streaming API, your code should handle disconnections gracefully.
To ensure continuous processing, the calling client should execute the Watch
call in a loop, sending in the last received ZedToken from ChangesThrough
if the call disconnects:
from authzed.api.v1 import (
Client, WatchRequest
)
from grpcutil import bearer_token_credentials
client = Client(
"localhost:50051",
bearer_token_credentials("your-token-here"),
)
last_zed_token = None
while not_canceled:
try:
watcher = client.Watch(WatchRequest(
optional_start_cursor=last_zed_token
))
for resp in watcher:
# process the update
last_zed_token = resp.changes_through
except Exception:
# log exception
continue
If your datastore supports checkpoints, you can also request them.
This will help keep the stream alive during periods of inactivity, which is helpful if your SpiceDB instance sits behind a proxy that terminates idle connections.
from authzed.api.v1 import (
Client,
WatchRequest,
)
from authzed.api.v1.watch_service_pb2 import WATCH_KIND_INCLUDE_CHECKPOINTS
from grpcutil import bearer_token_credentials
client = Client(
"localhost:50051",
bearer_token_credentials("your-token-here"),
)
last_zed_token = None
while not_canceled:
try:
watcher = client.Watch(WatchRequest(
optional_start_cursor=last_zed_token,
optional_update_kinds=[WATCH_KIND_INCLUDE_CHECKPOINTS]
))
for resp in watcher:
# process the update
last_zed_token = resp.changes_through
except Exception:
# log exception
continue
Transaction Metadata
SpiceDB's WriteRelationships (opens in a new tab) and DeleteRelationships (opens in a new tab) APIs support an optional metadata block called the Transaction Metadata (opens in a new tab).
When optional_transaction_metadata
is specified on the WriteRelationships (opens in a new tab) or DeleteRelationships (opens in a new tab) request, it will be stored and returned alongside the relationships in the Watch API:
from authzed.api.v1 import (
Client, WatchRequest
)
from grpcutil import bearer_token_credentials
client = Client(
"localhost:50051",
bearer_token_credentials("your-token-here"),
)
client.WriteRelationships(WriteRelationshipsRequest(
updates=[
RelationshipUpdate(
operation=RelationshipUpdate.Operation.OPERATION_CREATE,
relationship=Relationship(
resource=ObjectReference(object_type="document", object_id="somedoc"),
relation="viewer",
subject=SubjectReference(
object=ObjectReference(
object_type="user",
object_id="tom",
)
),
),
),
],
optional_transaction_metadata=Struct({"request_id": "12345"}),
})
...
WatchResponse{
Updates: [
{ Relationship: "document:somedoc#viewer@user:tom" }
],
OptionalTransactionMetadata: {
"request_id": "12345"
}
}
This allows callers to correlate write operations and the updates that come from the Watch API.