Ai
Axnify
Terug naar de blog
Engineering

How We Built Our Microservices Architecture

Axnify Team10 april 202618 min read

Axnify runs on 20+ Go microservices, each responsible for a specific domain of the e-commerce platform. This post explains the architectural decisions behind our backend, what we learned building it, and why we think this approach works for a multi-tenant SaaS product.

Why Go?

We evaluated three languages seriously: Node.js, Rust, and Go. Each had compelling strengths:

  • Node.js: Massive ecosystem, easy hiring, familiar to frontend developers. But CPU-bound tasks (image processing, encryption) bottleneck the event loop, and the lack of type safety at runtime leads to subtle bugs in production

  • Rust: Best-in-class performance and memory safety. But compile times are slow, the learning curve is steep, and the async ecosystem was still maturing when we started

  • Go: Exceptional concurrency model (goroutines are cheaper than threads or promises), fast compile times (full rebuild in under 5 seconds), and a simple, readable codebase that new engineers can contribute to within days

Go won because it optimises for the thing that matters most at our stage: developer velocity without sacrificing runtime performance.

Service Boundaries

Each service owns a specific domain and its database schema. Services communicate via REST APIs using an internal API key for service-to-service authentication. We chose REST over gRPC for simplicity — every endpoint is debuggable with curl.

Storage Architecture

File storage uses S3-compatible object storage with a sophisticated multi-node system:

  • Each tenant gets their own bucket

  • A capacity-aware selection algorithm picks the storage node with the most available space for each upload

  • Nodes can be added, drained, or retired without downtime

  • System-level buckets are stored separately from tenant data

Lessons Learned

  1. Start with shared libraries: Our shared package contains middleware, error handling, database utilities, storage management, and health checks. Every service imports it. Consistency across services is worth more than independence

  2. Use sqlc for database access: Writing SQL directly and generating type-safe Go code from it is faster, safer, and more maintainable than any ORM

  3. Invest in observability early: Prometheus metrics, structured logging, and Sentry error tracking from day one saved us countless hours of debugging

  4. Make everything idempotent: Webhook handlers, migration scripts, background jobs — if it can run twice, make sure running it twice is safe

architecturegomicroservices

Volgend artikel

Going Global: Multi-Currency and Multi-Language Guide

Everything you need to know about selling internationally with built-in localisation features.

Klaar om je winkel op te zetten?

Begin gratis — geen creditcard nodig.

Aan de slag