Finding the Solution

You have a clear problem statement. The stakeholders agree on what needs to be built. Now comes the part where most teams either get it right or waste three months heading in the wrong direction: finding the solution.

This is post #3 in the How It Works series. We are building a task management application, something like a simplified Trello, and using it as a running example. In the previous post, we defined the problem. Now we figure out how to solve it.

Research Before You Build

Before writing a single line of code, study what already exists. Trello, Jira, Asana, Monday.com, and dozens of other tools have already solved variations of this problem. Your job is not to ignore them. Your job is to learn from them.

Open each one. Use them. Take notes on:

  • What they do well — Trello nails simplicity. Jira handles complex workflows. Asana balances both.
  • What gaps exist — Maybe none of them integrate with your company’s internal tools. Maybe their pricing models don’t fit a small team.
  • What you can skip — If Trello already handles Kanban boards perfectly, don’t reinvent that wheel. Focus on what makes your app different.

Senior engineers spend more time studying existing systems than junior engineers expect. This research saves you from building features that already exist in better form elsewhere.

Evaluate Approaches

Once you understand the landscape, you need to make architectural decisions. For our task management app, here are the real trade-offs a small team faces:

Monolith vs. Microservices: You are a small team building an MVP. Pick a monolith. Microservices add deployment complexity, network latency debugging, and distributed system headaches that a three-person team does not need. You can extract services later when you actually hit scaling problems.

SQL vs. NoSQL: Task data is relational. Tasks belong to boards, boards belong to users, users belong to teams. PostgreSQL gives you ACID transactions, mature tooling, and JOINs that make this straightforward. NoSQL shines for unstructured data at massive scale, which is not where you are starting.

REST vs. GraphQL: REST is simpler to implement, easier to cache, and understood by every developer on the planet. GraphQL is powerful when clients need flexible queries across deeply nested data. For a task app with predictable data shapes, REST wins on simplicity.

The pattern here is clear: choose the simpler option unless you have a specific, measurable reason not to.

Choose Your Tech Stack

Your tech stack should be driven by three factors: what your team already knows, what the project requires, and what has a strong ecosystem.

For our task management app, here is a defensible stack:

  • Backend: Java with Spring Boot. Massive ecosystem, battle-tested in production at every scale, and your team has Java experience.
  • Alternative Backend: Python with Flask if your team leans Python. Faster to prototype, simpler to deploy for smaller apps.
  • Frontend: React. Component-based architecture maps naturally to cards, boards, and lists.
  • Database: PostgreSQL. Reliable, relational, and free.
  • Real-time: WebSockets for live task updates across users.

Notice that none of these choices are exotic. That is intentional. Every technology listed has years of Stack Overflow answers, production war stories, and hiring pools behind it.

Spike / Proof of Concept

Before committing to a full build, write a spike. A spike is a small, throwaway prototype that tests your riskiest assumption. For our app, the riskiest assumption is real-time task updates: when one user moves a card, every other user on that board should see it instantly.

Start with a simple REST endpoint that serves tasks, then layer WebSocket support on top.

Java Spring Boot spike — a basic task controller:

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

    private final List tasks = new ArrayList<>(List.of(
        new Task(1L, "Set up CI/CD pipeline", "IN_PROGRESS"),
        new Task(2L, "Design database schema", "TODO"),
        new Task(3L, "Write API documentation", "DONE")
    ));

    @GetMapping
    public ResponseEntity> getTasks() {
        return ResponseEntity.ok(tasks);
    }

    @PostMapping
    public ResponseEntity createTask(@RequestBody Task task) {
        task.setId((long) tasks.size() + 1);
        tasks.add(task);
        return ResponseEntity.status(HttpStatus.CREATED).body(task);
    }
}

Python Flask spike — the same endpoint:

from flask import Flask, jsonify, request

app = Flask(__name__)

tasks = [
    {"id": 1, "title": "Set up CI/CD pipeline", "status": "IN_PROGRESS"},
    {"id": 2, "title": "Design database schema", "status": "TODO"},
    {"id": 3, "title": "Write API documentation", "status": "DONE"},
]

@app.route("/api/tasks", methods=["GET"])
def get_tasks():
    return jsonify(tasks), 200

@app.route("/api/tasks", methods=["POST"])
def create_task():
    task = request.get_json()
    task["id"] = len(tasks) + 1
    tasks.append(task)
    return jsonify(task), 201

if __name__ == "__main__":
    app.run(debug=True)

Both spikes prove you can serve and create tasks through an API. The next step would be adding a WebSocket endpoint to test real-time updates with your frontend. If the spike works, you have validated your riskiest assumption. If it fails, you found out in two days instead of two months.

Document Your Decision

Every significant architectural decision should be recorded in an Architecture Decision Record (ADR). ADRs capture what you decided, why, and what the consequences are. Six months from now, when someone asks “why PostgreSQL over MongoDB?”, the ADR answers that question.

Here is the standard format:

# ADR-003: Use PostgreSQL for primary data store

## Status
Accepted

## Context
Our task management app has relational data (users, teams, boards, tasks).
The team has production experience with PostgreSQL.
We need ACID transactions for task state changes.

## Decision
We will use PostgreSQL 15 as the primary database.

## Consequences
- Positive: Strong relational model, mature tooling, team familiarity.
- Negative: Vertical scaling limits compared to distributed NoSQL.
- Mitigation: Add read replicas if query volume grows beyond single-node capacity.

Keep ADRs in your repository, usually in a docs/adr/ directory. They are version-controlled, searchable, and become part of your project’s institutional memory.

Senior Tip

Pick boring technology. The latest JavaScript framework with 200 GitHub stars is not what you want in production. Proven tools like Spring Boot, Django, PostgreSQL, and React have survived years of real-world abuse. They have known failure modes, established best practices, and communities that have already solved the problem you will hit at 2 AM on a Saturday. Boring technology lets you focus on your business problem instead of fighting your tools.

In the next post, we will take our chosen stack and start designing the system architecture: data models, API contracts, and component boundaries.




Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *