Event-Driven Architecture¶
Overview¶
VitalBridge uses an event-driven architecture to coordinate business operations across independently deployed microservices.
Instead of directly modifying data owned by other services, services communicate through domain events published to Apache Kafka.
This architecture provides:
- Loose coupling
- Independent deployments
- Fault isolation
- Horizontal scalability
- Eventual consistency
- Reliable event delivery
Why Event-Driven Architecture?¶
Traditional distributed systems often rely on synchronous service-to-service communication.
flowchart LR
A["Service A"]
B["Service B"]
C["Service C"]
A --> B
B --> C
This approach creates:
- Tight coupling
- Cascading failures
- Deployment dependencies
- Increased latency
VitalBridge instead uses asynchronous events.
flowchart LR
A["Service A"]
K["Apache Kafka"]
B["Service B"]
C["Service C"]
A -->|"Publish Event"| K
K -->|"Consume Event"| B
K -->|"Consume Event"| C
Services remain independent while still reacting to business events.
Core Principle¶
A service owns its own data.
flowchart LR
DOC["Doctor Service"]
DOC_DB[("Doctor DB")]
APPT["Appointment Service"]
APPT_DB[("Appointment DB")]
DOC --> DOC_DB
APPT --> APPT_DB
DOC_DB -.-x APPT_DB
No service may:
- Write to another service's database
- Share tables
- Share repositories
- Share schemas
Cross-service communication must occur through events.
Event Flow¶
A typical event flow looks like this:
flowchart LR
CMD["Business Command"]
SERVICE["Owning Service"]
OUTBOX["Outbox Event"]
KAFKA["Apache Kafka"]
CONSUMER["Consumer Service"]
CMD --> SERVICE
SERVICE --> OUTBOX
OUTBOX --> KAFKA
KAFKA --> CONSUMER
The producer service publishes an event.
One or more consumer services react independently.
Example: Tenant Creation¶
When a Super Administrator creates a tenant:
flowchart LR
SA["Super Administrator"]
TR["Tenant Registry Service"]
K["Apache Kafka"]
ADM["Admin Service"]
ID["Identity Service"]
SA -->|"Create Tenant"| TR
TR -->|"Tenant Created Event"| K
K --> ADM
K --> ID
The Tenant Registry Service remains unaware of how downstream services process the event.
Example: Appointment Booking¶
flowchart LR
PAT["Patient"]
APPT["Appointment Service"]
K["Apache Kafka"]
SCH["Doctor Schedule Service"]
COMM["Communication Engine"]
AUDIT["Audit Log Service"]
PAT --> APPT
APPT -->|"Appointment Confirmed"| K
K --> SCH
K --> COMM
K --> AUDIT
A single business event may trigger multiple workflows.
Transactional Outbox Pattern¶
VitalBridge uses the Transactional Outbox pattern for reliable event delivery.
The Problem¶
Publishing directly to Kafka inside a database transaction can lead to inconsistencies.
flowchart LR
DB["Database Commit"]
K["Kafka Publish"]
DB --> K
If Kafka fails after the database commit succeeds:
- Data is stored
- Event is lost
This creates inconsistent state.
The Solution¶
VitalBridge writes business data and event records atomically.
flowchart LR
CMD["Command"]
TX["Database Transaction"]
DATA["Business Data"]
OUTBOX["Outbox Event"]
CMD --> TX
TX --> DATA
TX --> OUTBOX
Both records are committed together.
Event Publication¶
A background publisher processes outbox records.
flowchart LR
OUTBOX["Outbox Table"]
PUBLISHER["Outbox Publisher"]
KAFKA["Apache Kafka"]
OUTBOX --> PUBLISHER
PUBLISHER --> KAFKA
This guarantees:
- No lost events
- Reliable delivery
- Retry safety
CQRS Communication Model¶
VitalBridge distinguishes between commands and queries.
Commands¶
Commands change state.
flowchart LR
CLIENT["Client"]
SERVICE["Service"]
K["Kafka Event"]
CLIENT -->|"Command"| SERVICE
SERVICE -->|"Domain Event"| K
Examples:
- Create Tenant
- Add Provider
- Add Patient
- Create Appointment
- Cancel Appointment
Queries¶
Queries read data.
flowchart LR
CLIENT["Client"]
SERVICE["Query Service"]
CLIENT -->|"Read Request"| SERVICE
Queries never modify state.
Examples:
- List Providers
- List Patients
- Get Availability
- Get Appointments
Event Consumers¶
Consumers must be idempotent.
flowchart LR
K["Kafka Event"]
CONSUMER["Consumer"]
DUP["Duplicate Event"]
K --> CONSUMER
DUP --> CONSUMER
Consumers must safely process duplicate deliveries.
This protects the platform from:
- Retries
- Rebalances
- Network failures
Eventual Consistency¶
Event-driven systems do not provide immediate consistency.
flowchart LR
SERVICE_A["Service A"]
K["Kafka"]
SERVICE_B["Service B"]
SERVICE_A --> K
K --> SERVICE_B
There is a small delay between:
- Event publication
- Event consumption
This behavior is expected and intentional.
Kafka as the Event Backbone¶
Apache Kafka acts as the central event backbone.
flowchart TB
TR["Tenant Registry"]
DOC["Doctor"]
PAT["Patient"]
SCH["Schedule"]
APPT["Appointment"]
VIDEO["Video"]
K["Apache Kafka"]
TR --> K
DOC --> K
PAT --> K
SCH --> K
APPT --> K
VIDEO --> K
Kafka enables:
- Service independence
- Reliable delivery
- Replay capabilities
- Horizontal scalability
Architectural Rules¶
VitalBridge enforces the following event-driven principles:
Allowed¶
- Services publish domain events
- Services consume domain events
- Queries use read-only communication
- Outbox pattern for event publication
Forbidden¶
- Shared databases
- Cross-service writes
- Distributed transactions
- Direct database access between services
- State-changing Kafka RPC
These rules ensure the platform remains scalable, resilient, and maintainable as additional services are introduced.