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

Spotify and Klarna Set the Bar. Every Other Stockholm Startup Fights for the Same Backend Talent

Your backend candidate loved the interview. Then Spotify called. You never heard from her again.

Read more

Designing with Java Enums — When They're the Right Model and When They're Not

Java enums are more capable than most developers use them for, but that capability has limits. Here is a clear-eyed look at what enums do well, where they break down, and the design decisions that determine which side you end up on.

Read more

Why Versioning Your API From Day One Saves You Pain Later

Skipping API versioning early feels faster, but it locks you into brittle contracts. Starting with versioning from day one keeps you flexible when real-world changes inevitably arrive.

Read more

Lockheed, Boeing and Raytheon Set Denver's Backend Salary Bar — Startups Cannot Clear It

Denver's defense and aerospace industries pay for backend engineering talent at a scale most startups can't match. The hiring market reflects it.

Read more