Skip to main content

ADR-003: Monorepo Architecture β€” Multi-Service Polyglot Design

Metadata​

FieldValue
StatusAccepted
Date2026-02-06
LinearN/A (Foundational)

Context​

STOA Platform is a comprehensive API Management solution comprising multiple services: a control plane API, multiple frontends, an AI-native gateway, infrastructure automation, and tooling. These components share common dependencies, require coordinated releases, and benefit from unified CI/CD pipelines.

The architectural decision on repository structure significantly impacts:

  • Developer Experience β€” How easy is it to contribute across components?
  • CI/CD Complexity β€” How do we build, test, and deploy interdependent services?
  • Code Sharing β€” How do we share utilities, types, and components?
  • Release Coordination β€” How do we version and release the platform?

The Problem​

"Should each service live in its own repository (polyrepo) or should everything coexist in a single repository (monorepo)?"

This is a foundational decision that affects every aspect of development workflow.

Considered Options​

1. Monorepo (Single Repository)​

All application code, infrastructure definitions, and shared components in one repository. Separate repositories only for runtime configuration (GitOps) and infrastructure-as-code secrets.

2. Polyrepo (Multiple Repositories)​

Each service in its own repository: stoa-api, stoa-console, stoa-portal, stoa-gateway, stoa-cli, etc.

3. Hybrid (Monorepo + Satellites)​

Core services in a monorepo, with specialized components (infrastructure, docs) in separate repositories.

Decision Outcome​

Chosen option: Hybrid Monorepo β€” Core application code in stoa, with dedicated repos for infrastructure (stoa-infra), documentation (stoa-docs), quickstart (stoa-quickstart), and CLI (stoactl).

Repository Structure​

RepositoryPurposeContent
stoaApplication monorepoAll services, shared code, CI/CD
stoa-infraInfrastructure IaCTerraform, Ansible, Helm (canonical)
stoa-docsDocumentationDocusaurus site, ADRs
stoa-quickstartSelf-hosted quickstartDocker Compose
stoactlCLI toolGo + Cobra
stoa-gitopsRuntime configTenant configs, ArgoCD apps

Monorepo Structure​

stoa/
β”œβ”€β”€ control-plane-api/ # Python 3.11 β€” FastAPI backend
β”œβ”€β”€ control-plane-ui/ # React 18 + TypeScript β€” Console UI
β”œβ”€β”€ portal/ # React 18 + TypeScript β€” Developer Portal
β”œβ”€β”€ mcp-gateway/ # Python 3.11 β€” MCP Gateway (current)
β”œβ”€β”€ stoa-gateway/ # Rust (stable) β€” Future unified gateway
β”œβ”€β”€ cli/ # Python β€” Internal CLI
β”œβ”€β”€ e2e/ # Playwright + BDD β€” E2E tests
β”œβ”€β”€ landing-api/ # Python 3.12 β€” Landing page API
β”‚
β”œβ”€β”€ shared/ # Shared frontend components
β”‚ β”œβ”€β”€ components/ # React components (Toast, CommandPalette)
β”‚ └── contexts/ # React contexts (Theme)
β”‚
β”œβ”€β”€ charts/ # Helm charts (synced with stoa-infra)
β”‚ └── stoa-platform/ # Main platform chart
β”‚
β”œβ”€β”€ deploy/ # Deployment configurations
β”‚ β”œβ”€β”€ argocd/ # ArgoCD ApplicationSets
β”‚ β”œβ”€β”€ demo/ # Demo tenant seeding
β”‚ β”œβ”€β”€ docker-compose/ # Local development
β”‚ └── platform-bootstrap/ # Initial platform setup
β”‚
β”œβ”€β”€ ansible/ # Ansible playbooks
β”‚ └── reconcile-webmethods/ # Gateway reconciliation
β”‚
β”œβ”€β”€ scripts/ # Utility scripts
β”œβ”€β”€ docs/ # Internal documentation
β”œβ”€β”€ services/ # Supporting services
β”‚ └── kafka-bridge/ # Kafka bridge service
β”‚
β”œβ”€β”€ stoa-catalog/ # UAC schemas and templates
β”œβ”€β”€ stoa-policy-engine-rs/ # Rust OPA evaluation library
β”‚
β”œβ”€β”€ .github/workflows/ # CI/CD workflows
└── CLAUDE.md # AI context file

Technology Stack Matrix​

ComponentLanguageFrameworkRuntime
control-plane-apiPython 3.11FastAPI, SQLAlchemy 2.0Async
control-plane-uiTypeScriptReact 18, Vite, ZustandNode 20
portalTypeScriptReact 18, ViteNode 20
mcp-gatewayPython 3.11FastAPI, OPAAsync
stoa-gatewayRust (stable)Tokio, AxumNative
cliPython 3.11Typer, RichSync
e2eTypeScriptPlaywright, BDDNode 20
landing-apiPython 3.12FastAPIAsync

CI/CD Architecture​

Path-Based Triggers​

Each component has its own CI workflow triggered by path changes:

# .github/workflows/control-plane-api-ci.yml
on:
push:
paths:
- 'control-plane-api/**'
- 'shared/**' # Shared dependencies

Workflow Matrix​

ComponentWorkflowTriggersActions
control-plane-apicontrol-plane-api-ci.ymlcontrol-plane-api/**ruff, mypy, pytest, bandit
control-plane-uicontrol-plane-ui-ci.ymlcontrol-plane-ui/**, shared/**eslint, vitest
mcp-gatewaymcp-gateway-ci.ymlmcp-gateway/**ruff, mypy, pytest
stoa-gatewaystoa-gateway-ci.ymlstoa-gateway/**cargo test, clippy
portalportal-ci.ymlportal/**, shared/**eslint, vitest
e2ee2e-tests.ymlManual, PRPlaywright BDD

Reusable Workflows​

.github/workflows/
β”œβ”€β”€ reusable-python-ci.yml # Python linting, testing, coverage
β”œβ”€β”€ reusable-node-ci.yml # Node linting, testing
β”œβ”€β”€ reusable-rust-ci.yml # Rust build, test, clippy
β”œβ”€β”€ reusable-docker-ecr.yml # Docker build, ECR push
└── reusable-k8s-deploy.yml # Kubernetes deployment

Shared Code Strategy​

Frontend Shared Components​

shared/
β”œβ”€β”€ components/
β”‚ β”œβ”€β”€ CommandPalette/ # Global search
β”‚ β”œβ”€β”€ Toast/ # Notifications
β”‚ β”œβ”€β”€ ThemeToggle/ # Dark/light mode
β”‚ β”œβ”€β”€ Breadcrumb/
β”‚ β”œβ”€β”€ ConfirmDialog/
β”‚ β”œβ”€β”€ EmptyState/
β”‚ β”œβ”€β”€ FormWizard/
β”‚ └── Skeleton/
└── contexts/
└── ThemeContext.tsx # Theme provider

Both control-plane-ui and portal import from shared/:

// portal/src/App.tsx
import { ThemeProvider } from '../../shared/contexts/ThemeContext';
import { Toast } from '../../shared/components/Toast';

Python Shared Patterns​

Python components share patterns via copy (not packages) due to different deployment contexts:

  • Auth patterns: RBAC decorators, Keycloak integration
  • Logging: structlog configuration
  • OPA integration: Policy evaluation client

Dependency Management​

Python (pyproject.toml per component)​

# control-plane-api/pyproject.toml
[project]
name = "control-plane-api"
requires-python = ">=3.11"

[tool.ruff]
line-length = 120

[tool.pytest.ini_options]
asyncio_mode = "auto"

Node (package.json per component + root)​

// Root package.json β€” workspace management
{
"workspaces": ["control-plane-ui", "portal", "e2e"],
"devDependencies": {
"commitlint": "^19.0.0",
"husky": "^9.0.0"
}
}

Rust (Cargo.toml per component)​

# stoa-gateway/Cargo.toml
[package]
name = "stoa-gateway"
edition = "2021"

[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.7"

Cross-Repo Synchronization Rules​

RuleSourceDestination
API changesstoa/control-plane-apistoa-docs/docs/api/
Helm chartsstoa/charts/stoa-infra/charts/
CRD definitionsstoa-infra/charts/stoa-platform/crds/Source of truth
CLI commandsstoa/cli/ or stoactl/stoa-docs/docs/reference/cli.md

Consequences​

Positive​

  • Atomic Changes β€” Cross-component changes are single PRs with unified review
  • Shared CI β€” Reusable workflows reduce maintenance overhead
  • Dependency Visibility β€” Breaking changes are caught at PR time
  • Simplified Onboarding β€” One clone, one setup script
  • Coordinated Releases β€” Release Please manages versioning across components

Negative​

  • Repository Size β€” Clone includes all components (~500MB with history)
  • CI Complexity β€” Path-based triggers require careful configuration
  • Partial Failures β€” One broken component can block main branch
  • IDE Performance β€” Large monorepo can slow some IDEs

Mitigations​

ChallengeMitigation
Large cloneShallow clone for CI (--depth=1)
CI complexityReusable workflows + clear ownership
Partial failuresRequired status checks per component
IDE performanceComponent-specific .vscode/ configs

Rationale for Satellite Repos​

stoa-infra (Separate)​

  • Security β€” Terraform state, AWS credentials, Vault secrets
  • Access Control β€” Restricted to platform operators
  • Audit Trail β€” Separate git history for compliance

stoa-docs (Separate)​

  • Public Site β€” docs.gostoa.dev served via Vercel
  • Community Contributions β€” Lower barrier for doc PRs
  • Docusaurus Build β€” Independent build pipeline

stoa-quickstart (Separate)​

  • Self-Contained β€” Users clone without full monorepo
  • Docker Compose β€” Simple local setup
  • Versioned β€” Tags match platform releases

stoactl (Separate)​

  • Go Binary β€” Different build toolchain
  • Independent Release β€” CLI can release faster than platform
  • Distribution β€” Homebrew, apt, direct download

Validation​

OSS Contributor Persona​

"Je veux contribuer une feature au portal. Je dois tout cloner?"

Response:

# Repository access granted to beta participants
# Request access: christophe@hlfh.io
git clone --depth=1 <repository-url>
cd stoa/portal
npm install
npm run dev

Full history unnecessary. Path-based CI runs only portal tests.

Enterprise Architect Persona​

"Comment vous gΓ©rez les dΓ©pendances entre control-plane-api et mcp-gateway?"

Response:

  • Both are Python 3.11 with shared patterns (copy, not package)
  • API changes require both CI pipelines to pass
  • Integration tested via E2E suite
  • Shared schemas in stoa-catalog/

References​


Standard Marchemalo: A 40-year veteran architect understands in 30 seconds