Why this matters
REP, CCP, and CRP form a tension triangle. Satisfying all three simultaneously is impossible — each pair excludes the third:
- REP + CCPignores CRP — components grow large with classes that some users never need.
- REP + CRPignores CCP — a single feature change ripples through many components, each requiring its own release.
- CCP + CRPignores REP — components are not properly tracked and released, making reuse impractical.
The position within this tension triangle should shift as the project matures. Early in development, the team changes frequently and broadly — CCP should dominate to minimize the number of components that must change together. As the project matures and external users begin depending on its components, REP and CRP become more important. Over-engineering the component structure for reuse before the design is stable creates more release overhead than value.
The problem
Over-splitting too early — before the design is stable — means a single feature change cascades through many packages, consuming release time instead of development time.
Bad
# Young startup: 20 micro-packages created from day one to satisfy REP/CRP.
# Adding "discount to order" requires:
# - bump order_core v0.3.0
# - bump order_events v0.2.1
# - bump pricing_rules v0.4.0
# - bump discount_engine v0.1.2
# - bump cart_service v0.5.0
# - bump checkout_api v0.8.1
# - bump notification_hooks v0.3.0
# 7 release PRs, 7 CI pipelines, 7 version pins to update.
# The team spends more time on releases than on features.
// 20 packages created on week 1 of the project:
// @app/order-core, @app/order-events, @app/pricing-rules,
// @app/discount-engine, @app/cart-service, @app/checkout-api,
// @app/notification-hooks, @app/user-profile, @app/address-book ...
// One product requirement touches 7 packages.
// Each needs its own semver bump, changelog, and npm publish.
// Teams block each other waiting for upstream packages to release.
The solution
Four coarse packages aligned to change boundaries (CCP-first). One feature change touches one or two packages — extract finer packages only once stable reuse patterns emerge.
Good
# order/ — all order domain logic (changes for same reason)
# pricing/ — discount + tax rules (change per finance requirements)
# checkout/ — cart + payment flow (change per UX requirements)
# notifications/ — email/SMS (change per marketing requirements)
# Adding discount touches order/ and pricing/ — two releases, not seven.
# As patterns emerge and external reuse grows, further splitting is justified.
// Four coarse packages aligned to change boundaries (CCP first):
// @app/orders — order domain; changes with order requirements
// @app/pricing — tax + discount logic; changes with finance requirements
// @app/checkout — cart + payment flow; changes with UX requirements
// @app/notifications — email/SMS; changes with marketing requirements
// As the codebase matures and reuse patterns become clear,
// extract finer packages guided by REP and CRP — not before.
Key takeaway
The component structure that optimizes for change (CCP) is not the same as the structure that optimizes for reuse (REP + CRP). Start with change, migrate toward reuse as the design stabilises — never the other way around.