Skip to content

System Architecture

SchemaStack is a hybrid Quarkus + Spring Boot platform. The split exists because Hibernate Reactive (used in Quarkus for non-blocking I/O) cannot do programmatic DDL generation — Spring Boot with classic Hibernate handles that part.

Deployment Model

The platform is designed for two deployment targets:

  • AWS Lambda — stateless REST APIs (metadata-rest, organisation-rest). Cold start optimized via Quarkus native builds.
  • AWS Fargate — long-running services (consumers, producers, SSE endpoints, Spring Boot processor). These maintain connections and state.

INFO

Infrastructure-as-code (CDK/SAM) is not in this repository. The codebase is Lambda-capable (quarkus-amazon-lambda-http dependency) but deployment automation lives separately.

Service Architecture

┌─────────────────────────────────────────────────────────┐
│                      Clients                            │
│            (Browser / API consumers)                    │
└──────────┬─────────────────────────────┬────────────────┘
           │ REST                        │ SSE
           ▼                             ▼
┌──────────────────┐          ┌──────────────────┐
│  Quarkus REST    │          │  Quarkus SSE     │
│  (Lambda)        │          │  (Fargate)       │
│  - metadata-rest │          │  - metadata-sse  │
│  - org-rest      │          │  - org-sse       │
└────────┬─────────┘          └────────▲─────────┘
         │                             │
         │ RabbitMQ                     │ RabbitMQ broadcast
         ▼                             │
┌──────────────────┐          ┌──────────────────┐
│  Quarkus         │          │  Quarkus         │
│  Consumers       │──────────│  Producers       │
│  (Fargate)       │          │  (Fargate)       │
└────────┬─────────┘          └──────────────────┘
         │ RabbitMQ

┌──────────────────────────────────────┐
│  Spring Boot Services (Fargate)      │
│  - processor    (DDL/Flyway)         │
│  - workspace-api (dynamic CRUD)      │
│  - session      (session factory)    │
└────────┬─────────────────────────────┘


┌──────────────────┐
│  PostgreSQL      │
│  - dynamicdb     │
│  - workspace DBs │
└──────────────────┘

Messaging

All inter-service communication uses RabbitMQ (not SQS/SNS).

Transactional Outbox Pattern

Messages are not sent directly to RabbitMQ from within business transactions. Instead, they're persisted to the outbox_message table as part of the same database transaction, then published asynchronously by a poller.

Business Transaction

    ├── 1. DB writes (entity changes)
    ├── 2. OutboxService.queueMessage() → inserts OutboxMessage (PENDING)
    └── COMMIT

OutboxPoller (every 5s)
    ├── 3. Polls PENDING messages (batch of 100)
    ├── 4. Marks PROCESSING
    ├── 5. basicPublish() to RabbitMQ
    └── 6. Marks SENT (or FAILED after 5 retries)

OutboxMessage fields: messageId (UUID, dedup key), aggregateType + aggregateId, exchange, routingKey, payload (JSONB), status, retryCount.

OutboxPoller scheduled jobs:

  • Every 5s: poll and publish (batch of 100, max 5 retries per message)
  • Every 1m: reset stuck messages (PROCESSING > 5 minutes → back to PENDING)
  • Every 1h: cleanup old SENT messages (> 7 days)

Currently used for sync operation completions and task completion notifications via OutboxService.queueSyncCompletionMessage() and queueTaskCompletionMessage().

MessageBus (Idempotency + Audit)

For direct RabbitMQ publishing (non-outbox), the MessageBus provides a middleware chain inspired by Symfony Messenger:

  1. Idempotency check — queries message_audit table; skips if already COMPLETED or PROCESSING
  2. Audit record — persists a MessageAudit with PENDING status, full JSON payload, and optional metadata (workspaceId, entityType, entityId, operation)
  3. Send with retry — publishes via SmallRye emitter with up to 3 retries
  4. Audit update — marks COMPLETED on success, FAILED → DEAD_LETTER on exhausted retries

MessageAudit statuses: PENDINGPROCESSINGCOMPLETED | FAILED | DEAD_LETTER.

Exchanges

Key exchanges:

ExchangeTypeFlow
metadata-updatesdirectQuarkus REST → Spring Boot processor
metadata-updates-retrydirectProcessor retry with TTL
metadata-updates-dlxdirectProcessor dead letter exchange
task-completiondirectProcessor → Quarkus consumers
task-completion-retrydirectConsumer retry with TTL
task-completion-dlxdirectConsumer dead letter exchange
workspace-eventsdirectWorkspace event broadcasts
bulk-actionsdirectBulk operation events
sync-operationdirectSchema sync operations
email-exchangedirectEmail sending
member-events-exchangedirectOrganisation member change events

Consumers use single-active-consumer pattern with RabbitMQ broadcast mode for fan-out to SSE producers.

Infrastructure Dependencies

DependencyUsage
PostgreSQLPrimary database. dynamicdb for metadata/org data; per-workspace schemas created by processor
RabbitMQAll async messaging. Durable queues, dead-letter exchanges, retry with TTL
S3 / S3-compatibleFile storage (uploads, exports, mapped files). Per-workspace config with encrypted credentials. Required in production — local filesystem is dev-only

Not used (despite some docs mentioning it)

Redis is not in the codebase.

Resilience

The Spring Boot processor has Resilience4j as a dependency with configuration for:

  • Circuit breakersmigrationService (50% failure threshold, 10-call window) and workspaceConnection (50%, 5-call window)
  • Retry — max 3 attempts with exponential backoff for both services
  • Rate limitermigrationService at 10 calls/second

Configuration in processor-service/src/main/resources/application.properties.

WARNING

The Resilience4j dependency and configuration exist, but @CircuitBreaker, @Retry, and @RateLimiter annotations are not yet applied to service methods. The protection is configured but not active.

SSE (Server-Sent Events)

Real-time updates use SSE endpoints in Quarkus:

  • metadata-sse — task completion events, workspace data changes
  • organisation-sse — org/member change events

SSE resources maintain a ConcurrentMap<String, Set<SseEventSink>> for active connections, filtered by workspace. Events flow from RabbitMQ consumer → broadcast → SSE endpoint → browser.

Email System

Email delivery is asynchronous via RabbitMQ. Three email types are supported:

TypeExpiryTrigger
VERIFICATION24 hoursUser registration
PASSWORD_RESET1 hourForgot password
INVITATION2 daysMember invitation

Flow: Service → EmailEventProducer → RabbitMQ (email-exchange) → EmailEventConsumerEmailService → AWS SES

The EmailService uses Quarkus ReactiveMailer backed by AWS SES SMTP in production. In dev mode (mailer.dev.enabled=true), all emails are redirected to a single address with a "[DEV REDIRECT]" subject prefix.

Email links point to the frontend: {app.frontend.url}/auth/verify-email?token={token}, {app.frontend.url}/auth/reset-password?token={token}, {app.frontend.url}/auth/accept-invitation?token={token}.

Subscription Tiers & Usage Tracking

Each organisation has a subscription tier that defines limits:

LimitExample
maxWorkspacesnull = unlimited
maxViewsPerWorkspace
maxMembers
monthlyRequestLimit
monthlyRowOperationLimit
storageLimitMb
rateLimitPerMinuteDefault 60

Usage is tracked per workspace per billing period via UsagePeriod (request count, row read/write counts, storage bytes). The UsageService aggregates usage across all workspaces for the current billing period and returns a UsageSummaryDTO.

SchemaStack Internal Developer Documentation