CQRS Pattern Implementation
This diagram shows how BlueRobin implements the CQRS (Command Query Responsibility Segregation) pattern, separating write operations from read-optimized queries.
CQRS Overview
flowchart TB
subgraph Client["Client Layer"]
User["User Interface
(Blazor)"]
end
subgraph Commands["Command Side (Write)"]
direction TB
CmdAPI["Archives API
Write Endpoints"]
CmdHandler["Command Handlers
(FastEndpoints)"]
Domain["Domain Model
(Aggregates)"]
WriteDB[(PostgreSQL
Write Model)]
Events{{"NATS JetStream
Domain Events"}}
end
subgraph Queries["Query Side (Read)"]
direction TB
QryAPI["Archives API
Read Endpoints"]
QryHandler["Query Handlers
(FastEndpoints)"]
ReadDB[(PostgreSQL
Read Views)]
VectorDB[(Qdrant
Semantic Search)]
GraphDB[(FalkorDB
Relationships)]
end
subgraph Sync["Synchronization"]
Projections["Event Projections
(Workers)"]
end
User -->|Commands| CmdAPI
User -->|Queries| QryAPI
CmdAPI --> CmdHandler
CmdHandler --> Domain
Domain --> WriteDB
Domain -.->|Publish| Events
QryAPI --> QryHandler
QryHandler --> ReadDB
QryHandler --> VectorDB
QryHandler --> GraphDB
Events -.->|Subscribe| Projections
Projections --> ReadDB
Projections --> VectorDB
Projections --> GraphDB
style Commands fill:#f0dbd8
style Queries fill:#d5eef0
style Sync fill:#faf2d0
Command Flow Detail
sequenceDiagram
autonumber
participant UI as Blazor UI
participant API as Archives API
participant Endpoint as UploadDocumentEndpoint
participant Service as DocumentService
participant Domain as Document Aggregate
participant DB as PostgreSQL
participant NATS as NATS JetStream
UI->>API: POST /api/documents
{file, metadata}
API->>Endpoint: Route to handler
rect rgb(248, 237, 237)
Note over Endpoint,Domain: Command Processing
Endpoint->>Service: UploadAsync(request)
Service->>Domain: Document.Create(...)
Domain->>Domain: Validate invariants
Domain->>Domain: RaiseDomainEvent(DocumentAdded)
end
rect rgb(237, 245, 246)
Note over Service,NATS: Persistence & Event Publishing
Service->>DB: SaveChangesAsync()
DB-->>Service: Committed
Service->>NATS: Publish: documents.uploaded
end
Service-->>Endpoint: DocumentId
Endpoint-->>API: 201 Created
API-->>UI: Response with ID
Query Flow Detail
sequenceDiagram
autonumber
participant UI as Blazor UI
participant API as Archives API
participant Endpoint as SearchDocumentsEndpoint
participant Service as RagService
participant Qdrant as Qdrant
(Read Model)
participant PG as PostgreSQL
(Metadata)
participant MinIO as MinIO
(Content)
UI->>API: POST /api/rag/search
{query, filters}
API->>Endpoint: Route to handler
rect rgb(237, 245, 246)
Note over Endpoint,MinIO: Query Processing (No DB Writes)
Endpoint->>Service: SearchAsync(query)
Service->>Service: Generate query embedding
Service->>Qdrant: Vector similarity search
Qdrant-->>Service: Matching chunk IDs + scores
Service->>PG: Get document metadata
(read-only view)
PG-->>Service: Document details
Service->>MinIO: Fetch content snippets
MinIO-->>Service: Text content
end
Service-->>Endpoint: SearchResults
Endpoint-->>API: 200 OK
API-->>UI: Results with snippets
Event Projection Pipeline
flowchart LR
subgraph Source["Event Source"]
E1["DocumentAdded"]
E2["AnalysisCompleted"]
E3["EmbeddingsCompleted"]
E4["EntitiesExtracted"]
end
subgraph Workers["Projection Workers"]
P1["DbUpdateEventConsumer
Updates PostgreSQL views"]
P2["EmbeddingAggregator
Updates Qdrant"]
P3["GraphSyncConsumer
Updates FalkorDB"]
end
subgraph ReadModels["Read Models"]
RM1[(PostgreSQL
Document views)]
RM2[(Qdrant
Vector index)]
RM3[(FalkorDB
Entity graph)]
end
E1 & E2 --> P1
E3 --> P2
E4 --> P3
P1 --> RM1
P2 --> RM2
P3 --> RM3
style Source fill:#f0dbd8
style Workers fill:#faf2d0
style ReadModels fill:#d5eef0
Read Model Optimization
flowchart TB
subgraph Queries["Query Types"]
Q1["List documents
(paginated)"]
Q2["Full-text search
(keywords)"]
Q3["Semantic search
(meaning)"]
Q4["Entity lookup
(relationships)"]
Q5["RAG query
(Q&A)"]
end
subgraph Optimized["Optimized Read Stores"]
DB1[(PostgreSQL
B-tree indexes
GIN for arrays)]
DB2[(PostgreSQL
tsvector FTS)]
DB3[(Qdrant
HNSW index)]
DB4[(FalkorDB
Graph traversal)]
end
Q1 --> DB1
Q2 --> DB2
Q3 --> DB3
Q4 --> DB4
Q5 --> DB3
Q5 --> DB1
style Queries fill:#eee9f5
style Optimized fill:#edf5f6
CQRS Benefits in BlueRobin
| Benefit |
Implementation |
Impact |
| Scalability |
Separate read/write DBs |
Scale reads independently |
| Performance |
Denormalized views |
Fast queries, no joins |
| Flexibility |
Multiple read models |
Qdrant + FalkorDB + PG |
| Consistency |
Eventual via events |
Acceptable for search |
| Auditability |
Event log in NATS |
Full history available |
Commands vs Queries
mindmap
root((CQRS))
Commands
POST /api/documents
Upload file
Create metadata
Publish event
PUT /api/documents/{id}
Update analysis
Update status
DELETE /api/documents/{id}
Mark deleted
Publish event
Queries
GET /api/documents
List with filters
Paginated results
POST /api/rag/search
Vector similarity
Score ranking
POST /api/rag/ask
Context retrieval
LLM generation
GET /api/graph/entities
Graph traversal
Relationship lookup
Consistency Model
| Operation |
Consistency |
Latency |
Use Case |
| Create document |
Strong |
~100ms |
Write to PG |
| Update metadata |
Strong |
~50ms |
Write to PG |
| Search by keyword |
Eventual |
~200ms |
PG FTS index |
| Semantic search |
Eventual |
~500ms |
Qdrant after embedding |
| Graph query |
Eventual |
~300ms |
FalkorDB after sync |
| RAG response |
Eventual |
~2s |
Full pipeline |