Skip to content

ADR-003: Module Federation for Micro-Frontend Architecture

Status

Accepted

Date

2023-Q2


Context

The Microtec ERP frontend needs to deliver 10 distinct business modules: Accounting, HR, Finance, Sales, Purchase, Inventory, Distribution, Fixed Assets, Business Owners portal, and an ERP home shell. Each module is owned by a different feature team, has an independent release cadence, and is expected to grow to tens of thousands of lines of code.

The initial approach was a single Angular workspace with lazy-loaded route modules. This quickly ran into limitations:

  • A change in any module required testing and deploying the entire application
  • Build times grew as all modules were compiled together
  • Teams could not deploy independently — a broken module in the Accounting build blocked the HR release
  • Bundle size was unconstrained; importing a large library in one module loaded it for all users

We evaluated three micro-frontend approaches:

OptionDescriptionKey Concerns
iFrameEach module runs in an isolated iframePoor UX (scroll, resize, auth sharing), high overhead, no shared state
single-spaFramework-agnostic MFE orchestratorFramework-agnostic (good), but adds indirection; Angular adapter needed
Module FederationWebpack 5 native, Angular native supportFirst-class Angular support via @angular-architects/module-federation

Decision

Adopt Webpack Module Federation via @angular-architects/module-federation.

Each of the 10 Angular applications in FrontApps/projects/ is an independently deployed and independently built Module Federation remote. The ERP home shell (erp-home, port 4401) acts as the host that dynamically loads remote modules at runtime.

Architecture

FrontApps/projects/
├── erp-home/            ← Host shell (port 4401) — loads all remotes
├── apps-accounting/     ← Remote (port 4402)
├── apps-hr/             ← Remote (port 4403)
├── apps-finance/        ← Remote (port 4404)
├── apps-sales/          ← Remote (port 4405)
├── apps-purchase/       ← Remote (port 4406)
├── apps-inventory/      ← Remote (port 4407)
├── app-distribution/    ← Remote (port 4408)
├── fixed-assets/        ← Remote (port 4409)
└── bussiness-owners/    ← Standalone (port 4301, separate Keycloak realm)

Shared Libraries

Singleton dependencies (Angular core, RxJS, Keycloak-js, PrimeNG) are declared as shared in each app's webpack.config.js. Module Federation ensures each singleton loads once across all remotes:

javascript
// webpack.config.js (each remote)
shared: share({
  "@angular/core":    { singleton: true, strictVersion: true, requiredVersion: "auto" },
  "@angular/router":  { singleton: true, strictVersion: true, requiredVersion: "auto" },
  "rxjs":             { singleton: true, strictVersion: true, requiredVersion: "auto" },
  "primeng":          { singleton: true, requiredVersion: "auto" },
  "keycloak-js":      { singleton: true, requiredVersion: "auto" },
})

Shared UI components and services live in FrontApps/libs/shared-lib.

Dynamic Remote Loading

The shell loads remotes using runtime manifest URLs — no compile-time coupling:

typescript
// erp-home: loadRemoteModule
const routes: Routes = [
  {
    path: 'accounting',
    loadChildren: () => loadRemoteModule({
      remoteEntry: environment.remotes.accounting,  // e.g., https://accounting.microtecstage.com/remoteEntry.js
      remoteName: 'apps_accounting',
      exposedModule: './AccountingModule'
    }).then(m => m.AccountingModule)
  }
];

Remote entry URLs are environment-specific and configured in environment.{env}.ts files.


Consequences

Positive

  • Independent deployment: The HR team deploys apps-hr without touching any other module
  • Independent build times: Each module builds in ~2 minutes independently vs ~15 minutes for the full monolith
  • Team autonomy: Feature teams have full ownership of their remote — routes, state, components
  • Incremental loading: Users only download the JavaScript for modules they visit
  • Shared singletons: Angular, RxJS, and PrimeNG load once — no duplicate frameworks
  • Graceful degradation: If one remote is unavailable, the shell can show an error boundary instead of crashing entirely

Negative

  • Version coordination required: If @angular/core versions diverge between remotes, singleton sharing breaks at runtime. Teams must coordinate Angular version upgrades.
  • Webpack configuration complexity: Each app needs a webpack.config.js with careful shared declarations. A misconfiguration causes silent duplicate-module bugs.
  • Local development overhead: Running all 10 apps simultaneously requires significant RAM. Use npm run start:erp to run only the apps you need.
  • Build system coupling: Module Federation is tightly coupled to Webpack. Migrating to Vite/esbuild would require significant rework.
  • SSR complexity: Server-side rendering with Module Federation requires additional orchestration (relevant for the Fooj project, not ERP).

Neutral

  • @angular-architects/module-federation is the community-standard wrapper and tracks Angular releases closely
  • The bussiness-owners app is deliberately separate (different Keycloak realm) and does not federate with ERP home

Implementation Notes

Angular Version Policy

All remotes must use the same Angular major version. Minor/patch updates can be staggered but must be completed within one sprint of the host shell upgrade.

RuleEnforcement
Same @angular/core majorEnforced in package.json peer deps of shared-lib
Same rxjs majorEnforced in root package.json
PrimeNG versionSynced manually during Angular upgrades

Port Assignments (Local Development)

AppPort
bussiness-owners4301
erp-home (shell)4401
apps-accounting4402
apps-hr4403
apps-finance4404
apps-sales4405
apps-purchase4406
apps-inventory4407
app-distribution4408
fixed-assets4409

Deployment Topology

In production, each remote is deployed to Azure Static Web Apps at a subdomain. The remote entry URL configured in the shell's environment file points to the production SWA URL.


  • ADR-001: Microservices Architecture (parallel to MFE decomposition on the frontend)
  • ADR-004: Keycloak SSO (shared auth across all MFE remotes)
  • ADR-005: Azure Service Bus (not directly related, but same decomposition philosophy)

Internal Documentation — Microtec Platform Team