A Remote Is Not a Component Library
The fact that two remotes need the same or a similar feature does not justify a horizontal dependency between them.
A remote is an independently runnable application and an explicitly owned domain area. It is not a runtime SDK, a component library, or a provider of internal services for other remotes.
This distinction becomes uncomfortable as soon as a feature is needed for the second time. From an implementation perspective, the answer often seems obvious:
This feature already exists. Why don’t we simply reuse it?
The question is valid. The obvious answer is not always the right one.
Before sharing an existing implementation, first establish whether the underlying domain capability is actually the same. Two interfaces can look identical today and change for entirely different domain reasons tomorrow.
Only then does it make sense to choose among three options:
- deliberate separate implementations,
- a shared library,
- an independently owned capability.
None of these options is inherently more mature than the others. They solve different problems and create different forms of coupling.
This Feature Already Exists
Section titled “This Feature Already Exists”Assume that a calendar remote already contains a patient picker. Users can search for patients and select one from a result list.
Now the billing remote also needs a patient picker.
The seemingly simple solution looks like this:
Remote B │ └── uses the patient picker from Remote ATechnically, Remote B could import source code directly from Remote A’s project, call a published runtime API, load a fragment from Remote A, or even embed the entire remote. The specific form changes the type of coupling, but not the fundamental architecture decision.
A source-code import creates build and version coupling: Remote B reaches into the internals of another domain project. A runtime API or embedded fragment additionally couples availability, activation, and lifecycles.
In every variant, Remote B becomes horizontally dependent on Remote A.
Local development and isolated testing of Remote B may now require code, contracts, or even a running instance of Remote A. Rollbacks and different activation states become harder to manage. Failures can no longer be assigned clearly to the consuming or supplying remote.
Most importantly, the responsibility of Remote A changes.
The calendar team no longer develops only the calendar. It must now provide a stable contract for external consumers. Internal components, services, or data models effectively become public APIs. Changes that would be harmless inside the calendar can now break other remotes.
A domain area quietly becomes a platform for other domain areas.
This often happens without a deliberate decision. The first implementation location becomes the supplier simply because the code already exists there. Technical availability replaces a conscious ownership decision.
An application does not become a library merely because one of its features is also needed elsewhere.
Is It Really the Same Domain Capability?
Section titled “Is It Really the Same Domain Capability?”The claim that two remotes need the exact same domain capability should first be treated as a hypothesis.
Consider patient selection in three areas.
In the calendar, a patient is selected to schedule an appointment. In billing, a patient is selected to assign an invoice or payer. In documentation, a patient is selected to create or file a document.
At first, all three interfaces may look almost identical:
- a search field,
- a result list,
- name and date of birth,
- perhaps insurance status.
This visual similarity says little about their domain meaning.
For appointment scheduling, it may matter whether the patient can be treated at the selected facility, whether appointment warnings exist, or whether the patient is associated with a particular practitioner.
For billing, the relevant concerns may be the payer, existing billing blocks, invoice status, or specific insurance information.
For documentation, access rights, retention rules, the legally formatted name, or the document context may determine whether and how a patient can be selected.
The differences therefore go beyond the additional information shown on screen. The reasons why each feature changes are different as well.
A new billing rule may affect only patient selection in billing. A new document-retention rule may affect only documentation. A restriction on scheduling must not automatically alter patient selection in other areas.
The same appearance is not evidence of the same domain capability.
The decisive question is therefore:
What happens when both remotes receive conflicting requirements for the supposedly shared feature tomorrow?
If the shared component must support both variants, its interface quickly grows to include optional parameters, domain flags, and consumer-specific callbacks. Different models are merged into common types. Switch logic decides which domain context is currently active.
The implementation may still be delivered only once, but its meaning is no longer shared. It now contains several domain variants whose differences are hidden behind a technical abstraction.

Deliberate Duplication Can Be the Cleaner Decision
Section titled “Deliberate Duplication Can Be the Cleaner Decision”Duplication is often treated reflexively as a defect in software development. This creates a strong incentive to abstract common elements as early as possible.
At domain boundaries, that can be the more expensive decision.
When patient selection has different meanings in calendar and billing, both remotes may own separate implementations:
Calendar Remote└── own patient picker for appointment scheduling
Billing Remote└── own patient picker for billingBoth implementations may use the same design tokens, input fields, table components, or technical search mechanisms. That does not require them to share the same domain component.
Three levels should be considered separately:
- shared appearance,
- shared technical building blocks,
- shared domain meaning.
A design system can enforce a consistent appearance. Recurring technical mechanisms can live in technical libraries. Neither implies that the domain-specific selection flow should also have one shared implementation.
Deliberate separate implementations are particularly reasonable when the features change for different reasons, different teams own them, they use different backend contracts, or domain divergence is likely.
The additional code is not automatically waste. It buys the ability to change independently.
This is not a licence for uncontrolled copy and paste. The decision to duplicate should be deliberate and documented. The implementations may then evolve in different directions. There is no obligation to keep them permanently synchronized.
Should meaning, behavior, and reasons for change prove genuinely identical over a longer period, a shared abstraction can still emerge later. Consolidating later is usually easier than undoing prematurely shared responsibility.
Additional implementation effort can be economically preferable to permanent coordination across several areas of responsibility.
Similarity Is Not Yet an Abstraction
Section titled “Similarity Is Not Yet an Abstraction”A shared abstraction should express a stable commonality. It should not merely preserve the current similarity of two implementations.
Domain-specific UI components often appear more universal at first than they really are. The first version supports one consumer. The second consumer needs an additional option. The third requires a callback at a point that was originally an internal part of the workflow.
Configuration grows with every consumer:
Patient Picker├── Calendar Mode├── Billing Mode├── Documentation Mode├── optional warnings├── replaceable result view├── callback before selection├── callback after selection└── special cases per remoteThe abstraction does not remove the differences. It moves them into a larger property API, flags, and conditional flows.
Once central types begin mixing several domain languages, or no participating team can reject a requirement, reuse has become a shared negotiation point.
An abstraction should express shared stability, not preserve temporary similarity.
When a Shared Library Makes Sense
Section titled “When a Shared Library Makes Sense”A shared library is a plausible solution when the same stable implementation is genuinely meant to be shared.
With technical libraries, this is often relatively easy to justify. Logging, telemetry, design tokens, base components, technical error handling, or an authentication client abstraction deliberately expose broad and stable contracts. Their meaning does not depend on the individual domain consumer.
Formatting functions, internationalization mechanisms, or clearly scoped protocol types can also be shared sensibly.
A domain shared library requires more.
It is appropriate only when its semantics are truly identical for every consumer. Changes must arise for the same domain reasons. Every consumer must require the same behavior. It must also be clear who decides on the shared contract.
One example could be a calculation algorithm defined unambiguously by law whose meaning is identical in every area that uses it. A domain validator may also be shared when it represents exactly the same rule everywhere and must not be interpreted differently by context.
A domain shared library is not a neutral storage location. It is a shared product with its own contract.
That includes rejecting requirements that apply to only one consumer. Otherwise, the shared implementation once again grows into a collection of domain variants.
A Shared Library Does Not Eliminate Coupling
Section titled “A Shared Library Does Not Eliminate Coupling”A shared library can avoid a direct runtime dependency on another remote:
Remote A ─┐Remote B ─┼── Shared LibraryRemote C ─┘No remote must wait for another at runtime. Local development and operation remain separate.
But not all coupling has disappeared.
Consumers still depend on library versions. Changes must remain compatible or be migrated deliberately. New releases require testing against several consumers. Breaking changes require a decision on whether all remotes must update together or whether several versions will be supported.
The project graph also becomes broader. When a small change to a shared library marks numerous remotes and hosts as affected, that is not merely a build-system problem. It reveals the reach of the shared dependency.
A shared library removes runtime coupling. It does not automatically remove organizational or domain coupling.
The more central the library becomes, the greater its influence on release decisions, prioritization, and technical migrations. A change may be locally small and still require broad coordination.
No runtime coupling does not yet mean independent evolvability.
“Shared” Is Not an Ownership Model
Section titled ““Shared” Is Not an Ownership Model”As soon as several remotes use a library, questions arise that cannot be answered by placing code in a shared folder:
- Who owns the library?
- Who decides on changes?
- Who prioritizes consumer requirements?
- Who assesses breaking changes?
- Who publishes new versions?
- Who supports older versions?
- Who decides when requirements conflict?
- May a consumer remain on an older version?
- Who is allowed to reject a requirement?
A shared library does not eliminate ownership. It centralizes it.
Without a clear owner, it often turns into a common dumping ground:
shared-domain├── patient-picker├── billing-rules├── calendar-mapper├── document-validator└── assorted special casesEveryone uses the library, but no one owns it completely. Changes require broad coordination. Breaking changes are avoided because nobody wants to be responsible for migrating every consumer. Special cases accumulate because removing an existing variant appears riskier than adding one more option.
Eventually, the library becomes harder to change than any individual consumer.
The word “shared” merely describes that several areas use something. It does not answer who is responsible for it.
The First Implementation Location Does Not Determine the Owner
Section titled “The First Implementation Location Does Not Determine the Owner”The patient picker may have originated in the calendar. Later, billing and documentation need a similar capability as well.
That does not mean the calendar team should permanently own the shared patient picker.
The team may have no domain responsibility for billing or documentation. It may have no mandate to operate a platform for other areas. Its release priorities are shaped by the calendar. External requirements therefore inevitably compete with the team’s actual goals.
Nevertheless, an expectation quickly emerges that the calendar team should stabilize, document, version, and support the component for other consumers. The historical accident of where the first implementation happened becomes a permanent organizational structure.
Historical accident is not a sustainable ownership model.
When a feature is genuinely meant to become shared, its ownership must be decided again deliberately. It may remain with the original team if its mandate and responsibility are expanded accordingly. It may instead move to another team, a platform owner, or an independent capability.
The existing code does not answer that question.
When the Feature Becomes an Independent Capability
Section titled “When the Feature Becomes an Independent Capability”Some features genuinely form an independent domain capability and are needed in several contexts.
A comprehensive patient search, for example, may be far more than a search field and result list. It may enforce permissions, handle duplicates, offer different search strategies, apply legal visibility rules, and own domain processes of its own.
The same can apply to a complex document editor, a payment flow, a legally regulated signing process, or an independent reporting area.
In such cases, a library may not be enough. The feature has its own domain lifecycle and justifies explicit ownership, independent operation, and deliberately designed integration points.
Shared Capability├── own domain contract├── clear owner├── own lifecycle├── defined integration points└── multiple hosts or usage contextsSuch a capability is not simply a fragment from Remote A imported by Remote B.
It has its own domain identity. Its contract is published deliberately. Error ownership, lifespan, versioning, and activation are defined. Integration preferably takes place at a higher composition level or through a contract designed explicitly for that purpose.
This is different from nesting one remote inside another merely because the required interface already happens to exist there.
Nested remotes couple availability and lifecycles. Routing and error ownership become harder. Local development requires several applications. For users, it is often unclear which area owns the visible end-to-end experience.
A shared capability needs its own owner. It should not accidentally belong to the first remote in which it was implemented.
Three Possible Decisions
Section titled “Three Possible Decisions”Deliberate Separate Implementations
Section titled “Deliberate Separate Implementations”Remote A → own implementationRemote B → own implementationThis option makes sense when meaning or rules differ, divergence is expected, or independent releases matter more than a centralized implementation.
Its benefit lies in clear ownership and independent evolvability. There is no shared versioning and no central contract that must serve several domain areas at once.
The cost is repeated implementation and testing effort. Defects may need to be fixed in several places. Appearance and behavior may also diverge unintentionally.
Shared Library
Section titled “Shared Library”Remote A ─┐Remote B ─┼── versioned Shared LibraryThis option makes sense when meaning and behavior are genuinely identical, the implementation is sufficiently stable, and a clear owner maintains a deliberately published contract.
It enables consistent behavior and centralized defect fixes. The implementation is maintained in one place.
The cost is version and governance coupling. Changes have broader effects. Migrations must be coordinated and consumers supported. Ownership becomes centralized.
Independent Capability
Section titled “Independent Capability”Host or Composition├── Remote A├── Remote B└── independent CapabilityThis option makes sense when the capability has its own domain lifecycle, is needed in several hosts or usage contexts, and is larger than a simple library building block.
It receives a clear domain contract, explicit ownership, and the ability to evolve independently.
The cost is additional runtime integration, deployment, and operational responsibility. Observability, error handling, and version management must be solved for another application.

Decision Questions
Section titled “Decision Questions”No fixed scoring matrix is required. A few questions can still reveal what kind of commonality actually exists:
- Is the domain meaning identical, or does only the presentation look similar?
- Do both occurrences change for the same reasons?
- Must both always behave identically?
- May they diverge in domain terms?
- Do they use the same backend contract?
- Does the feature make sense independently outside the original remote?
- Who owns the shared contract?
- Who decides when requirements conflict?
- Can consumers use different versions?
- Is a synchronized migration acceptable?
- How large is the actual implementation cost of duplication?
- How high is the long-term coordination cost of sharing?
- Is the capability substantial enough to justify its own lifecycle?
- Would a shared implementation hide domain differences?
The most important question remains:
What happens when both remotes receive different requirements for the supposedly identical feature tomorrow?
The answer often reveals more clearly than any technical analysis whether a genuinely shared capability exists.
Reuse Is an Economic Decision
Section titled “Reuse Is an Economic Decision”Duplication costs additional implementation, multiple tests, and potentially repeated defect fixes. Parallel implementations can become inconsistent and must be maintained independently.
Sharing costs coordination, versioning, governance, centralized prioritization, and consumer support. Breaking changes must be planned. Builds and tests affect more projects. The code duplication avoided today may be paid for over years through additional coordination.
An independent capability creates another set of costs: runtime integration, deployment, operation, observability, contract maintenance, and explicit error ownership.
None of these cost categories is inherently lower. They arise in different places and at different points in time.
The relevant question is therefore not how to maximize code reuse. It is which responsibility should be shared permanently.
Reuse is not free. It trades repeated implementation effort for shared responsibility.
Conclusion
Section titled “Conclusion”“We need the exact same domain capability in two remotes” is not a justification. It is an assumption that needs to be tested.
When only the interface is similar while meaning, rules, and reasons for change differ, deliberate separate implementations may preserve the cleaner domain boundary.
When semantics and lifecycle are genuinely identical and stable, a shared library may be appropriate. It still requires a clear contract, versioning, and an explicit owner.
When the capability has its own domain lifecycle, it may be owned as an independent capability and integrated at an appropriate composition level.
In none of these cases should Remote B simply become horizontally dependent on Remote A.
Share an implementation only when its meaning, lifecycle, and responsibility are meant to be shared as well.