Large-Scale Enterprise Architecture & DDD
Building applications that scale to hundreds of developers, thousands of components, and millions of active users requires strict architectural discipline. Without clean boundaries, codebases inevitably degrade into tangled "spaghetti" architectures where modifying a single component causes cascading regressions across unrelated features.
Modern enterprise Angular architectures apply Domain-Driven Design (DDD) and Feature-Sliced Architecture, organizing code into isolated Bounded Contexts with strict unidirectional dependencies.
┌─────────────────────────────────────────────────────────────┐
│ Enterprise DDD Folder Structure │
│ │
│ src/app/ │
│ ├── core/ # Application-wide singletons│
│ │ ├── auth/ # Authentication & JWT tokens│
│ │ ├── http/ # Interceptors & API client │
│ │ └── layout/ # Shell header, nav, footer │
│ ├── shared/ # Universal dumb UI & utils │
│ │ ├── ui/ # Buttons, Badges, Modals │
│ │ └── pipes/ # Currency, Date formatters │
│ └── domains/ # Isolated Bounded Contexts │
│ ├── billing/ # Billing Domain │
│ │ ├── data-access/ # Services, Stores, APIs │
│ │ ├── feature-invoices/ # Smart Container Views │
│ │ ├── ui-invoice-table/ # Presentational Dumb UI │
│ │ └── billing.routes.ts # Feature routing definition │
│ └── inventory/ # Inventory Domain │
└─────────────────────────────────────────────────────────────┘
1. The 4 Library/Folder Archetypes
In an enterprise architecture, every folder or library belongs to one of four strictly categorized types:
feature-*(Smart Containers):- Routable pages and container components.
- Injects domain facades/stores, coordinates workflow, and handles navigation.
ui-*(Presentational Components):- Reusable, pure dumb components with zero service dependencies.
- Accepts data via
input()signals and emits events viaoutput().
data-access(Domain Logic & State):- Contains API repositories, state stores (SignalStore), domain models, and validation rules.
util(Pure Utilities):- Pure helper functions, custom date formatters, and utility types.
2. The Facade Pattern: Decoupling UI from State & APIs
A Facade provides a simplified, consolidated interface over complex underlying subsystems (HTTP services, caching stores, WebSocket connections):
// src/app/domains/billing/data-access/billing.facade.ts
import { Injectable, inject } from '@angular/core';
import { BillingApiService } from './billing-api.service';
import { BillingStore } from './billing.store';
@Injectable({ providedIn: 'root' })
export class BillingFacade {
private api = inject(BillingApiService);
private store = inject(BillingStore);
// Read-only state exposed to feature components
readonly invoices = this.store.invoices;
readonly isLoading = this.store.isLoading;
readonly totalOutstanding = this.store.totalOutstanding;
loadInvoices(): void {
this.store.loadInvoices();
}
payInvoice(id: string): void {
this.api.submitPayment(id).subscribe(() => {
this.store.markAsPaid(id);
});
}
}
Feature components interact exclusively with the BillingFacade, remaining completely unaware of how state is stored or how API requests are structured.
Summary & Key Takeaways
- Domain-Driven Design (DDD) separates massive applications into isolated bounded contexts.
- Code is structured into 4 archetypes:
feature,ui,data-access, andutil. - The Facade pattern creates a clean abstraction boundary between UI components and backend state systems.
- Unidirectional dependencies ensure changes in one domain cannot break unrelated business domains.
Best Practices & Senior Guidance
- Enforce Module Boundaries with ESLint: Use
@nx/enforce-module-boundariesto prevent features in Domain A from illegally importing internal logic from Domain B. - Keep UI Components Completely Stateless: Never inject
HttpClientor global state stores intoui-*components. - Co-locate Unit Tests with Source Code: Keep
.spec.tsfiles adjacent to their corresponding.tsimplementations.