Skip to content

Event System

OctoFHIR uses a unified event system to coordinate actions across modules when resources are created, updated, or deleted. This enables real-time cache invalidation, policy reloads, GraphQL subscriptions, and multi-instance synchronization.

When a FHIR resource is modified through the REST API, GraphQL, or any other endpoint, the system automatically:

  1. Emits a ResourceEvent to the central event bus
  2. Dispatches the event to all registered hooks
  3. Each hook performs its action asynchronously (cache invalidation, reload, etc.)
  4. If Redis is enabled, broadcasts the event to other server instances
┌──────────────────────────────────────────────────────────────┐
│ FHIR CRUD Operation │
│ POST /fhir/Patient → Storage → Event Broadcast │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Event Broadcaster │
│ (tokio::broadcast channel) │
└──────────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Policy │ │Gateway │ │ Search │ │ Audit │
│ Reload │ │ Reload │ │ Param │ │ Log │
└────────┘ └────────┘ └────────┘ └────────┘

OctoFHIR includes several hooks that react to resource changes:

Automatically reloads the policy cache when AccessPolicy resources are modified.

Triggered by: AccessPolicy create/update/delete

Action: Notifies the policy reload service to refresh the in-memory policy cache, ensuring authorization decisions use the latest policies.

Reloads the gateway routing table when App or CustomOperation resources change.

Triggered by: App, CustomOperation create/update/delete

Action: Rebuilds the dynamic API routes from the database, activating new endpoints or removing deleted ones.

Updates the search parameter registry when SearchParameter resources are modified.

Triggered by: SearchParameter create/update/delete

Action: Upserts the parameter into the registry and clears the query cache to ensure new searches use the updated parameter.

Forwards resource events to connected GraphQL subscription clients.

Triggered by: All resource types

Action: Broadcasts the change to clients subscribed to that resource type, enabling real-time UI updates.

Logs resource changes as FHIR AuditEvent resources asynchronously.

Triggered by: All resource types (filtered by audit configuration)

Action: Creates an AuditEvent recording the action, actor, and outcome without blocking the API response.

For horizontal scaling with multiple OctoFHIR instances, enable Redis to synchronize events across all instances.

[redis]
enabled = true
url = "redis://localhost:6379"
pool_size = 10
timeout_ms = 5000
┌─────────────────────────┐ ┌─────────────────────────┐
│ Instance 1 │ │ Instance 2 │
│ │ │ │
│ POST /fhir/Patient │ │ │
│ │ │ │ │
│ ▼ │ │ │
│ EventBroadcaster ──────┼────────►│ EventBroadcaster │
│ │ │ Redis │ │ │
│ ▼ │ Pub/Sub │ ▼ │
│ Local Hooks │ │ Local Hooks │
│ (Policy, Gateway...) │ │ (Policy, Gateway...) │
└─────────────────────────┘ └─────────────────────────┘

When Redis is enabled:

  1. RedisPublishHook publishes events to the octofhir:resource_events channel
  2. RedisEventSync subscribes to the channel on each instance
  3. Received events are forwarded to the local broadcaster
  4. Local hooks process the event (cache invalidation, etc.)

This ensures all instances have consistent caches without manual coordination.

Emitted when a FHIR resource is created, updated, or deleted:

FieldTypeDescription
event_typeCreated | Updated | DeletedType of change
resource_typeStringFHIR resource type (e.g., “Patient”)
resource_idStringResource ID
version_idOption<i64>Version number (if available)
resourceOption<Value>Full resource JSON (None for deletes)
timestampOffsetDateTimeWhen the event occurred

Emitted for authentication-related actions:

FieldTypeDescription
event_typeSessionCreated | LoginSucceeded | TokenRevoked | …Type of auth event
user_idOption<String>User ID (if applicable)
client_idStringOAuth client ID
session_idOption<String>Session ID
ip_addressOption<IpAddr>Client IP address
timestampOffsetDateTimeWhen the event occurred

Each hook runs in isolation to prevent failures from affecting other hooks or the API:

  • Timeout: 30 seconds maximum execution time
  • Panic Recovery: Panics are caught and logged
  • Error Isolation: Errors in one hook don’t affect others
  • Async Execution: Hooks run concurrently
Hook A: ✓ Success
Hook B: ✗ Error (logged, other hooks continue)
Hook C: ✓ Success
Hook D: ⏱ Timeout (logged, other hooks continue)
API Response: Success (hooks don't block response)

The event system is designed for minimal impact on API latency:

OperationLatencyNotes
Event broadcast~1-5 μsNon-blocking, returns immediately
Hook dispatch~10-100 μsPlus async execution
Redis publish~1-5 msNetwork round-trip
Total API impact< 10 μsHooks run after response

Events are emitted after the storage operation completes but before sending the API response. Hook execution happens asynchronously, so it doesn’t add latency to the response.

Enable debug logging to see event activity:

Terminal window
RUST_LOG=octofhir_core::events=debug,octofhir_server::hooks=debug cargo run

Example logs:

DEBUG octofhir_storage::evented: Emitting Created event for Patient/123
DEBUG octofhir_server::hooks::policy: Handling AccessPolicy change
INFO octofhir_server::hooks::gateway: Gateway routes reloaded
DEBUG octofhir_server::events::redis: Published event to Redis
  • Redis is optional for single-instance deployments
  • All hooks work locally without Redis
  • Consider enabling audit logging for compliance
  • Always enable Redis for cache consistency
  • Use the same Redis instance for all OctoFHIR servers
  • Monitor Redis connectivity (graceful degradation on failure)
  • Consider Redis Sentinel or Cluster for high availability

When building applications that modify resources directly via the database:

  1. Use the OctoFHIR REST API instead of direct database writes
  2. This ensures events are emitted and all hooks are triggered
  3. Direct database changes bypass the event system
  1. Check that resources are modified via the API (not direct DB writes)
  2. Verify hooks are registered (check startup logs)
  3. Enable debug logging for event tracing
  1. Verify Redis connectivity on all instances
  2. Check redis.enabled = true in configuration
  3. Monitor Redis pub/sub with redis-cli SUBSCRIBE octofhir:resource_events
  4. Check for Redis connection errors in logs
  1. Check logs for hook error messages
  2. Verify required services are running (e.g., PostgreSQL for gateway reload)
  3. Hook failures are isolated and don’t affect other hooks
  • Webhook notifications - HTTP callbacks for external systems
  • Event replay - Replay events from a point in time
  • Event filtering - Subscribe to specific resource types only
  • Metrics - Prometheus metrics for event throughput and latency