Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-001: Monorepo Structure

Status

Accepted (2024-01-15)

Context

Tenki Cloud consists of multiple interconnected services:

  • Frontend applications (Next.js web app, future mobile apps)
  • Backend services (Go microservices)
  • Shared packages (TypeScript utilities, proto definitions)
  • Infrastructure code (Kubernetes manifests, Terraform)

We need a repository structure that:

  1. Enables code sharing between services
  2. Ensures coordinated deployments
  3. Maintains clear boundaries between services
  4. Provides good developer experience

Decision

We will use a monorepo structure with:

  • pnpm workspaces for TypeScript/JavaScript projects
  • Go modules with replace directives for Go services
  • Turborepo for orchestrated builds
  • Shared tooling across all services

Repository structure:

tenki.app/
├── apps/           # Deployable applications
├── backend/        # Go services
├── packages/       # Shared libraries
├── proto/          # Protocol buffer definitions
└── infra/          # Infrastructure code

Consequences

Positive

  1. Atomic changes - Features spanning multiple services can be implemented in a single commit
  2. Shared tooling - Linting, formatting, and testing tools configured once
  3. Simplified dependencies - No need for private package registries
  4. Consistent versioning - All services released together
  5. Easier refactoring - Moving code between services is straightforward
  6. Single source of truth - Proto definitions shared directly

Negative

  1. Larger repository - Clone and fetch times increase over time
  2. Complex CI/CD - Need to determine which services to build/deploy
  3. Steeper learning curve - New developers must understand entire structure
  4. Potential for coupling - Easier to create inappropriate dependencies
  5. Tooling requirements - Requires pnpm, Go, and other tools installed

Alternatives Considered

1. Separate Repositories

Rejected because:

  • Coordination overhead for cross-service changes
  • Dependency version management complexity
  • Need for private package registry
  • Difficult to maintain API contracts

2. Git Submodules

Rejected because:

  • Poor developer experience
  • Complex update workflows
  • Easy to get into inconsistent states
  • Limited tool support

3. Lerna (instead of Turborepo)

Rejected because:

  • Turborepo has better performance
  • Native pnpm workspace support
  • Better caching mechanisms
  • Simpler configuration

Implementation Notes

  1. Use pnpm filters for targeted operations:

    pnpm -F app dev          # Run only app
    pnpm -F "backend/*" test # Test all backend
    
  2. Go services use local replace:

    replace github.com/luxorlabs/proto => ../../proto
    
  3. CI uses Turborepo caching:

    {
      "pipeline": {
        "build": {
          "cache": true
        }
      }
    }