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:
- Business rules belong in the domain — not in UI handlers, database queries, or API routes.
- Dependency direction is enforced — UI calls Application; Application calls Domain; Infrastructure implements ports.
- Boundaries are explicit — every import across a package boundary is intentional and documented.
- 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:
| Artifact | Target | Technology |
|---|---|---|
apps/web | Cloudflare Workers | Astro SSR + D1 |
apps/docs | GitHub Pages | Astro 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/weband 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.