4 min read

5 Architecture Decisions I Made in My Personal Project – And Why They Matter

5 Architecture Decisions I Made in My Personal Project – And Why They Matter
Image generated with AI (DALLE3 by OpenAI)

Over the past months, I've been building a personal platform project in my free time – a system to collect, normalize, and explore book (especially manga) metadata using modern technologies.  
This project is more than a playground. It reflects how I approach software architecture, how I evaluate tools, and how I make decisions in a real-world-inspired context.

In this post, I want to share five key architectural decisions I made early on – and why they mattered for the overall structure, maintainability, and future of the system.


1. Starting with Microservices

Many developers recommend starting with a monolith for simplicity and faster progress.
I deliberately went the other way and started with microservices.

Why?

Because the responsibilities of the system are inherently distinct:

  • Importing and aggregating manga metadata from various sources
  • Cleaning and normalizing this data
  • Providing search and browse functionality via a public API
  • Allowing user-specific collection tracking and statistics
  • Offering an internal admin UI for manual corrections

Trying to cram all of this into a single codebase would have resulted in complexity growing way too fast especially for data flow and separation of concerns.

Benefits:

  • Clear service boundaries from day one
  • Easier testing and focused development per context
  • Better alignment with event-driven design
  • Option to deploy parts of the system independently

Trade-offs:

  • More DevOps setup (CI/CD, service communication, infrastructure management)
  • Slower initial progress compared to a monolith
  • Requires architectural discipline early on

Decision rationale:

I’m treating this project as both a product and a learning platform.
By embracing a modular, distributed architecture early, I’m able to refine my skills in real-world microservice challenges from deployment pipelines to observability and service evolution.


2. Event-Driven Architecture with RabbitMQ

Instead of making services call each other synchronously, I use RabbitMQ for asynchronous, event-driven communication.

Why?

The system involves multiple stages of processing:

  • Importing raw data
  • Normalizing and enriching it
  • Writing to the catalogue
  • Triggering cache invalidation or user updates

Rather than tightly coupling these steps, I publish events like:

  • book.imported
  • book.normalized
  • book.updated

This way, services can subscribe to the events they care about and ignore the rest.

Benefits:

  • Low coupling between services
  • Better scalability (services can process events at their own pace)
  • Fault-tolerance through retry and DLQ mechanisms
  • Clear workflow orchestration without central coordination

Implementation:

  • Services use pika (Python) or quarkus-amqp-client (Kotlin) to publish/consume messages
  • Topics are prefixed with their domain (manga.*, user.*)
  • All communication goes through RabbitMQ with JSON payloads
  • Events are versioned via a schema convention

Lessons learned:

  • Designing events is hard – they need to be meaningful, stable, and extendable
  • Monitoring queues is essential (dead letters, stuck consumers, etc.)
  • Correlation IDs help track flows across services

3. GitOps with ArgoCD

Instead of deploying services manually or using custom scripts, I manage my cluster with GitOps.

Why?

I wanted a reproducible, traceable, and declarative way to deploy:

  • Services
  • Databases
  • Infrastructure components (Traefik, Vault, etc.)
  • Secrets (via External Secrets Operator)

Benefits:

  • Single source of truth in Git
  • Instant rollback via Git revert
  • Full auditability of changes
  • Consistency across environments (local, staging, prod)

Setup:

  • All Kubernetes manifests live in a separate cluster-infrastructure repo
  • Each ArgoCD Application points to a Helm chart or a raw manifest directory
  • ArgoCD runs in the cluster and watches Git
  • I use dependsOn to orchestrate critical bootstrapping (e.g. cert-manager → Vault → External Secrets)

Lessons learned:

  • GitOps scales well even for small projects
  • Managing Helm + CRDs + secrets requires careful ordering
  • The discipline to only change infra via Git is worth it

4. Quarkus over Spring Boot

Although I’ve worked with Spring Boot for years, I opted for Quarkus in this project.

Why?

Quarkus is designed for cloud-native environments – and has strong advantages:

  • Faster startup times
  • Lower memory footprint
  • Native image builds (via GraalVM)
  • Better hot reload (quarkus:dev is 🔥)
  • Very modular & annotation-light

Use Cases:

  • My services are small, focused, and don't need the full Spring ecosystem
  • I care about startup time for scaling and cold starts
  • I wanted to explore native builds

Differences from Spring:

  • CDI-based instead of Spring annotations
  • Config is in application.yml (but very flexible)
  • Dev mode is faster and more enjoyable
  • No bloated magic, which makes it easier to follow execution flow

Challenges:

  • Smaller ecosystem and less StackOverflow coverage
  • Some features (like Security) require extra work

Still, I found the learning curve absolutely manageable and the performance gains are absolutely worth it.


5. GraphQL as the Primary API

Instead of building a RESTful API for the catalogue and admin interface, I use GraphQL.

Why?

The client side (upcoming Flutter app and admin UI) benefits from:

  • Precise data fetching (no over- or under-fetching)
  • Schema introspection
  • Strong typing
  • Easier composition of complex queries

Implementation:

  • Catalogue Service exposes a GraphQL API via quarkus-smallrye-graphql
  • All queries and mutations are versioned
  • Caching and pagination are handled at the GraphQL layer
  • The admin UI uses fragments for shared data patterns

Benefits:

  • Flexibility: frontends control the shape of data
  • Extensibility: new fields can be added without breaking changes
  • Tooling: VSCode plugins, schema validation, mock servers, etc.

Considerations:

  • You need to define and document your schema clearly
  • Some edge cases (e.g., file uploads, long-running queries) are trickier
  • Requires GraphQL know-how on the frontend

But overall, GraphQL fits the project perfectly and makes the API feel clean, modern, and easy to consume.


Final Thoughts

These decisions weren’t made just for fun they were deliberate, based on experience, trade-offs, and a desire to grow as a software architect.

Some might be overkill for a solo side project. But for me, this project is a personal lab and these architectural foundations make it worth building on.

I’m planning to cover Quarkus, GitOps and event-driven architecture in more detail in future posts soon.