Baseleg Docs

Architecture

Baseleg is a pragmatic domain-led modular monolith: clear module boundaries and explicit dependency direction, without heavy enterprise ceremony.

Approach

The system is a single deployable application with internal modules that mirror the domain’s bounded contexts. This provides:

  • Fast iteration and simple operations — one deployable to reason about.
  • Clear boundaries that constrain accidental coupling between contexts.
  • A path to extract services later, only if operational necessity demands it.

The architecture is guided by four principles:

  1. Business rules belong in the domain — not in UI handlers, database queries, or API routes.
  2. Dependency direction is enforced — UI calls Application; Application calls Domain; Infrastructure implements ports.
  3. Boundaries are explicit — every import across a package boundary is intentional and documented.
  4. Simplicity is a feature — if an enterprise pattern is not earning its complexity, do not add it.

Layering

Baseleg uses a four-layer architecture. Dependencies flow in one direction only.

┌─────────────────────────────────────────┐
│  UI  (Astro routes, @baseleg/ui)        │
└────────────────┬────────────────────────┘
                 │ calls use cases
┌────────────────▼────────────────────────┐
│  Application  (use cases per context)   │
└────────────┬──────────────┬─────────────┘
             │ owns model   │ calls ports
┌────────────▼──────────┐  ┌▼─────────────────────────────┐
│  Domain               │  │  Infrastructure               │
│  (rules, entities,    │  │  (D1/Drizzle, adapters,       │
│   value objects)      │  │   repository implementations) │
└───────────────────────┘  └──────────────────────────────-┘
             ▲                           │
             └───────────────────────────┘
               Infrastructure depends on Domain

UI layer

  • Astro routes and pages (apps/web/src/pages/)
  • Shared component library (packages/ui/)
  • Responsible for rendering, user interaction, loading/empty/error states
  • Calls application use cases; does not make business rule decisions
  • Must not import infrastructure implementations or Drizzle schema

Application layer

  • One package per bounded context: packages/application/<context>/
  • Contains use cases that orchestrate domain logic
  • Manages transaction boundaries and input validation flows
  • Depends on Domain (for model types and rules) and Shared (for utilities)
  • Defines ports (interfaces) that Infrastructure implements

Domain layer

  • One package per bounded context: packages/domain/<context>/
  • Contains entities, value objects, domain rules, and invariants
  • Depends only on packages/shared/ primitives
  • Must not import Infrastructure, Astro, or any runtime-specific code
  • The most stable layer — changes here require the most care

Infrastructure layer

  • Lives in packages/infrastructure/
  • Implements ports defined by Application: repository implementations, D1/Drizzle wiring, notification adapters, auth
  • Depends on Domain (for type definitions) and Shared
  • Wired together at the application entry point (apps/web/)

Package naming

packages/
  domain/
    people/          → @baseleg/domain-people
    aircraft/        → @baseleg/domain-aircraft
    scheduling/      → @baseleg/domain-scheduling
    training/        → @baseleg/domain-training
    billing/         → @baseleg/domain-billing
    compliance/      → @baseleg/domain-compliance
    notifications/   → @baseleg/domain-notifications
    reporting/       → @baseleg/domain-reporting
  application/
    people/          → @baseleg/application-people
    aircraft/        → @baseleg/application-aircraft
    (... same pattern)
  infrastructure/
    db/              → @baseleg/infrastructure-db
    repositories/    → @baseleg/infrastructure-repositories
    (... adapters)
  ui/                → @baseleg/ui
  shared/
    result/          → @baseleg/shared-result
    (... utilities)
  config/
    typescript/      → @baseleg/config-typescript
    (... shared configs)

Deployment

Baseleg deploys two separate artifacts:

ArtifactTargetTechnology
apps/webCloudflare WorkersAstro SSR + D1
apps/docsGitHub PagesAstro static

Separating deployments means:

  • Documentation publishing is low-risk and independent from production.
  • The app can evolve runtime bindings (D1, Workers) without affecting the docs site.
  • Infrastructure concerns (Workers, D1) are isolated to apps/web and the infrastructure packages.

How to navigate

  • Package boundaries — allowed and forbidden imports, with examples.
  • Database — D1/Drizzle structure, schema conventions, migration rules.
  • Testing — layered testing strategy and regression requirements.
  • Coding standards — TypeScript rules and module conventions.
  • Definition of done — what “done” means for every feature.
  • UX system — design tokens, components, patterns, and diagrams (separate section).
  • Architecture decisions — ADRs with rationale for every significant decision.