1. 09Feature modules are units of change
  2. 10The parts of a feature
  3. 11Making a feature ready to build
  4. 12Building a feature from idea to implementation
  5. 13Testing a feature without the host
  6. 14The application is the composer

Building product features

14

The application is the composer

Route components act as composition roots: connecting ambient host state to the feature's contract.

By Eyal Ellenbogen August 29, 2026 2 min read

A self-contained feature module can have polished views and an explicit port contract, but it still cannot mount itself in production. Something has to read route parameters, establish authenticated sessions, render the global application chrome, and pass in a live implementation of the port.

That integration happens outside the module. In dependency-injection terms, this is our frontend composition root: the narrow layer where ambient application context meets the feature's explicit contract.

Let the root do the wiring

A composition root stays thin when its only responsibility is assembly. It takes ambient configuration and session state from the host environment, instantiates the concrete adapter, and provides it to the feature:

// The host creates the adapter from ambient environment and session state
const adapter = new HttpMemberInviteAdapter({
  apiEndpoint: config.apiUrl,
  authToken: session.token,
})

// The feature only knows its port contract
<MemberInviteFeature port={adapter} />

Whether we pass the adapter as a component prop in React or supply it through dependency injection in Angular, the architectural boundary is identical.

This keeps ambient plumbing completely outside the feature. MemberInviteFeature never imports HTTP clients, authentication stores, or environment variables directly; it only interacts with the methods declared on MemberInvitePort.

The moat and the drawbridge

The port contract acts as a drawbridge: it is the single, controlled path between the host application and the feature. Everything else is a moat.

Because the feature exposes no internal controls or mutable state, the host cannot reach across. The architecture protects the feature from outside meddling by design.

The boundary is equally defended from the inside out through immediate, automated feedback:

  • The standalone demo: Boots without the application shell or live authentication, immediately exposing missing ambient providers.
  • Feature test suites: Run against in-memory mock ports instantly, failing the moment an unsupplied host dependency is introduced.
  • Module boundary linters: Tools like Nx or Sheriff block illegal cross-module imports before code can be committed.

These guardrails keep the contract intact without relying on code review vigilance. If a capability is needed from the host, we lower the drawbridge: declare it on the port, and let the host supply it.