Architecture Deployment API

Cross-cutting Concerns

Middleware Pipeline

Every HTTP request passes through these middleware layers in order:

flowchart TD
  A[HTTP Request] --> B[GlobalExceptionMiddleware\nCatches unhandled exceptions → ProblemDetails]
  B --> C[RequestIdMiddleware\nAdds X-Request-ID header]
  C --> D[LocalisationMiddleware\nSets Accept-Language culture]
  D --> E[Authentication\nJWT validation]
  E --> F[TenantResolutionMiddleware\nExtracts vendor_id / customer_id from JWT claims]
  F --> G[TokenBlacklistMiddleware\nRejects revoked JTI tokens]
  G --> H[Authorization\nPolicy enforcement]
  H --> I[RateLimiter]
  I --> J[IdempotencyMiddleware\nChecks Idempotency-Key header → Redis]
  J -->|duplicate request| Z1[Return cached response]
  J -->|new request| K[ETagMiddleware\nSets ETag / handles If-None-Match]
  K --> L[Controller]

Caching Strategy

flowchart TD
  A[Query via MediatR] --> B{Implements ICacheableQuery?}
  B -->|No| C[Execute handler directly]
  B -->|Yes| D[CachingBehaviour checks Redis]
  D -->|Hit| E[Return cached result]
  D -->|Miss| F[Execute handler]
  F --> G[Store result in Redis with TTL + version key]
  G --> E

  subgraph Cache Invalidation
    H[Command handler runs] --> I[Increment version key in Redis]
    I --> J[Next read: version mismatch → cache miss]
  end

  subgraph Special Cases
    K[Geocoding: CachingGeocodingService uses IMemoryCache in-process]
    L[Idempotency keys: IdempotencyMiddleware uses Redis directly]
  end

Multi-tenancy

flowchart TD
  A[JWT Token] --> B[TenantResolutionMiddleware]
  B --> C{Role claim}
  C -->|VendorAdmin| D[SetTenantContext\nvendor_id + vendor_type + user_id]
  C -->|User/Customer| E[No vendor claims set\nCustomerAccess policy enforced downstream]
  C -->|PlatformAdmin| F[SetPlatformAdminContext\nvia dynamic dispatch]
  D --> G[ITenantContextService]
  E --> G
  F --> G
  G --> H[TenantValidationBehavior in MediatR pipeline]
  H --> I[Handler: GetTenantContext\nScopes all DB queries by VendorId + VendorType]

Idempotency

sequenceDiagram
  participant Client
  participant API as IdempotencyMiddleware
  participant Redis

  Client->>API: POST /orders Idempotency-Key: abc123
  API->>Redis: GET idempotency:abc123
  alt Key not found
    Redis-->>API: null
    API->>API: Process request
    API->>Redis: SET idempotency:abc123 {status, body} TTL=24h
    API-->>Client: 201 Created
  else Key found
    Redis-->>API: Cached response
    API-->>Client: 201 Created (replayed — no duplicate order)
  end

Strongly-Typed IDs

All domain identifiers use strongly-typed value objects to prevent ID confusion across aggregates:

Type Wraps Used in
VendorId Guid Vendor, SmartVase, Bouquet, Employee
BouquetId Guid Bouquet, OrderLine, Favourite, Cart
CustomerId Guid Customer, Order, Favourite, Subscription
OrderId Guid Order, OrderLine
VaseId Guid SmartVase, Bouquet
UserId Guid User