Skip to main content

Architecture Overview

Understanding how PearDrop applications are structured.

High-Level Architecture

┌─────────────────────────────────────────────────────────┐
│ Blazor WebAssembly Client (App.Client) │
│ - Interactive components │
│ - Send Commands via ICommandRunner │
│ - Execute Queries via IQueryRunner │
└──────────────────────┬──────────────────────────────────┘
│ HTTPS
│ BluQube API Contracts

┌─────────────────────────────────────────────────────────┐
│ Server Application (App) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Write Path (Commands) │ │
│ │ ─────────────────────────────────────────────── │ │
│ │ 1. Validate input (FluentValidation) │ │
│ │ 2. Load aggregate from repository │ │
│ │ 3. Execute business logic │ │
│ │ 4. Raise domain events (immediate) │ │
│ │ 5. Save changes → AppDbContext │ │
│ │ 6. Publish integration events (async, CAP) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Read Path (Queries) │ │
│ │ ─────────────────────────────────────────────── │ │
│ │ 1. Fetch from read models (AppReadModels) │ │
│ │ 2. Apply filters and pagination │ │
│ │ 3. Return projection DTOs │ │
│ │ (No domain logic, optimized for reads) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└──────────────┬───────────────────────────────────────┬─┘
│ │
Write Model Read Model
AppDbContext AppReadDbContext
│ │
└───────────────→┌─────────────────────┘
SQL Server
(Single DB)

Key Architectural Concepts

Command Query Responsibility Segregation (CQRS)

PearDrop strictly separates writes from reads:

Write Side (Commands):

  • Domain aggregates with business logic
  • Command handlers that validate and execute
  • Single database transaction per command
  • Change tracking enabled

Read Side (Queries):

  • Projection models optimized for reading
  • Read-only DbContext mapped to database views
  • No domain logic (pure projection)
  • High-performance queries

Benefits:

  • ✅ Write logic stays in aggregates (business rules protected)
  • ✅ Read queries are simple and fast (denormalized)
  • ✅ Scalable - can optimize each side independently
  • ✅ Testable - tests don't cross concerns

Domain-Driven Design (DDD)

PearDrop is structured around DDD patterns:

Aggregates

  • Domain entities that protect business invariants
  • Example: EquipmentAggregate manages equipment and its state
  • Encapsulate all business rules related to that entity

Bounded Contexts

  • Groups of related aggregates organized in folders
  • Example: EquipmentAggregate/ is a context folder
  • Clear boundaries prevent spaghetti code

Commands & Queries

  • Commands represent intent: "CreateEquipment", "UpdateEquipment"
  • Queries represent requests for data: "GetEquipmentById", "ListEquipment"
  • Handlers execute the logic

Layered Architecture

┌──────────────────────────────┐
│ Presentation (Components) │ ← User interactions
│ ├─ Blazor Components │
│ └─ API Controllers │
├──────────────────────────────┤
│ Application (Handlers) │ ← Use cases
│ ├─ Command Handlers │
│ ├─ Query Handlers │
│ └─ Validators │
├──────────────────────────────┤
│ Domain (Aggregates) │ ← Business logic
│ ├─ Aggregates │
│ ├─ Value Objects │
│ ├─ Domain Events │
│ └─ Specifications │
├──────────────────────────────┤
│ Infrastructure (Data Access)│ ← Technical details
│ ├─ DbContexts │
│ ├─ Repositories │
│ ├─ External Services │
│ └─ Persistence │
└──────────────────────────────┘

Dependency Rule:

  • Inner layers don't depend on outer layers
  • Aggregates (Domain) → Commands/Queries (Application)
  • Nothing depends directly on Infrastructure

Project Organization

YourProject.App/                    ASP.NET Core Server (API only)
├─ Infrastructure/
│ ├─ Domain/ Business logic (aggregates, commands)
│ ├─ Queries/ Read-side handlers
│ └─ Data/ Database contexts
└─ Program.cs

YourProject.App.Client/ Blazor WebAssembly Client
├─ Infrastructure/
│ └─ Module.cs [BluQubeRequester] services
├─ Components/ Blazor components (Pages, Layouts, Shared)
└─ Program.cs

Data Flow: A Complete Example

Here's how a Create Note command flows through the system:

1. User Interaction

// Component sends command
var result = await commandRunner.Send(
new CreateNoteCommand("My Note", "Content here"));

2. Server Receives Command

Command → BluQube API endpoint → ICommandRunner.Send()

3. Command Handler Executes

public class CreateNoteCommandHandler : AuditableCommandHandler<CreateNoteCommand, Guid>
{
// 1. Validate (FluentValidation)
// 2. Create aggregate
var note = new NoteAggregate(Guid.NewGuid(), request.Title, request.Content, userId);

// 3. Add to repository
repository.Add(note);

// 4. Save to WriteModel DbContext
repository.UnitOfWork.SaveEntitiesAsync();

// 5. Publish integration events (async)
publisher.PublishAsync(new NoteCreatedIntegrationEvent(...));
}

4. Database Updated

AppDbContext (WriteModel)
└─ Note table updated

5. Client Displays Data

// Component queries for new note
var result = await queryRunner.Send(
new GetNoteByIdQuery(noteId));

// Handler fetches from read model
IAppReadModels.Notes.Where(n => n.Id == noteId).ToListAsync();

6. Response to Client

Query Handler → DTO → Client → Component displays

Technology Stack

  • Framework: .NET 10
  • Frontend: Blazor WebAssembly
  • Database: SQL Server (EF Core)
  • CQRS: BluQube (source generators)
  • Validation: FluentValidation
  • Messaging: DotNetCore.CAP (integration events)
  • Authorization: MediatR.Behaviors.Authorization
  • Testing: xUnit, Fixture builders

Key Principles

  1. Single Responsibility - Aggregates handle business logic, handlers orchestrate
  2. Domain-Centric - Business rules live in domain, not in handlers or components
  3. CQRS Separation - Writes and reads are completely separate concerns
  4. Immutable Read Models - Queries never modify data
  5. Event-Driven - Domain events for consistency, integration events for scale
  6. Repository Pattern - Data access abstracted, testable without DB
  7. Lazy Initialization - Read models initialized on-demand for performance

Next Steps