1. 18Package boundaries define the blast radius
  2. 19Turning boundaries into build speed

Delivering change

18

Package boundaries define the blast radius

Repository and package boundaries turn architectural rules into compiler constraints so teams can change UI work independently.

By Eyal Ellenbogen September 8, 2026 3 min read

A design system can have semantic tokens, documented primitives, and careful deprecation policies, but none of that protects delivery speed if every change ripples across the entire codebase. At some point, architectural boundaries have to survive Git, the package manager, and the CI pipeline.

Organizing a feature into ui, api, and demo folders helps developers navigate the filesystem, but folders alone cannot stop unwanted imports. The boundaries dissolve the moment a component imports an API client simply by typing ../../services/api.

Package and project boundaries turn architectural rules into compiler constraints. A feature may depend on shared UI kit primitives and its own port contract, but it should never reach into backend clients, the global router, or a sibling feature's internal styles. The application sits at the top: it composes features, mounts routes, and provides the adapters that satisfy ports.

Import direction

Allowed
Application → feature, adapter, kit. Feature → kit and its own port.
Refused
Feature → live client, application router, or sibling feature internals.

Packages follow the work that changes together

Every boundary in our architecture maps to a package with an explicit public API:

  • The UI kit package: Exposes clean entry points for primitives and layout. Consuming features take a versioned release, allowing us to evolve the design system without blocking products.
  • Feature packages: Export only the view the application mounts and the port contract. Local state, presentation helpers, and internal interfaces stay private—one feature never reaches into another feature's internals.
  • Adapter packages: Live in an integration layer outside the feature, ensuring the dependency arrow points strictly outward toward backend services.

Whether these live across multiple repositories or in a single monorepo is an organizational scaling decision. The architectural rule remains constant: explicit public APIs and a unidirectional dependency graph.

Application composition root

Application

Adapter

Feature

View · port contract

Kit

Primitives + design tokens

The application creates the adapter and provides it to the feature through its feature-owned port. The feature imports kit primitives, never the adapter or live client.

Don't trust humans. Fail the build.

We shouldn't ask code reviewers to police import paths. Under deadline pressure, someone will always write ../../ across a boundary, and on a busy Friday, someone else will approve it.

Tools like Nx module boundaries and Sheriff make bad imports fail before code can merge. We define which package types are allowed to depend on each other, and let linters block violations directly in the editor and in CI.

Enforcing the dependency graph at the tool level guarantees that architectural isolation stays real under delivery pressure. The blast radius ends at the package boundary.