Skip to content

From idea to architecture decision

Some projects don’t start with a kickoff, a Jira epic, or an architecture diagram. Sometimes they start with a conversation between neighbors.

In this case, it was about table tennis tournaments at the local club. More precisely: youth tournaments, where players need to be assigned, groups formed, matches scheduled, results recorded, and winners determined at the end. Sounds manageable at first. And that’s exactly what makes the idea interesting.

Because at first glance, it’s not a huge product. Not a platform. Not an ecosystem. Not an enterprise portal with fifteen roles, three tenants, and a roadmap through 2031. At first, it’s a concrete problem from the real world:

How can a club prepare a table tennis tournament more easily and run it smoothly on tournament day?

The MVP is meant to stay deliberately small at first.

Players should have records with just a few fields: first name, last name, an initial ranking value, and optionally a date of birth, in case age groups become relevant later.

You should be able to create a tournament from these players. For the start, a very concrete flow is enough: select players, form groups, distribute strong players fairly across groups based on their ranking, and assign the remaining players via drag and drop.

On tournament day itself, things get more interesting from a domain perspective. Within the groups, doubles pairings should rotate. Players don’t play with the same partner the whole time — instead, they should be mixed as fairly as possible across multiple games. Results get recorded, wins get counted, and in the end, the best players qualify for the next round.

A small live view for parents or spectators might get added later. But that’s not the starting point. The starting point is the tournament flow itself.

My first thought was obvious, but maybe also born a little too much out of habit:

New microfrontend, host + remote in the existing Nx monorepo, Angular on the frontend, NestJS on the backend, SCS structure, Keycloak realm, role model, its own services, its own database.

At first, that doesn’t sound wrong. On the contrary: the infrastructure already exists, the concepts are familiar, the existing projects in my architecture lab follow similar patterns. A new SCS would be set up quickly, and the existing CI/CD and deployment mechanisms could be reused.

But this is exactly the point where a second look pays off.

Just because an architecture is possible doesn’t mean it’s appropriate.

For this MVP, there’s initially no independent team per domain area. There’s no need to deploy individual frontend parts independently of each other. There’s also no domain reason yet to run players, tournaments, results, and history as separate services.

The product is small, the domain core is cohesive, and the most important value doesn’t lie in distributed architecture, but in a tournament flow that actually works.

The real complexity isn’t in integrating a microfrontend cleanly into a host. The real complexity is in questions like:

  • How do players get distributed fairly across groups?
  • How do rotating doubles pairings come about?
  • How easily can a referee record results on tournament day?
  • How does the flow stay understandable when things need to move fast?
  • How little personal data do we actually need?

That shifts the architecture question considerably.

The decision: start simpler, but not arbitrarily

Section titled “The decision: start simpler, but not arbitrarily”

The decision for the MVP is therefore:

No microfrontend architecture for the start. Instead, a classic Angular standalone app and a backend as a modular deployment monolith.

Concretely, that means:

  • one Angular app
  • one API
  • one backend service
  • one database
  • one clear external interface
  • but a clean domain structure internally

The important point is: deployment monolith doesn’t mean code soup.

The application gets built and operated as one unit. But within that unit, the domain areas are deliberately kept separate. Player management, tournament preparation, tournament day, match scheduling, result recording, standings calculation, and history shouldn’t end up in one big technical junk drawer.

The structure should be chosen so that a later split stays possible, if it’s ever actually needed.

Not today. Not on principle. But whenever the product gives us reasons to.

In the frontend, the application shouldn’t be organized by technical categories like components, services, and models. In growing applications, that often ends up tearing domain contexts apart.

Instead, the app is structured vertically by use case.

An initial structure could look like this:

player-management
tournament-list
tournament-setup
tournament-day
tournament-history

Each area bundles what’s relevant to that particular domain flow. Tournament setup, then, contains not just components, but also its routes, its local state, its view models, and its API connection. Tournament day gets its own area too, because different requirements apply there: fast operation, clear display, result recording, current standings.

The goal isn’t to create as many folders as possible. The goal is for a use case to stay locally understandable.

The project starts deliberately simple on the backend too.

The MVP doesn’t need a landscape of multiple services. One backend service with its own database is enough. Still, the domain logic doesn’t get mixed together arbitrarily.

One possible internal module structure:

players
tournaments
tournament-setup
match-scheduling
match-results
standings
history

These modules can be deployed together for now. They can live together in one database. But they should have clear responsibilities.

players manages players.

tournament-setup handles selection and group assignment.

match-scheduling contains the logic for rotating pairings.

match-results records results.

standings calculates standings and qualification.

history represents completed tournaments.

That’s not a distributed system. But it’s also not one big ball of randomly thrown-together logic.

This project is deliberately taking shape in my architecture lab.

Not because a small club tournament necessarily needs a big lab environment. But because a lot of things are already in place there that are valuable for experiments and small products:

  • existing infrastructure
  • existing CI/CD mechanisms
  • existing deployment experience
  • familiar Angular and NestJS conventions
  • a reusable development environment
  • a context that AI-assisted development already knows

That last point especially shouldn’t be underestimated. When a project takes shape within a familiar structure, architecture decisions, code conventions, and existing patterns can be reused more effectively. That reduces friction.

The existing infrastructure also lowers the cost. For an MVP, I don’t have to build a new platform first — I can use existing building blocks. At the same time, the project doesn’t force me to adopt the entire platform architecture.

That’s an important distinction:

Reusing infrastructure doesn’t mean having to repeat every architecture decision.

The initial stack looks deliberately unspectacular as a result:

Frontend:
Angular Standalone App
Angular Material
CDK Drag & Drop
vertical feature structure
Backend:
NestJS API
NestJS Service
PostgreSQL
modular monolith
Auth:
Keycloak, but initially only with the roles that are actually necessary
Operations:
existing lab infrastructure
existing CI/CD and Docker mechanisms

That’s not a spectacular stack. And that’s exactly the point.

At the start, the focus shouldn’t be on the architecture, but on the domain vertical slice: maintain players, create a tournament, form groups, schedule matches, record results, see the winners.

If this flow holds up, the application can grow. Maybe with a live view for parents. Maybe with better ranking logic. Maybe with multiple tournament formats. Maybe, at some point, with a stronger technical separation.

But the MVP doesn’t have to answer all of that yet.

It only has to be structured clearly enough that these questions can be answered later without having to throw everything away.

The first architecture decision in this project is, in effect, an anti-decision:

No microfrontend for the start. No artificial service landscape. No architecture just because it’s available.

Instead:

A deliberately simple deployment monolith with a clear internal structure.

That’s less glamorous than a big architecture diagram. But probably a lot healthier for this MVP.