Skip to main content

Architecture Overview

Cascadia PLM is a code-first Product Lifecycle Management system. This document provides a mental model of the system architecture and explains key design decisions.

Detailed guides: For implementation specifics, see Service Patterns, Git-Style Versioning, and Database Patterns.

System Mental Model​

┌─────────────────────────────────────────────────────────────────────────────┐
│ Cascadia PLM │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Parts │ │ Documents │ │ Change │ │Requirements │ │
│ │ │ │ │ │ Orders │ │ & Tasks │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │ │
│ └──────────────────┴────────┬─────────┴──────────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ Item Registry │ ← Unified item system │
│ │ & Services │ │
│ └─────────┬─────────┘ │
│ │ │
│ ┌─────────────────────────────────┼─────────────────────────────────┐ │
│ │ │ │ │
│ ┌▼────────────┐ ┌────────────────▼─────────────┐ ┌───────────────▼┐ │
│ │ Versioning │ │ Workflows & Lifecycles │ │ File Vault │ │
│ │ (Git-style)│ │ (state machines, guards) │ │ │ │
│ └─────────────┘ └──────────────┬───────────────┘ └────────────────┘ │
│ │ │
│ ┌─────────▼─────────┐ │
│ │ Background Jobs │ ← Async processing │
│ │ (RabbitMQ) │ (notifications, etc.) │
│ └─────────┬─────────┘ │
│ │ │
├────────────────────────────────────┼────────────────────────────────────────┤
│ PostgreSQL Database │
└─────────────────────────────────────────────────────────────────────────────┘

Technology Stack​

LayerTechnologyWhy This Choice
FrameworkTanStack StartFull-stack TypeScript, file-based routing, excellent DX
DatabasePostgreSQL 18+ACID compliance, JSON support, proven at scale
ORMDrizzleType-safe, SQL-like syntax, great migrations
Message QueueRabbitMQReliable async job processing, message persistence
UITailwind + RadixUtility-first CSS, accessible components
ValidationZodRuntime validation with TypeScript inference
AuthOslo.js + ArcticModern crypto, OAuth support, no magic
TestingVitest + PlaywrightFast unit tests, reliable E2E

Core Design Decisions​

1. Code-First Configuration​

Decision: All customization happens in TypeScript code, not UI configuration.

Why:

  • Version controlled with Git
  • Full IDE support (autocomplete, refactoring, type checking)
  • No hidden configuration in database
  • Deployments are reproducible
  • Easier to review changes in PRs

Trade-off: Less accessible to non-developers, but PLM customization typically requires developer skills anyway.

2. Two-Table Pattern for Items​

Decision: Every item type has a base record in items plus type-specific fields in a dedicated table.

items (base) parts (type-specific)
├── id ├── itemId (FK) ─────┐
├── masterId ├── makeBuy │
├── itemNumber ├── material │
├── revision └── weight │
├── state │
└── ... ◄─────────────────────┘

Why:

  • Unified queries across all item types ("show me all items in Review state")
  • Common operations work on any item (revise, transition, search)
  • Type-specific tables stay focused and small
  • Relationships point to items.id, not type-specific IDs

Learn more: Database Patterns

3. Git-Style Versioning​

Decision: Use branches and commits instead of linear revision sequences.

Traditional PLM: PN-001: A → B → C → D (linear, blocking)

Cascadia: main: [A] ────────── [B] ────── [C]
│ ↑ ↑
eco/ECO-1: └──[work]─────┘ │
eco/ECO-2: ──────────[work]─────────┘

Why:

  • Multiple ECOs can work on the same part simultaneously
  • ECO cancellation is clean (just delete the branch)
  • Changes are grouped by purpose (ECO), not just "next revision"
  • Complete history with merge commits
  • Revision letters assigned only on merge (release)

Trade-off: More complex than linear revisions, but solves real workflow problems.

Learn more: Git-Style Versioning

4. Service Layer Pattern​

Decision: All business logic in service classes, not in routes or components.

Route (thin) Service (business logic) Database
│ │ │
│ validate request │ │
│ ──────────────────► │ │
│ │ complex logic │
│ │ ──────────────────────► │
│ │ ◄────────────────────── │
│ ◄────────────────── │ │
│ return response │ │

Why:

  • Routes stay thin and focused on HTTP concerns
  • Business logic is testable without HTTP
  • Services can call other services
  • Easier to understand "where does X happen?"

Learn more: Service Patterns

5. Program-Based Permissions​

Decision: Users belong to Programs, which control what they can access.

Organization (tenant)
└── Program A (permission boundary)
│ ├── Design 1
│ │ └── Parts, Documents, etc.
│ └── Design 2
└── Program B (separate boundary)
└── Design 3

Why:

  • Natural fit for contract-based work (defense, aerospace)
  • Clear data isolation between programs
  • Users can belong to multiple programs
  • Simpler than item-level ACLs for most use cases

6. Workflow Definitions as Data​

Decision: Workflows and lifecycles are defined as JSON structures in the database, not hardcoded.

Why:

  • Different item types can share lifecycle definitions
  • Admins can modify workflows without code deployment
  • Workflow history is tied to the definition version
  • Supports approval voting, guards, and effects

Trade-off: More complex than hardcoded states, but necessary for configurability without redeploys.

7. Background Job System​

Decision: Use RabbitMQ for async job processing instead of synchronous operations.

Service Job Queue Worker
│ │ │
│ submit(type, payload) │ │
│ ─────────────────────► │ │
│ │ consume job │
│ │ ─────────────────────► │
│ │ │ execute handler
│ │ ◄───────────────────── │
│ │ ack/nack │

Why:

  • Non-blocking operations for notifications and bulk processing
  • Automatic retries with configurable backoff
  • Job status tracking and admin visibility
  • Horizontal scaling with multiple workers
  • Message persistence if worker crashes

Components:

  • JobService: Submit jobs, track status, manage retries
  • JobTypeRegistry: Register job types with schemas and handlers
  • Worker Process: Consumes jobs from RabbitMQ and executes handlers

Trade-off: Adds infrastructure complexity (RabbitMQ), but necessary for reliable async operations.

Directory Structure​

src/
├── lib/
│ ├── items/ # Item type system
│ │ ├── registry.ts # Central registry
│ │ ├── types/ # Type definitions and schemas
│ │ └── services/ # ItemService, ChangeOrderService
│ │
│ ├── services/ # Versioning services
│ │ ├── DesignService.ts
│ │ ├── BranchService.ts
│ │ ├── CommitService.ts
│ │ ├── CheckoutService.ts
│ │ └── VersionResolver.ts
│ │
│ ├── jobs/ # Background job system
│ │ ├── JobService.ts # Job submission and tracking
│ │ ├── registry.ts # Job type registry
│ │ ├── handlers/ # Job handler implementations
│ │ └── worker/ # Worker process logic
│ │
│ ├── db/
│ │ ├── index.ts # Database connection
│ │ └── schema/ # Drizzle table definitions
│ │
│ ├── auth/ # Authentication
│ │ ├── session.ts # Session management
│ │ ├── password.ts # Password hashing
│ │ └── server.ts # Route helpers
│ │
│ └── vault/ # File storage
│ └── services/
│
├── routes/
│ ├── api/ # API endpoints
│ ├── admin/ # Admin UI (jobs, settings)
│ └── (app)/ # UI routes
│
└── components/
├── ui/ # Base components
└── items/ # Item-specific components

Data Flow​

Read Path (e.g., viewing a part)​

1. Browser requests /parts/[id]
2. UI route loads, calls API
3. API route: requireAuth() → requirePermission()
4. ItemService.findById() called
5. Drizzle queries items + parts tables
6. Merged result returned to UI
7. React renders PartDetail component

Write Path (e.g., ECO release)​

1. User clicks "Release ECO" button
2. API route receives POST request
3. requirePermission('ChangeOrder', 'update')
4. EcoReleaseService.releaseEco() called
5. Transaction begins:
a. Validate ECO is approved
b. For each affected design:
- Merge ECO branch to main
- Assign revision letters
- Create merge commit
- Archive ECO branch
c. Update ECO state to Implemented
6. Transaction commits
7. Success response returned

Integration Points​

External Systems​

IntegrationMethodStatus
SolidWorksDesktop add-in (C#)In progress
SysML 2.0REST API at /api/sysml/*Complete
ERP SystemsPlanned webhook eventsFuture
SSO/SAMLArctic OAuth providersSupported

Extension Patterns​

Adding a new item type: Define schema, create type file, register in registry. See Adding Item Types.

Custom workflow actions: Add action handlers to workflow definition.

External triggers: API endpoints accept webhooks for external events.

Key Architectural Boundaries​

Client vs Server​

Client (Browser) Server (Node.js)
───────────────── ─────────────────
React components API routes
TanStack Router Services
Form state Database access
Local validation Authoritative validation
│
│ HTTP/JSON
▼

Rule: Database code never runs in the browser. Use .server.ts suffix or dynamic imports for server-only code.

Item Types vs Services​

Item Types (data shape) Services (behavior)
─────────────────────── ───────────────────
Zod schemas Business logic
Type-specific fields State transitions
UI components Cross-type operations
Registry config Database transactions

Rule: Services are type-agnostic where possible. ItemService.create('Part', data) works for any registered type.

Performance Considerations​

Database​

  • Indexes: Key queries have covering indexes (see Database Patterns)
  • Pagination: All list endpoints support limit/offset
  • Soft deletes: Deleted items excluded from queries by default

Caching​

  • Session cache: Sessions cached in memory with DB fallback
  • Registry cache: Item type configs cached after first load
  • No query cache: Drizzle queries hit DB directly (PostgreSQL handles caching)

Bundle Size​

  • Code splitting: Routes are lazy-loaded
  • Server components: Heavy logic stays server-side
  • Tree shaking: Unused code eliminated in production build

Further Reading​

TopicDocument
Using servicesService Patterns
Branching and commitsGit-Style Versioning
Schema and queriesDatabase Patterns
Background jobsJob Management
Coding standardsCode Conventions
Extending the systemAdding Item Types
Test infrastructureTesting