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
Start with shared libraries: Our
sharedpackage contains middleware, error handling, database utilities, storage management, and health checks. Every service imports it. Consistency across services is worth more than independenceUse sqlc for database access: Writing SQL directly and generating type-safe Go code from it is faster, safer, and more maintainable than any ORM
Invest in observability early: Prometheus metrics, structured logging, and Sentry error tracking from day one saved us countless hours of debugging
Make everything idempotent: Webhook handlers, migration scripts, background jobs — if it can run twice, make sure running it twice is safe