Microservice or shared library? A decision framework

Whenever shared logic appears across several products, the question often arrives too early: "Should we extract this as a microservice?"

The more useful question is less flashy:

Important

What specific problem am I trying to solve: reuse, secrets, compute, centralized deployment or organizational isolation?

Decision map

flowchart TD start["I have shared logic"] --> client{"Must it run in client/offline?"} client -->|Yes| library["Shared library"] client -->|No| secrets{"Does it handle secrets, keys or heavy compute?"} secrets -->|Yes| service["Microservice"] secrets -->|No| consumers{"How many real consumers exist?"} consumers -->|1| local["Leave it where it is"] consumers -->|2| wait["Observe and document the contract"] consumers -->|3 or more| library library --> servercase{"Is there also a server-side case?"} servercase -->|Yes| both["Library + service that imports it"] servercase -->|No| done["Done"]

The four goals that get confused

When someone says "microservice", they are often mixing different goals:

Real goal Better tool Microservice? Practical signal
Reuse logic across products Shared library Not as the first answer Same code, same semantics
Protect secrets Server-side service Yes Keys, tokens or private signatures
Run heavy compute Specialized service Yes PDF, headless browser, ML, batch jobs
Change behavior without updating clients Versioned API Sometimes Many external consumers or teams

Architecture gets clearer when these goals are separated. A microservice solves secrets, compute and centralized operations well. It does not magically solve reuse.

Quick comparison

Shared library

Use it when logic is pure, portable and needs to run near the consumer.

  • Local render.
  • Validators.
  • Parsers.
  • Secret-free transformations.

Microservice

Use it when runtime matters or the client must not see what happens.

  • Private keys.
  • Deploys.
  • Server-side exports.
  • Billing, licenses and audit.

Both

Use both when there is a pure core and a server-side case wrapping it.

  • Render core.
  • Exports API.
  • Publishing service.
  • Portal previews.

Runtime question

The first question is always where the logic must execute.

Required runtime Implication
Browser You cannot protect the code. You can package it better, not hide it.
Offline desktop You need a local library. A service would break offline mode.
Server You can protect secrets and control deploys.
Worker or job You can isolate cost, memory and permissions.
Warning

"Let's make it a microservice to protect the code" is incomplete. If the client needs to execute that logic, the code will still be in the client.

Real case: Markdown rendering in a multi-product platform

The enriched Markdown renderer looked like a microservice candidate because several products used it. Applying the framework:

Question Answer Decision
Must it run offline in the editor? Yes Library
Is it pure logic without secrets? Yes Library
Are there more than three consumers? Yes Extract package
Is there a server-side case? Yes, exports and publishing Service that imports the library
Does the service have its own secrets? Yes, deploys, quotas, third-party credentials Microservice for exports and publishing

Result:

flowchart LR core["Render core<br/>Markdown to HTML"] --> editor["Offline editor"] core --> web["Web reader"] core --> dashboard["Dashboards"] core --> renderService["Render service"] renderService --> outputs["PDF / HTML / Word"] publish["Publishing service"] --> renderService publish --> edge["Edge / object storage"]

Using both library and service is not a contradiction. The contradiction would be turning a pure function into a remote API and breaking offline mode just to call it architecture.

When the service is the answer

There are cases where a shared library is not enough:

Case Why it needs a service
Deploy to edge or static hosting The client must not hold deploy tokens
License signing The private key belongs on the server
Reliable PDF export Render engine, fonts and runtime need control
Billing Subscription state is authoritative on the server
Audit Logs must be central, signed and not client-editable
Tip

If you can write a pure function with clear input and output, start with a library. If you need permissions, secrets, authoritative state or infrastructure, start with a service.

Anti-patterns

Signs of a premature microservice
  • There is only one real consumer.
  • The logic must run offline.
  • There are no secrets or heavy compute.
  • The API would be a remote call around a pure function.
  • The service only exists because it "looks cleaner".
  • Latency and deployment cost exceed maintenance savings.
Signs that a required service is hidden inside a library
  • The library needs third-party tokens.
  • The client could bypass quotas or plans.
  • The result must be audited.
  • The computation consumes too much memory or CPU for the client.
  • Every consumer reimplements permissions, billing or deployment.

Operational rule

Before moving a piece, I write this sentence:

Template Good example Bad example
"I extract it to X to solve Y in Z runtime" "I extract it to a service to sign licenses with a private key on the server" "I extract it as a microservice to protect the code"
"I extract it to a library to share Y across Z consumers" "I extract it to a library to render Markdown the same way in editor, web and dashboard" "I extract it because it is duplicated twice"

The rule I keep

Do not choose between microservice and library by fashion. Choose by runtime, secret, compute, number of consumers and contract.


This article is part of my architecture notes. If you are interested in this topic, follow the blog or contact me.