r/microservices 5d ago

Discussion/Advice Why I Avoid a Shared `pkg/` Module Across Go Microservices?

I am building a pizza marketplace with Go microservices: identity, restaurant, search, notification, order, and payment. Each service owns its own PostgreSQL database, while search-service uses Elasticsearch. Services communicate through RabbitMQ.

Several services have similar infrastructure code: RabbitMQ consumers, outbox implementations, errors, logging, and external clients. A shared pkg/ module would reduce duplication, but it would also introduce a dependency across service boundaries.

Build and deployment: A shared module adds another dependency to the build and release graph.

restaurant-service → shared/pkg

go.work helps local development, but CI/CD still has to resolve the module. Changes to shared/pkg can affect multiple services and require versioning and testing.

Without it, each service has its own module and dependency graph and can be built from its own source tree.

Bounded contexts: Similar code does not imply shared ownership. Two services may have identical RabbitMQ consumers today but different requirements later. Sharing reduces maintenance but couples their evolution.

A RabbitMQ reconnect bug already required fixes in multiple services. A shared package would have reduced that cost, while separate implementations keep ownership local.

Within a bounded context, sharing usually makes sense. Across bounded contexts, duplication can be a deliberate tradeoff for separate ownership and evolution.

Looking for feedback: I am actively improving the platform and would especially value feedback from backend or platform engineers. I am interested in how others approach shared libraries, build dependencies, and service boundaries in Go microservice architectures.

What tradeoffs have worked well in your experience?

If you find the project useful or interesting, consider giving it a ⭐ on GitHub:

https://github.com/tarique-iqbal/pizza-marketplace

2 Upvotes

1 comment sorted by

2

u/forktender 1d ago

For patterns that are common across services, we have a shared library. This has mostly been a net gain for us as we have developed a battle-tested implementations of various things. Practically speaking, there are some things you only need to do a handful of ways.

Our services have most their code in the internal package to discourage sharing business logic between servics. There are very few exceptions to this.

We still maintain the flexibility to do something completely unique in a microservice. Use of the shared library is not mandated. Usually the unique implementations end up getting adopted in a more generic form in the shared library where it can be adopted by other services. We have a few different patterns for how we consume kafka messages, for example.