Skip to content

Command Query Responsibility Segregation (CQRS)

Overview

VitalBridge adopts the Command Query Responsibility Segregation (CQRS) pattern to separate operations that modify state from operations that retrieve data.

CQRS helps maintain clear service boundaries, simplifies business logic, and aligns naturally with the platform's event-driven architecture.

The core principle is simple:

  • Commands change state
  • Queries read state

A request should never do both.


Why CQRS?

Without CQRS, a service often handles reads and writes through the same model.

flowchart LR

    CLIENT["Client"]

    SERVICE["Application Service"]

    DB[("Database")]

    CLIENT --> SERVICE

    SERVICE --> DB
Hold "Alt" / "Option" to enable pan & zoom

As systems grow, read and write concerns evolve differently.

Typical examples include:

Write Operations

  • Create Tenant
  • Add Provider
  • Register Patient
  • Create Appointment
  • Cancel Appointment
  • Submit Leave Request

Read Operations

  • List Providers
  • View Schedule
  • Search Patients
  • View Appointments
  • View Availability

CQRS separates these concerns.


CQRS Model

flowchart TB

    CLIENT["Client Application"]

    COMMANDS["Commands"]

    QUERIES["Queries"]

    CLIENT --> COMMANDS

    CLIENT --> QUERIES
Hold "Alt" / "Option" to enable pan & zoom

Commands and queries have different responsibilities and different rules.


Commands

Commands represent business actions.

Commands always attempt to change state.

Examples:

  • Create Tenant
  • Create Provider
  • Create Patient
  • Create Appointment
  • Cancel Appointment
  • Approve Leave Request

Command Flow

flowchart LR

    CLIENT["Client"]

    COMMAND["Create Appointment"]

    SERVICE["Appointment Service"]

    DB[("Appointment DB")]

    OUTBOX["Outbox Event"]

    CLIENT --> COMMAND

    COMMAND --> SERVICE

    SERVICE --> DB

    SERVICE --> OUTBOX
Hold "Alt" / "Option" to enable pan & zoom

A command:

  • Validates business rules
  • Modifies data
  • Produces domain events

Queries

Queries retrieve information.

Queries never modify state.

Examples:

  • Get Appointment
  • List Patients
  • View Schedule
  • View Availability
  • Search Providers

Query Flow

flowchart LR

    CLIENT["Client"]

    QUERY["Get Available Slots"]

    SERVICE["Schedule Service"]

    DB[("Schedule DB")]

    CLIENT --> QUERY

    QUERY --> SERVICE

    SERVICE --> DB
Hold "Alt" / "Option" to enable pan & zoom

A query:

  • Reads data
  • Returns data
  • Does not publish events
  • Does not modify state

Command Example

Patient books an appointment.

sequenceDiagram

    participant Patient

    participant AppointmentService

    participant AppointmentDB

    participant Outbox

    Patient->>AppointmentService: Create Appointment

    AppointmentService->>AppointmentDB: Save Appointment

    AppointmentService->>Outbox: Create appointment.created

    AppointmentService-->>Patient: Success
Hold "Alt" / "Option" to enable pan & zoom

The command changes business state.


Query Example

Patient views upcoming appointments.

sequenceDiagram

    participant Patient

    participant AppointmentService

    participant AppointmentDB

    Patient->>AppointmentService: Get Upcoming Appointments

    AppointmentService->>AppointmentDB: Read Appointments

    AppointmentDB-->>AppointmentService: Results

    AppointmentService-->>Patient: Appointment List
Hold "Alt" / "Option" to enable pan & zoom

The query only reads information.


CQRS and Events

Commands often produce events.

Queries never produce events.

flowchart LR

    COMMAND["Command"]

    DB[("Database")]

    EVENT["Domain Event"]

    COMMAND --> DB

    DB --> EVENT
Hold "Alt" / "Option" to enable pan & zoom

Examples:

Command Event
Create Tenant tenant.created
Create Provider provider.created
Create Patient patient.created
Create Appointment appointment.created
Cancel Appointment appointment.cancelled

This integrates naturally with VitalBridge's event-driven architecture.


Service Ownership

Commands must always be sent to the service that owns the business domain.

flowchart LR

    CLIENT["Client"]

    APPT["Appointment Service"]

    APPT_DB[("Appointment DB")]

    CLIENT --> APPT

    APPT --> APPT_DB
Hold "Alt" / "Option" to enable pan & zoom

For example:

  • Appointment commands belong to Appointment Service
  • Schedule commands belong to Doctor Schedule Service
  • Patient commands belong to Patient Service

Services may not execute commands against another service's database.


Cross-Service Queries

Sometimes a service requires information owned by another service.

flowchart LR

    APPT["Appointment Service"]

    DOC["Doctor Service"]

    APPT -->|"Query"| DOC
Hold "Alt" / "Option" to enable pan & zoom

In VitalBridge:

  • Commands are asynchronous through events
  • Queries are synchronous through request/response communication

This preserves ownership boundaries while allowing services to retrieve information when needed.


What CQRS Is Not

CQRS is often confused with Event Sourcing.

VitalBridge does not require:

  • Event Sourcing
  • Event replay as the source of truth
  • Separate read databases
  • Separate deployment units for reads and writes

The platform simply separates:

  • State-changing operations
  • Read-only operations

This lightweight interpretation provides most of the benefits without unnecessary complexity.


Benefits

CQRS provides several advantages:

Clear Responsibility

Every request is either:

  • A command
  • A query

Never both.

Better Maintainability

Business logic remains focused and easier to reason about.

Better Scalability

Read and write workloads can evolve independently.

Natural Event Integration

Commands produce domain events that integrate seamlessly with the event-driven architecture.

Strong Service Boundaries

Only owning services may execute business commands.


Relationship to Other Architectural Patterns

CQRS works alongside several other platform patterns.

flowchart TB

    CQRS["CQRS"]

    OUTBOX["Transactional Outbox"]

    EVENTS["Event-Driven Architecture"]

    TENANT["Multi-Tenancy"]

    CQRS --> OUTBOX

    OUTBOX --> EVENTS

    EVENTS --> TENANT
Hold "Alt" / "Option" to enable pan & zoom

Together these patterns form the foundation of the VitalBridge architecture.


Architectural Rules

VitalBridge enforces the following CQRS rules:

Commands

  • Change state
  • Validate business rules
  • Generate events
  • Execute only within owning services

Queries

  • Read state
  • Never change data
  • Never publish events
  • May retrieve information from other services

Forbidden

  • Commands that return complex read models
  • Queries that modify state
  • Cross-service database writes
  • Mixed command/query operations

Following these rules keeps the platform predictable, scalable, and aligned with its event-driven architecture.