Architecture
DPG has four main layers:
- network contract
- instance runtime
- storage and cache
- client-facing APIs
graph TB
subgraph Client["Client Layer"]
UI[UI App]
SDK[API Client]
end
subgraph Instance["DPG Instance"]
direction TB
API[Fastify API]
Auth[Auth Middleware]
ItemRoutes[Item Routes]
ActionRoutes[Action / Event Routes]
NetworkRoutes[Network Fetch Routes]
end
subgraph Storage["Storage Layer"]
PG[(PostgreSQL\nItems · Actions · Events)]
Redis[(Redis\nFetch Cache · Count Cache)]
Disk[(Disk\nSchema Cache)]
end
subgraph Peers["Peer Instances"]
Peer1[DPG Instance B]
Peer2[DPG Instance C]
end
UI --> API
SDK --> API
API --> Auth
Auth --> ItemRoutes & ActionRoutes & NetworkRoutes
ItemRoutes --> PG & Redis
ActionRoutes --> PG
NetworkRoutes --> Redis
NetworkRoutes -->|inter-instance fetch| Peer1 & Peer2
API --> Disk
1. Network Contract
Section titled “1. Network Contract”The network config is the source of truth for:
- domains
- item schema identifiers
- registered instances
- action permissions
- action request schemas
- action event schemas
- minimum cache TTL for inter-instance fetches
2. Instance Runtime
Section titled “2. Instance Runtime”Each API instance loads:
- environment variables
- served domain bindings
- one or more network configs
That runtime state determines:
- which requests the instance may serve
- which schemas are valid
- which peer instances should be contacted
- which origins should be allowed through CORS
3. Storage And Cache
Section titled “3. Storage And Cache”DPG uses:
- PostgreSQL for durable item, action, and event storage
- Redis for fetch caching and inter-instance count caching
- disk cache for fetched network and custom item schemas
The split is deliberate:
- PostgreSQL stores facts
- Redis stores short-lived query results
- disk cache stores schema documents that change infrequently
4. Client-Facing APIs
Section titled “4. Client-Facing APIs”DPG exposes three broad route groups:
- item APIs
- action and event APIs
- network APIs
- Better Auth APIs mounted under
/api/auth/*
Request Flows
Section titled “Request Flows”Item creation
Section titled “Item creation”- client sends
network,domain,item_type, anditem_state - backend checks whether it serves that binding
- backend checks whether the item type is defined by the network schema
- backend resolves the schema to validate against
- backend validates
item_state - backend generates
item_instance_urlanditem_schema_url - backend stores the item
Local item fetch
Section titled “Local item fetch”- client calls
GET /api/v1/item/fetch - backend queries only its own database
- result is cached in Redis for a very short TTL
- response is returned
Inter-instance fetch
Section titled “Inter-instance fetch”- client calls
GET /api/v1/network/item/fetch - aggregator finds all registered instances for the requested domain
- aggregator calls
count_localon each instance - zero-result instances are excluded
- a page plan is built from the counts
- aggregator calls
fetch_localonly on contributing instances - results are merged and cached in Redis
Cross-instance action
Section titled “Cross-instance action”- client calls
POST /api/v1/action/performon the source item instance - source instance validates that it serves the source domain
- source instance validates the target instance against the target network config
- source instance forwards the action to
POST /api/v1/network/action/performon the target instance - target instance validates
requirements_snapshotagainst the action interaction schema - target instance stores the action and an initial event
- target instance mirrors the event back to the source instance when the source lives elsewhere
Action status update
Section titled “Action status update”- target-side user calls
POST /api/v1/action/update-status - target instance increments
update_count - target instance stores a new action event
- event payload is validated against
event_schemawhen configured - the event is mirrored to the source instance
Why DPG Splits Local And Network Fetch
Section titled “Why DPG Splits Local And Network Fetch”This split keeps two very different use cases clean:
- “show me this instance’s items”
- “show me the network-wide view for this domain”
Local fetch should stay simple and cheap.
Network fetch should own:
- peer discovery
- count phase
- pagination planning
- cross-instance caching