MK
Manjit KumarFull Stack Developer
Contact
Opinion

Stop Adding Tools to Your Stack Without Understanding the Problem

17 October 20268 min read0 views
Stop Adding Tools to Your Stack Without Understanding the Problem
#Softwareengineering#Developertools#Techstack#Engineering#Cleancode#Opinion

Stop Adding Tools to Your Stack Without Understanding the Problem

A new project starts with React, Node.js, and PostgreSQL. Then Redis gets added for performance, Kafka for events, Kubernetes for deployment, and three more libraries for tasks the application barely needs.

The stack looks impressive. But does the application actually need all of it?

Choosing a technology should start with a problem, not a trending repository, a conference talk, or another developer's architecture.

This does not mean avoiding new tools. It means understanding what each tool solves, what it costs, and when it becomes useful.

1. Start With the Problem, Not the Technology

Imagine your API is slow.

Adding Redis might help, but only if repeated database reads are a significant part of the problem. If the delay comes from an inefficient SQL query, an external API, or a large response payload, Redis may not fix the root cause.

A better workflow is:

  1. Measure the current behavior.
  2. Identify the bottleneck.
  3. Find the simplest solution that addresses it.
  4. Measure the result.
  5. Introduce another tool only if the remaining problem justifies it.

For example, suppose an endpoint takes 900 ms to respond. Profiling reveals that a database query consumes 750 ms because it scans a large table.

The first step is to inspect the query plan, review the schema, and determine whether an appropriate index or query rewrite can solve the problem.

Adding a cache before understanding the query might hide the issue temporarily while introducing invalidation and consistency problems.

A tool is a solution only when it addresses the actual cause of a problem.

2. Understand the Cost of Every Dependency

Installing a package takes seconds. Maintaining it can take years.

Every dependency introduces some combination of:

  • Additional configuration and integration code.
  • Another API or abstraction the team must understand.
  • Security vulnerabilities and update requirements.
  • Version compatibility concerns.
  • Runtime overhead and operational failure modes.
  • Migration work if the dependency is eventually replaced.

Consider adding a message broker to a small application.

The broker might provide durable message delivery, buffering, and asynchronous processing. Those capabilities can be valuable, but the application must also handle broker availability, retries, duplicate messages, monitoring, and deployment.

If the only requirement is to send a welcome email after registration, a managed background-job service or a simple queue may be sufficient.

The correct choice depends on delivery guarantees, traffic, operational capacity, and future requirements—not on which architecture looks more advanced.

3. Choose the Simplest Architecture That Meets the Requirements

Consider three common decisions.

Example A: PostgreSQL or PostgreSQL + Redis?

Start with PostgreSQL when it can serve the application's query and consistency requirements.

Add Redis when measurements show a specific need, such as:

  • Caching frequently requested data.
  • Managing rate-limit counters.
  • Storing short-lived session or coordination data.
  • Supporting operations that benefit from in-memory access.

Do not add Redis merely because every backend architecture diagram seems to contain it.

A cache creates a second place where data may exist. You must decide when entries expire, how updates invalidate them, and what happens when Redis is unavailable.

If the database already responds quickly enough, the extra complexity may deliver no meaningful benefit.

Example B: A Modular Monolith or Microservices?

A modular monolith can keep related functionality in one deployable application while maintaining clear internal boundaries.

Microservices can make sense when independent deployment, team ownership, scaling requirements, or fault isolation justify separate services.

But splitting a small application into services also introduces network calls, distributed tracing, service-to-service authentication, deployment coordination, and partial failures.

Start with clear modules and explicit interfaces. Split them into separate services when there is a concrete reason to do so.

Example C: A Managed Platform or Kubernetes?

A managed application platform may be enough when the goal is to deploy an API reliably with minimal infrastructure maintenance.

Kubernetes becomes more attractive when the workload and organization need its orchestration capabilities and can support its operational demands.

Kubernetes is not automatically better because it provides more control. More control also means more responsibility.

Choose based on deployment needs, reliability requirements, team expertise, and operational cost.

4. Use Evidence to Decide When a Tool Is Necessary

Avoid decisions based entirely on assumptions such as:

  • "This database will be faster."
  • "Microservices will scale better."
  • "We need Kubernetes because the application will grow."
  • "This library is popular, so it must be the right choice."

Turn assumptions into questions that can be tested.

AssumptionWhat to investigate
The API needs cachingRequest frequency, database latency, repeated reads, and cache hit potential
The database cannot scaleQuery plans, indexes, connection usage, data volume, and resource utilization
The application needs microservicesDeployment bottlenecks, ownership boundaries, scaling differences, and failure isolation
A new framework will improve developmentExisting pain points, migration cost, ecosystem maturity, and maintainability
Kubernetes is necessaryDeployment complexity, workload requirements, availability goals, and team capacity

You do not need sophisticated observability infrastructure for every project. Start with useful logs, basic metrics, and reproducible measurements. Add more advanced monitoring when the system's needs justify it.

The important distinction is between a requirement supported by evidence and an assumption based on popularity.

5. Build a Small Proof of Concept Before Committing

When a new tool could solve a real problem, test it against a narrow use case.

Suppose you are considering Redis to reduce the response time of a product-listing endpoint.

A useful proof of concept would:

  1. Record the endpoint's baseline latency.
  2. Identify the expensive database operation.
  3. Implement caching for the relevant data.
  4. Measure response time under comparable conditions.
  5. Test behavior when cached data changes.
  6. Test what happens when Redis is unavailable.
  7. Estimate the additional operational cost.

Do not measure only the best-case response time. Consider cache misses, stale data, memory limits, and failure behavior too.

For a library or framework, test the functionality you actually need. Check documentation quality, maintenance activity, compatibility, licensing, and whether the tool makes the code easier to maintain.

A small experiment can prevent a much larger migration later.

6. Avoid Two Opposite Mistakes

There are two common extremes.

Mistake 1: Adding a Tool for Every Problem

A separate library or service is introduced for every small requirement.

The result can be a stack with duplicated functionality, excessive abstractions, more deployment steps, and a high maintenance burden.

The solution is not to reject every tool. It is to require a clear reason for each addition.

Mistake 2: Refusing to Adopt Better Tools

The opposite approach is keeping an unsuitable implementation because changing it feels risky.

For example, a synchronous request path may become unreliable when a workload requires durable asynchronous processing. At that point, introducing a queue may be the appropriate engineering decision.

Avoiding dependencies at all costs is not simplicity. Simplicity means having the least complexity necessary to satisfy the requirements.

7. A Practical Checklist Before Adding a Tool

Before introducing a framework, database, service, or library, answer these questions:

  • Problem: What specific issue am I solving?
  • Evidence: How do I know this issue exists?
  • Alternatives: Can the existing stack solve it adequately?
  • Trade-offs: What new failure modes or maintenance tasks will this introduce?
  • Operations: Can I deploy, monitor, debug, and update it?
  • Security: What permissions, secrets, data, or attack surface does it add?
  • Exit plan: How difficult would it be to replace or remove it?
  • Success criteria: What measurable result would justify keeping it?

If you cannot explain why the tool is needed, postpone the decision until you can.

You do not need a detailed document for every small dependency. For major infrastructure or architectural decisions, recording the reason and trade-offs can prevent the same debate from recurring.

8. Your Stack Should Reflect Your Requirements

A practical backend might begin with:

  • Node.js and Express for HTTP handling.
  • PostgreSQL for persistent relational data.
  • A migration tool for controlled schema changes.
  • Automated tests for important behavior.
  • Logs and basic monitoring.
  • A straightforward deployment process.

That may be enough for a substantial application.

As real requirements emerge, you might add Redis for caching, a queue for background jobs, object storage for uploaded files, or an orchestration platform for more demanding deployments.

There is no universal stack that is correct for every application. Even the examples above should change when the data model, workload, reliability requirements, or team constraints change.

The goal is not to keep the stack small forever. The goal is to let the stack grow in response to actual needs.

Conclusion

A strong developer is not someone who can name the most frameworks, databases, and infrastructure tools.

A strong developer can explain why a technology belongs in a system, what trade-offs it introduces, and how to determine whether it is working.

Learn new tools. Experiment with them. Understand their internals when the problem calls for it.

But before adding another dependency, ask one question:

What concrete problem does this solve that my current system cannot solve adequately?

If you can answer that question and justify the trade-offs, adding the tool may be the right engineering decision. If you cannot, keep investigating before adding more complexity.

Share

© 2026 Manjit Kumar - All rights reserved

Last updated

With Love, By Manjit.

;