Chapter 8: Reuse Patterns
saht reuse-patterns distributed-architecture sidecar shared-library service-mesh
Status: Notes complete
Overview
Code reuse is a fundamental engineering principle, but in distributed architectures it becomes significantly more complex. In a monolith, reuse is trivial — call a method, share a module. In a distributed system, “reuse” can mean anything from copying code to deploying a dedicated runtime service, and each approach carries dramatically different trade-offs in change management, performance, fault tolerance, and operational complexity.
Chapter 8 asks: what is the right mechanism for sharing code across services? The authors present four reuse patterns — Code Replication, Shared Library, Shared Service, and Sidecars/Service Mesh — and provide a framework for choosing among them. The central insight is that not all shared code is alike: infrastructure concerns (logging, monitoring, circuit breakers) have very different reuse characteristics than shared domain logic.
A key principle introduced: reuse via platforms — the idea that as a system matures, many shared concerns should be absorbed into the platform (infrastructure, service mesh) rather than owned by individual services or shared libraries.
Core Concepts
| Term | Definition |
|---|---|
| Code replication | Copying shared code into each service that needs it; no runtime or build-time dependency |
| Shared library | A versioned, compiled artifact (JAR, npm package, NuGet package) that services depend on at build time |
| Shared service | A runtime service that encapsulates shared logic and is called via network by other services |
| Sidecar | A separate process/container deployed alongside each service, handling cross-cutting infrastructure concerns |
| Service mesh | A dedicated infrastructure layer (e.g., Istio, Linkerd) that manages service-to-service communication using sidecars |
| Change risk | The probability that a change to shared code will break consumers |
| Dependency management | The practice of controlling which versions of a shared library each service uses |
| Semantic versioning | Version numbering (MAJOR.MINOR.PATCH) that communicates the nature of changes: breaking/non-breaking/bug-fix |
Pattern 1: Code Replication
Description
The simplest approach: copy the shared code into every service that needs it. There is no shared artifact, no runtime dependency, no build-time coupling — each service owns its copy of the code.
When Replication Makes Sense
- The shared code is small and unlikely to change (e.g., a simple utility function)
- The code is used by only two or three services
- Divergence is acceptable — each service may legitimately need to customize the logic for its context
- The teams owning the services prefer zero coupling over consistency
Trade-offs
| Dimension | Assessment |
|---|---|
| Simplicity | Very high — no infrastructure needed, no versioning, no dependency management |
| Independence | Maximum — each service is completely decoupled from others |
| Consistency | Very low — copies diverge over time; bug fixes must be manually applied to all copies |
| Maintenance | Poor at scale — updating shared behavior requires touching every service |
| Governance | Poor — no central control; drift is invisible |
Risks
- Silent divergence: copies of code drift apart without anyone noticing, until behavior inconsistencies surface in production
- Bug proliferation: a bug fixed in one copy must be manually propagated to all others — a process that is slow, error-prone, and easily forgotten
- Does not scale: acceptable for 2-3 services sharing trivial code; unacceptable for 20 services sharing non-trivial business logic
When to Avoid
Avoid code replication when: the shared logic is non-trivial and likely to require bug fixes, when behavioral consistency across services is a business requirement, or when there are more than 2-3 consumers.
Pattern 2: Shared Library
Description
Extract shared code into a versioned, deployable artifact — a library — that services import as a build-time dependency. Examples: a Java JAR in a Maven/Gradle repository, an npm package, a NuGet package, a Python PyPI package, a Go module.
Versioning Strategies
The key challenge with shared libraries is managing change — how does a new version of a library reach all consumers without breaking them?
Three common versioning strategies:
-
Versioned release (pinned version)
- Each service pins to a specific version:
my-library:2.3.1 - Services upgrade on their own schedule — no forced upgrade
- Risk: consumers lag far behind the current version; support burden grows
- Good for: stable libraries with infrequent releases
- Each service pins to a specific version:
-
Semantic versioning with deprecation policy
- Follow SemVer (MAJOR.MINOR.PATCH): breaking changes = major bump
- Maintain a deprecation policy (e.g., “old major versions supported for 6 months after new major release”)
- Services must upgrade before deprecation deadline
- Good for: widely-used libraries with active development
-
Single version (no versioning)
- All consumers must use the current version — no pinning
- Library changes are backward-compatible always, or all consumers are updated in sync
- Good for: single-team ownership of all consumers (rare in large distributed systems)
Change Control Challenges
The hardest problem with shared libraries: who owns the library, and what is the process for changing it?
- If one team owns the library and many teams depend on it, the owning team becomes a bottleneck
- If any team can change the library, breaking changes can impact many consumers unexpectedly
- The solution is a change control process: breaking changes require advance notice, versioning, migration guides, and consumer sign-off before release
Trade-offs
| Dimension | Assessment |
|---|---|
| Simplicity | Moderate — requires artifact repository, versioning discipline, dependency management |
| Independence | Good — services can upgrade on own schedule (with pinning) |
| Consistency | Good when pinned versions are current; poor when consumers lag significantly |
| Performance | Excellent — in-process call, no network overhead |
| Change management | The core challenge — requires versioning discipline and governance |
| Deployment coupling | Low for consumers; the library itself requires no deployment (build-time only) |
| Fault tolerance | Excellent — no network call; in-process |
When to Use Shared Library
- Shared code is infrastructure/utility in nature (logging, retry logic, serialization helpers, common DTOs)
- Shared code is domain logic that is stable and changes infrequently
- The shared code must be performant (cannot afford a network call)
- Services are written in the same language/platform (cross-language libraries are extremely difficult)
- You have the governance discipline to maintain versioning and change control
Limitations
- Does not work well across multiple languages/runtimes — each language needs its own library implementation
- Cannot enforce runtime upgrades — consumers may stay on old versions indefinitely
- Dependency version conflicts: services with complex dependency trees can experience version conflicts when multiple libraries transitively depend on different versions of the shared library
Pattern 3: Shared Service
Description
Extract shared logic into a standalone runtime service that other services call via the network (HTTP, gRPC, message queue). The shared service is independently deployed, scaled, and owned.
When Shared Service Makes Sense
- The shared code contains runtime-variable behavior (e.g., logic that changes based on live configuration, feature flags, or database state)
- The shared code needs to be polyglot — usable across services written in different languages
- The shared logic has significant computational requirements that justify independent scaling
- The shared logic requires independent security controls (e.g., it accesses sensitive data)
- Centralized control of a shared business function is a strong requirement
Trade-offs
| Dimension | Assessment |
|---|---|
| Language agnostic | Excellent — any service that can make an HTTP/gRPC call can consume it |
| Change management | Good — API versioning; consumers are insulated from implementation changes |
| Runtime updatability | Excellent — change the service, all consumers get the new behavior without redeploying |
| Performance | Poor — every call crosses a network boundary; adds latency (10-100ms per call) |
| Scalability | The shared service becomes a potential bottleneck if not scaled correctly |
| Fault tolerance | Poor — the shared service is a single point of failure; if it goes down, all consumers are impacted |
| Operational complexity | High — requires its own deployment pipeline, monitoring, SLA, scaling policy |
The Fault Tolerance Problem
The shared service pattern introduces a critical fault tolerance concern: it becomes a shared dependency point. If the shared service fails or becomes slow:
- All consumers are affected simultaneously
- A bug in the shared service can cascade to many services at once
- Network partitions between the consumer and shared service block functionality
Mitigation: circuit breakers on all consumers, redundant deployment (multiple instances, multiple availability zones), comprehensive health checks, and a clear SLA ownership model.
The Performance Problem
Every call to the shared service crosses a network boundary. In scenarios where the shared logic is called frequently (e.g., an authorization check on every API call), the cumulative latency impact can be significant. Caching results on the consumer side can mitigate this but introduces cache invalidation complexity.
Change Risk
Compared to a shared library, a shared service has higher change risk in one sense: a buggy deployment of the shared service immediately affects all consumers at runtime — there is no version pinning to protect consumers. This requires robust testing, canary deployments, and backward-compatible API changes.
When to Avoid Shared Service
Avoid when: the shared logic is called on the critical performance path (high-frequency, low-latency requirement), when the shared logic is simple enough that a library suffices, or when the operational overhead of an additional service is not justified by the sharing requirement.
Pattern 4: Sidecars and Service Mesh
Description
The sidecar pattern deploys a separate, lightweight process alongside each service (in the same pod in Kubernetes, or the same VM) to handle cross-cutting infrastructure concerns. The sidecar handles concerns like: logging, metrics collection, distributed tracing, service discovery, health checking, circuit breaking, mTLS encryption, and rate limiting.
A service mesh (e.g., Istio, Linkerd, Consul Connect) is a platform that coordinates all sidecars across the system — providing a control plane for configuring network policies, observability, and security uniformly across all services.
What Problem Does This Solve?
Without sidecars, every service must implement infrastructure concerns directly:
- Each service imports a logging library AND a metrics library AND a tracing library AND a circuit breaker library
- When you want to add a new infrastructure capability (e.g., distributed tracing), every service must be updated and redeployed
- If services are polyglot, you need equivalent libraries in every language
- Infrastructure libraries pollute service codebases with non-domain code
The sidecar solution:
- Infrastructure concerns are delegated to the sidecar process
- The service code handles only domain logic — no infrastructure imports needed
- Infrastructure upgrades are done by updating the sidecar, not the service
- Works uniformly across polyglot services — the sidecar is language-agnostic
Sidecar vs. Shared Library vs. Shared Service
| Concern | Shared Library | Shared Service | Sidecar |
|---|---|---|---|
| Type of logic | Business or infra | Business | Infrastructure only |
| Coupling | Build-time | Runtime (network) | Runtime (local IPC) |
| Language support | Same language only | Any language | Any language |
| Performance | In-process (best) | Network call (worst) | Local IPC (very fast) |
| Update mechanism | Library upgrade + redeploy | Deploy new service version | Update sidecar (no service redeploy) |
| Fault coupling | None | High (shared failure point) | None (sidecar is local) |
| Operational overhead | Low | High | Medium (platform manages it) |
What the Service Mesh Adds
The service mesh control plane provides:
- Centralized policy enforcement: apply a new circuit-breaking policy to all services without touching service code
- Uniform observability: all traffic flows through sidecars → complete topology visibility, latency metrics, error rates
- mTLS everywhere: mutual TLS between all services enforced at the sidecar layer — no service-level certificate management
- Traffic management: canary deployments, A/B testing, traffic shifting controlled at the mesh level
- Security policies: fine-grained authorization rules (service A can call service B on endpoint X only)
Trade-offs
| Dimension | Assessment |
|---|---|
| Infrastructure coupling | Near zero — services are unaware of infrastructure concerns |
| Language agnostic | Excellent — sidecar speaks to service via localhost |
| Performance | Very good — local IPC (loopback), not a network call |
| Operational overhead | High upfront — service mesh is complex infrastructure to operate |
| Update agility | Excellent — update infrastructure without touching service code |
| Fault isolation | Excellent — sidecar failure affects only one service (not a shared dependency) |
| Learning curve | High — Istio/Linkerd are complex platforms |
| Standardization | Very good — enforces uniform behavior across all services |
When to Use Sidecars
- The system has many services (10+) — amortizing the mesh overhead is only worthwhile at scale
- Services are polyglot — a single infrastructure library cannot serve all languages
- Infrastructure concerns are evolving rapidly — you want to update logging/tracing/security without touching service code
- The organization wants to enforce uniform standards (security, observability) across all services without relying on every team to implement them correctly
- Compliance and security requirements demand uniform mTLS and audit logging
When to Avoid Sidecars
- Small number of services — the mesh overhead is not justified
- Homogeneous language environment — a shared library may be simpler and sufficient
- Team lacks platform engineering expertise to operate a service mesh
The Four Patterns Compared
Summary Trade-off Table
| Pattern | Best For | Performance | Change Mgmt | Lang Support | Fault Risk | Complexity |
|---|---|---|---|---|---|---|
| Code Replication | Tiny, stable, divergence-OK code | Excellent | Very poor | All | None | Very low |
| Shared Library | Stable logic, same-language, performance-critical | Excellent | Moderate | Same language | None | Moderate |
| Shared Service | Runtime-variable logic, polyglot, independent scale | Poor (network) | Good | All | High (shared SPOF) | High |
| Sidecar/Mesh | Infrastructure concerns, polyglot, large systems | Very good (IPC) | Excellent | All | Low (local) | Very high |
Decision Flowchart
What kind of code needs to be shared?
│
├── Infrastructure concern (logging, tracing, circuit breakers, mTLS)?
│ └── → Use SIDECAR / SERVICE MESH (if at scale)
│ └── If small scale → Use SHARED LIBRARY (infrastructure library)
│
└── Domain / Business logic?
│
├── Small, stable, few consumers, divergence acceptable?
│ └── → Consider CODE REPLICATION
│
├── Stable, same language, performance-critical, good governance?
│ └── → Use SHARED LIBRARY
│
├── Runtime-variable, polyglot, needs independent scaling/security?
│ └── → Use SHARED SERVICE
│ └── But: add circuit breakers, plan for SPOF, accept latency cost
│
└── Extremely performance-critical AND polyglot?
└── → No perfect answer; consider language-specific libraries per runtime
When Does Reuse Add Value? — Reuse via Platforms
The authors make an important distinction: not all reuse is equal. Reuse adds value when it:
- Prevents inconsistent behavior that would cause bugs or compliance failures
- Reduces operational burden (teams don’t each implement logging/monitoring from scratch)
- Enforces standards (security, observability) uniformly
Reuse does not add value — and may harm the system — when it:
- Couples services unnecessarily (shared library with frequent breaking changes)
- Creates shared points of failure (shared service called by everything)
- Forces homogeneity where diversity is appropriate (services legitimately need different behavior)
The mature answer for infrastructure reuse is platforms: invest in a service mesh, a central logging platform, a tracing platform, and a secrets management platform. Teams consume these platforms rather than implementing concerns individually. This is reuse through infrastructure, not code.
Sysops Squad Saga
The Sysops Squad story in Chapter 8 covers two types of shared code: common infrastructure logic and shared domain functionality.
Shared Infrastructure Logic
Context: The Sysops Squad team realizes that all services need: logging, distributed tracing, circuit breaker logic, service discovery, and mTLS. Initially implemented as a shared library, this creates problems: not all services are in the same language, the infrastructure team wants to update tracing without triggering service redeployments, and services teams are frustrated maintaining infrastructure code.
Solution chosen: Move to a sidecar pattern with a service mesh. Infrastructure concerns are extracted entirely from service code. Each service gets a sidecar proxy (the book uses the term “service plane”) that handles all infrastructure cross-cutting concerns.
Result: Service teams write only domain logic. The platform team controls infrastructure upgrades. Updating distributed tracing across all 15 services requires deploying a new sidecar version — zero service code changes.
Lesson: At sufficient scale, the sidecar/service mesh pattern eliminates a class of coupling (infrastructure-to-service) that shared libraries can never fully eliminate.
Shared Domain Functionality
Context: The customer notification logic — the rules for when to notify customers, via which channel, and with what content — is needed by multiple services: the ticket service (ticket status updates), the survey service (survey invitations), and the billing service (invoice notifications).
Options considered:
- Shared library: the notification rules change when business decisions change; deploying a library update and redeploying all consumers is cumbersome
- Code replication: risk of rules diverging across services — a customer gets inconsistent notifications
- Shared service: centralizes the notification logic; all consumers call the notification service
Solution chosen: Shared service for the domain notification logic. One notification service owns the rules for when and how to notify. Consumer services call it when they want to trigger a notification. This ensures consistency (same rules for all notification types), allows the notification rules to be updated by deploying only the notification service, and is polyglot-friendly.
Mitigation of shared service risks:
- Circuit breakers on all consumers to prevent cascading failure
- Asynchronous notification (put events on a queue; notification service processes them) to decouple notification from the critical path
Lesson: Shared domain logic that requires consistent behavior across services and changes driven by business decisions (not engineering decisions) often warrants a shared service — accepting the performance and fault tolerance trade-offs.
Key Takeaways
-
Code replication is a last resort for trivial code only: acceptable for tiny, stable utilities used by 2-3 services where divergence is fine. Fails at scale when the code changes.
-
Shared libraries work well for stable, same-language code: the dominant challenge is change management — versioning, deprecation policies, and change control processes are essential for library governance.
-
Shared services enable runtime polymorphism and polyglot support: at the cost of performance (network call), fault tolerance (shared SPOF), and operational complexity. Always add circuit breakers.
-
Sidecars decouple infrastructure from services: by handling cross-cutting concerns in a separate process, services stay clean of infrastructure code, and infrastructure can be updated independently.
-
Service mesh = coordinated sidecars: the mesh control plane enables uniform policy enforcement, observability, and security across all services — this is the mature infrastructure-as-code approach to operational concerns.
-
Infrastructure vs. domain logic requires different reuse patterns: infrastructure concerns (logging, tracing, circuit breakers) → sidecar/mesh. Domain logic (business rules, shared calculations) → shared library or shared service, depending on change rate and polyglot requirements.
-
Change management is the hardest problem in shared libraries: the discipline required (semantic versioning, deprecation policies, change control) is often underestimated. Without it, shared libraries create worse coupling than the code they were meant to consolidate.
-
The shared service is a shared point of failure: every consumer’s availability now depends on the shared service’s availability. Design accordingly — multiple instances, cross-region deployment, circuit breakers, async consumption where possible.
-
Reuse via platforms is the architectural ideal: rather than each team implementing infrastructure, invest in shared platforms (logging, tracing, service mesh, secrets management) that provide reuse through infrastructure rather than code. This is what mature platform engineering teams deliver.
-
Choosing the wrong reuse pattern creates architectural debt: using code replication for non-trivial logic creates consistency debt; using shared service for performance-critical logic creates latency debt; using shared library across polyglot services creates maintenance debt.
Related Resources
- ch07-service-granularity — granularity decisions that determine what code lives where
- ch09-data-ownership — analogous trade-offs for data reuse and data coupling
- ch11-managing-distributed-workflows — workflow patterns that interact with shared service reuse
- ch13-contracts — how consumer-driven contracts help manage shared service change risk
- README — SAHT book overview
Last Updated: 2026-05-30