Every team that adopts microservices eventually hits the same dilemma: several services need the same functionality. Do you package it as a shared library that each service compiles in, or extract it into a shared service that everyone calls over the network? Choose wrong and you either redeploy twenty services for every bug fix, or you add a network hop (and a single point of failure) to your hottest code path.
Both approaches are legitimate. The right answer depends on how the functionality will change, how it is used, and how much operational complexity you can absorb. Here is a practical framework.
Option 1: The Shared Library
A shared library is a versioned package of reusable code that services pull in as a dependency and execute in their own process. Think of a JAR, an npm package, or a Go module.
Typical Use Cases
- Utility functions: input validation, date/time handling, formatting.
- Shared domain models and data contracts used across services.
- Security plumbing, such as OAuth client integrations or token parsing.
Advantages of a Shared Library
- Performance: the code runs in-process. No network latency, no serialization overhead, no timeouts.
- Developer velocity: no extra service to stand up, deploy, or mock; teams iterate quickly.
- Simplicity: no additional API surface, infrastructure, or on-call burden.
Drawbacks of a Shared Library
- Painful rollouts: when the library changes, every dependent service must be rebuilt and redeployed. In a large architecture, that is a coordination project, not a patch.
- Version drift: guaranteeing that all services run the correct version is hard; stragglers on old versions cause subtle inconsistencies.
- Hidden coupling: a frequently changing or context-specific library quietly couples services that were supposed to be independent.
Option 2: The Shared Service
A shared service is a separate microservice that encapsulates common functionality behind an API, typically HTTP or gRPC, which other services call at runtime.
Typical Use Cases
- Centralized authentication: issuing and validating tokens, managing sessions.
- Notification delivery: email, SMS, push alerts.
- Centralized data processing: reporting and analytics services.
Advantages of a Shared Service
- Independent deployment: fix a bug or ship a feature once, and every consumer benefits immediately. No mass redeployment.
- Cleaner versioning: evolving one API is far easier than chasing library versions across dozens of repositories.
- Independent scaling: the service scales horizontally to match its own workload, separate from its consumers.
Drawbacks of a Shared Service
- Latency: every call crosses the network. For latency-sensitive paths, that overhead is real and cumulative.
- A single point of failure: if the shared service goes down, every consumer feels it. Resilience patterns (retries, circuit breakers, fallbacks) become mandatory.
- Operational complexity: consumers must handle network failures and timeouts, and someone has to own, monitor, and operate one more service.
A Decision Framework
Choose a shared library when:
- The functionality is small, stable, and rarely changes.
- Performance is critical and network calls must be minimized.
- You want to keep the architecture simple, without another API to manage.
Example: input validation and data-formatting helpers. They are simple, change rarely, and belong in-process. McDonald’s took exactly this approach with the producer and consumer SDKs in its event-driven platform: stable, cross-cutting plumbing packaged as libraries.
Choose a shared service when:
- The functionality evolves frequently and improvements should reach all consumers at once.
- Isolation matters for maintainability, security, or scaling.
- Critical logic such as auth, billing, or analytics must behave consistently everywhere.
Example: a centralized authentication service that issues and validates access tokens. Security logic changes often, must be uniform across the platform, and benefits enormously from having a single owner and deployment pipeline.
Three Tips Before You Decide
- Project the rate of change. Complex functionality with a high likelihood of evolution leans service; frozen utility code leans library.
- Weigh call frequency against latency budget. If many services hit the functionality on hot paths, in-process execution may be non-negotiable.
- Start simple. When in doubt, begin with a library and extract a service when the pain justifies it. Premature service extraction is a classic form of over-engineering.
Frequently Asked Questions
Can I use both approaches together?
Yes, and mature architectures usually do. A common pattern is a shared service for the core capability plus a thin client library that wraps the API, handles retries, and standardizes usage. That gets you the best of both worlds, as long as the client stays thin.
Is duplicating code ever better than sharing it?
Sometimes. If two services need superficially similar logic that will evolve in different directions, duplication preserves independence. Share what is genuinely identical and stable; duplicate what merely looks similar today.
What is the biggest mistake teams make here?
Putting volatile business logic into a shared library. Every change then forces coordinated redeployments across services, which reintroduces the monolith’s worst property into a distributed system.
Get the Architecture Right the First Time
Decisions like this one are cheap to make and expensive to unmake. At Stage28, our AI-native senior engineers have navigated these trade-offs across dozens of production systems, and we deliver fixed-scope, fixed-price projects where you pay only after delivery. If you are designing or untangling a microservices architecture, reach out and we will help you choose well.