Not Every Problem Needs a Microservice, a Queue, or a Cache

by Arif Ikhsanudin, Backend Developer

The Pattern-First Trap

A junior-to-mid engineer reads about microservices, message queues, and caching. They understand how each one works. They encounter a new problem. They fit the problem to the pattern rather than fitting the pattern to the problem.

The result is a system that looks sophisticated in architecture diagrams and is painful to operate. Microservices that should be a module. A Kafka topic for events that happen twice a day. A Redis cache with a 30-second TTL on data that changes every 15 seconds.

Each of these patterns has real value. Each is also misapplied more often than it is applied correctly. The skill is knowing which problems actually call for which patterns — and having the discipline to use simpler solutions when the pattern isn't justified.

The Microservice Default

Microservices solve specific problems: independent deployment of components that change at different rates, independent scaling of components with different load profiles, isolation of components with different reliability requirements, team autonomy for large organizations where multiple teams can own separate services.

They are routinely applied to problems that none of these justify. A five-person team building a product with one deployment environment doesn't benefit from microservices. They pay the full cost — distributed tracing, service-to-service authentication, network latency on what were function calls, independent deployment pipelines, testing across service boundaries — without any of the benefits, because the benefits are about organizational scale and independent deployment, not about technical correctness.

The monolith-first approach — build as a modular monolith, extract services when the justification is concrete — is both well-documented as a pattern (Sam Newman's Building Microservices endorses it) and consistently more pragmatic for small-to-medium products and teams.

The Queue Default

Message queues earn their place when temporal decoupling matters: when the producer shouldn't have to wait for the consumer, when consumers need to process at a different rate than producers emit, when at-least-once delivery semantics are required for reliability.

They are misapplied when:

  • The operation is synchronous by necessity (the user is waiting for a result)
  • The event volume is low enough that a database job table would serve identically with less operational overhead
  • The "decoupling" being achieved is between two parts of the same service that could just be function calls

A database table with a status column and a background worker polling it is not glamorous. For moderate event volumes (under a few hundred per second), it is often more operationally reliable, easier to monitor, and easier to debug than a message broker. This pattern — sometimes called "transactional outbox" — avoids the dual-write problem and requires no new infrastructure. For many use cases, it should be the default.

The Cache Default

The cache is the most reflexively applied pattern of the three. Slow? Add a cache. High database load? Add a cache. Expensive computation? Add a cache.

Caching is only useful when:

  • The same data is read significantly more often than it changes
  • The cache hit rate under real traffic will be meaningfully high
  • The staleness window is acceptable for the data being cached

A cache that is invalidated on every write is not a cache — it's latency. A cache with a 60-second TTL on data that changes every 30 seconds will serve stale data half the time. A cache on a page that generates unique content per user has near-zero hit rate.

Before adding caching, measure: what is the actual read-to-write ratio for this data? What TTL would be necessary to achieve an 80%+ hit rate? Is that TTL acceptable given the staleness implications?

The Right Default

The right default for most backend problems is the simplest design that correctly handles the required functionality at the current scale. A well-indexed PostgreSQL table with a background job. A service with a clear module structure rather than separate deployed services. A query result returned directly rather than cached.

Add the pattern when you can name the specific problem it solves. Not "this is how it's done" — the specific problem.

Microservices: when you need independent deployment or scaling of this specific component. Queues: when you need temporal decoupling, at-least-once delivery, or rate leveling for this specific integration. Caches: when this specific data has a read/write ratio and staleness tolerance that makes caching beneficial.

The Practical Takeaway

Before adding any of these three patterns to your next design, write one sentence that describes the specific problem you have today that requires it. "This is the right architecture" is not a problem statement. "The notification service causes payment timeouts when it's slow" is a problem statement that justifies a queue. If you can't complete the sentence, use the simpler design.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

How Remote Teams Manage Projects Without Chaos

Managing projects remotely can feel like herding cats. With the right approach, teams stay organized, aligned, and stress-free.

Read more

German Companies Pay €86K for Backend Engineers — What the Async Alternative Looks Like

That salary figure keeps showing up in your budget meetings. There's a version of this where you stop arguing about headcount.

Read more

What Actually Happens When SQL Executes Your Query

SQL is a declarative language, which means you say what you want but the database decides how to get it — understanding the execution pipeline explains why identical-looking queries can have wildly different performance.

Read more

No Online System Is Safe? Why Forcing Developers Into the Office Backfires

Managers love to claim “no online system is safe” as a reason to pull developers into the office. But forcing presence often drains lives more than it protects systems.

Read more