Patterns and decisions

Review

Patterns and decisions

Where the design sits against the microservices.io pattern language, how the build can scale out, and the design decisions behind it.

Pattern map

Laid out like the microservices.io pattern language: monolith and microservices as alternatives at the top, then clusters of patterns. Colour shows where Capacity Connect stands on each.

Pattern map

Monolithic architecturehackathon build: modular monolith Microservice architecturetarget once we split by capability alternative to Decomposition Decompose by business capabilityauth, courses, assessments, resources Strangler applicationpeel services off the monolith Decompose by subdomainnot planned External API and UI API gatewayNginx edge + Express chain Backend for frontendadmin, portal, mobile each get one Client-side UI compositionsidebar + section app per page Data management Shared databaseone Postgres, RLS isolation Database per serviceone schema per service Sagacertificate render + record API compositionadmin dashboards over services Communication Remote procedure invocationREST, plus HTTP to Ollama Messagingreplaces the Postgres job queue Transactional outboxreliable events on approval Idempotent consumersafe embed job retries Discovery and deployment Server-side discoveryNginx upstream, Docker DNS Service as a containerDocker Compose stack Service registrynot needed at this scale Serverless deploymentnot planned Reliability and security Access tokenBetter Auth JWT Circuit breakeraround Ollama and Vertex AI calls Rate limiting (ours)Nginx + per-user limits Row-level security (ours)SET LOCAL per transaction Observability Health check API/healthz on every service Log aggregationone place for all service logs Application metricsqueue depth, request latency Distributed tracingnot planned Cross-cutting concerns Externalized configurationenv vars, AI_PROVIDER, Ollama host Microservice chassisshared package, like Gracenotes Consumer-driven contract testchecks API shape between services LEGEND In the hackathon build Target if we split Not planned
In the build, planned if we split, or not planned. Two patterns marked ours are not on that site.

Scaling path

The current build is a modular monolith with two satellite services. The scaling path splits the API by business capability, with one schema each and a shared contract package. Modules are already separated in code, so the split does not require a rewrite.

Scaling path

split by business capabilityTarget: one schema per servicejob event

API gateway

auth service
schema auth

course service
schema course

assessment service
schema assessment

resource service
schema resource

notice service
schema notice

ai-service
embed workers

pdf-service

shared contract package
roles, JWT verifier

Current build: modular monolith plus satellites

Nginx edge

Express API
modules: auth, courses, assessments,
resources, notices, admin

One Postgres
pgvector and RLS

ai-service
embed workers

Gotenberg

Local file storage

What stays

ai-service, Gotenberg and the JWT chain carry over unchanged. Only the Express API is split.

What changes

Cross-service foreign keys become plain id columns, and the Postgres job queue becomes a message broker.

Design decisions

  • super_admin is a fourth app role

    It writes resource_chunks and the embed workers run as it. The enum is super_admin, admin, trainer, trainee.

  • Local file storage

    Files sit on a Docker volume (uploads/, certs/, snapshots/) and are served only through the API. The database stores a file_path, so there is no external storage dependency.

  • AI_PROVIDER switch

    Quiz generation calls Vertex AI (Gemini) or local Ollama (Qwen3.5 9B/4B) from one env var, with identical JSON output. Embeddings always stay on Ollama nomic-embed-text, with embedding_model stored beside each vector.

  • Direct pg Pool, no pooler

    A pooler in transaction mode can leak the RLS context between requests.

  • One portal app

    Trainer and trainee share about 70 percent of their UI, so pages branch on role.

  • Vite multi-page plus nested React Router

    Top-level navigation is a real page load. React Router only handles sub-views.

  • go_router for Flutter

    Its redirect callback mirrors authorizeRoute.

  • Drizzle for schema and migrations

    Type-safe schema and migrations shared by the API and the workers.

Capacity Connect · Team Syntax Squad · SIH 2026 · PS 26075Code samples are implementation sketches.