Dirora
Blog'a geri dön
Engineering

How We Built Our Microservices Architecture

Dirora Team10 Nisan 20268 min read

Dirora is API-first, built on more than 40 Go microservices — one service per domain, each responsible for a specific slice of the commerce platform: products, orders, payments, storefronts, subscriptions, analytics and more. This post is an honest, high-level tour of the decisions behind that backend: why we chose the tools we did, how we keep dozens of moving parts from turning into chaos, and what we would tell any team building a multi-tenant SaaS product from scratch. It is written for engineers who are curious about what sits under the hood, but it should make sense even if you have never written a line of Go.

None of this is architecture for architecture's sake. Every choice below traces back to something a merchant feels: pages that load fast, checkouts that do not double-charge, uploads that never run out of room, and a platform that keeps working while we ship changes underneath it. If you want to see how those foundations translate into what you can actually do with a store, our features overview is the merchant-facing companion to this piece.

Why Go?

Early on we evaluated three languages seriously — Node.js, Rust and Go — because the choice of runtime shapes everything that follows. Each had genuine strengths, and none was an obvious loser.

  • Node.js has a massive ecosystem, an easy hiring pool, and a syntax familiar to anyone who has written frontend code. But CPU-bound work — image processing, encryption, large report generation — competes with request handling on a single-threaded event loop, and the lack of type safety at runtime tends to surface as subtle production bugs rather than compile-time errors.

  • Rust offers best-in-class performance and memory safety with no garbage collector. The trade-offs at our stage were slower compile times, a steeper learning curve for new contributors, and an async ecosystem that was still settling when we started. For a systems kernel, Rust is superb; for a large surface area of ordinary business logic that changes weekly, the cost felt high.

  • Go gave us a concurrency model that is genuinely pleasant to work with — goroutines are cheap enough that you can spin up thousands without thinking about thread pools — plus fast compile times and a deliberately small language that new engineers can read and contribute to within days. Its standard library covers most of what a web backend needs without pulling in a dependency for every task.

Go won because it optimises for the thing that matters most when you are building broad and moving quickly: developer velocity without giving up runtime performance. A small, readable codebase that a new engineer can navigate on day two is worth more, over the life of a product, than squeezing out the last few percent of throughput. That high-performance Go backend is also why our storefronts render quickly under load — something we dig into in our guide to store performance optimisation.

One service per domain

The core organising principle is simple: each service owns exactly one business domain, and it owns its own database schema. The product service owns products; the order service owns orders; payments, subscriptions, storefront rendering, analytics and the rest each live behind their own boundary. No service reaches into another service's tables. If the order service needs product data, it asks the product service through a defined interface — it never runs a query against product tables directly.

This isolation is the discipline that keeps a growing system maintainable. Because no one can quietly depend on the internal shape of someone else's tables, we can refactor a service's storage, change its indexes, or rework its logic without breaking five other services by accident. The boundary is the contract. It costs a little more up front — you write an API where a monolith would write a join — but it pays for itself every time a domain needs to evolve independently. Because the platform is API-first, those same domain services are exposed to developers through a public REST API, provider-neutral webhooks, a CLI and the theme editor — the storefront and admin apps are just clients of the same API you can build against. If you are building on top of Dirora, our post on API-first, headless commerce covers that developer-facing side.

How services talk to each other

Services communicate over HTTP using JSON, authenticated for internal service-to-service calls with a shared internal key. We chose REST over heavier binary protocols deliberately, at least while the system was taking shape: every endpoint is inspectable with ordinary tools, easy to reason about, and simple to debug when something misbehaves at 2am. There is real value in being able to reproduce a production call by hand rather than needing special tooling to decode it.

Where two services genuinely need to stay in sync — an order being paid, a subscription renewing — we lean on events and webhooks rather than tight, synchronous chains of calls. The rule we keep coming back to is that a request should not fan out into a long dependency chain where one slow service drags down everything upstream of it. Loose coupling is not just an architecture buzzword; it is what stops a hiccup in one corner of the platform from becoming an outage everywhere.

Type-safe data access and versioned migrations

For database access we generate type-safe Go code from hand-written SQL rather than reaching for a heavyweight ORM. Writing the SQL ourselves keeps queries transparent and tunable, while the code generation catches whole classes of mistakes — a renamed column, a wrong type, a missing argument — at compile time instead of in production. It is the sweet spot between raw string queries and an ORM that hides what is really happening.

Schema changes go through versioned, forward-only migrations. Every change to the database is a numbered, reviewed step that can be applied predictably across environments, so the schema in front of a customer is never a mystery. Combined with per-service ownership, this means each team can evolve its own data model on its own schedule without a giant coordinated release.

Multi-tenancy without the noisy-neighbour problem

Dirora is multi-tenant by design: many independent stores share the same platform while staying strictly isolated from one another. Getting that isolation right — so one busy store can never see, slow down, or affect another — is one of the hardest and most important parts of the system, and it deserves its own treatment. We wrote about the model in detail in how we handle multi-tenancy, which explains how tenant identity flows through the stack and how we keep every store's data walled off.

The same design lets a single store operate across borders — multiple currencies, multiple languages, and localised storefronts — without forking the underlying services. If that is relevant to your business, our multi-currency and multi-language guide and the one-click AI translation write-up show what that looks like from the merchant's side.

Storage architecture

All uploaded files — product images, theme assets, digital downloads — live in S3-compatible private object storage rather than on any single server's disk. The design is capacity-aware: each store's assets are logically separated, and a selection step routes new uploads toward the storage with the most available headroom, so we can grow capacity smoothly as merchants add more products and media. System-level assets are kept separate from tenant data.

The practical upshot for merchants is that storage is not a wall you hit. Uploading images does not compete with your store's live traffic, files are served efficiently, and the platform can scale its storage footprint behind the scenes without downtime or a migration you would ever notice. Automatic image optimisation sits on top of this so that the large photos you upload are served in appropriately sized, fast-loading versions.

Rendering storefronts fast

Customer-facing storefronts are server-side rendered, which means the first thing a shopper's browser receives is real, ready-to-display HTML rather than a blank page waiting on JavaScript. That matters for two reasons: it is faster for the shopper, and it is legible to search engines, which is a large part of why Dirora storefronts come with strong SEO foundations and structured data built in. Speed is not a vanity metric in commerce — it is directly tied to how many visitors become buyers.

Observability and reliability

You cannot operate a distributed system you cannot see, so metrics, structured logging and error tracking were part of the platform from early on rather than bolted on later. When something goes wrong across a dozen services, the difference between a five-minute fix and a five-hour investigation is almost entirely whether you invested in observability before you needed it.

Reliability also comes from a simple, unglamorous discipline: make everything idempotent. Webhook handlers, background jobs, payment callbacks and migrations are all written so that running them twice is safe. Networks retry, messages get redelivered, and users double-click. Assuming every operation might happen more than once — and making sure it does no harm when it does — is what prevents duplicate orders, double charges and corrupted state.

What this means for merchants

Architecture is invisible when it works, which is exactly the point. The payoff of all of the above is a platform that stays fast under load, keeps each store's data isolated, and lets us ship improvements continuously without asking you to migrate anything. It is also what lets us keep our commercial model clean: Dirora charges no transaction fees on any plan. The only cut we take is a small platform fee that falls as you grow — 1.5% on the free Starter plan, 0.75% on Pro, 0.25% on Business, and 0% on Enterprise — so the more you sell, the less we take. Good engineering is what makes a fair, simple pricing model sustainable rather than a marketing promise we cannot keep.

Lessons learned

  1. Invest in shared foundations. A common internal library for middleware, error handling, database helpers, storage and health checks means every service behaves consistently. At scale, consistency across services is worth more than each team's independence.

  2. Prefer generated, type-safe data access. Writing SQL by hand and generating typed code from it is faster to work with and safer than either raw string queries or a heavy ORM.

  3. Add observability before you need it. Metrics, structured logs and error tracking from day one save far more time than they cost. You cannot debug what you cannot see.

  4. Make everything idempotent. If an operation can run twice — and in a distributed system it eventually will — make running it twice harmless.

  5. Draw boundaries and defend them. One domain per service, one schema per service, no reaching across the wall. The discipline is annoying on day one and priceless on day two hundred.

If you want to see the platform these choices produced — from the visual theme editor to custom domains with automatic SSL — the features page is the place to start, and if you are weighing us against alternatives, our honest platform comparison lays out the trade-offs.

Sıkça sorulan sorular

What language is Dirora's backend written in?

Dirora's backend is built in Go, organised as an API-first platform of more than 40 microservices — one per domain — that developers can build against through a public REST API. We chose Go for its lightweight concurrency, fast compile times and readable codebase, which together let us build a broad platform quickly without sacrificing runtime performance.

Why use microservices instead of a single application?

Splitting the platform into more than 40 services by domain — products, orders, payments, storefronts and so on — means each part can evolve, scale and be fixed independently without risking the rest. Each service owns its own database schema and never queries another service's tables directly, which keeps the system maintainable as it grows and lets us expose the whole platform through one consistent public REST API.

Where are my store's images and files stored?

Uploaded files live in S3-compatible private object storage, with each store's assets logically separated and capacity managed automatically. Storage scales behind the scenes, so uploading more products and media never competes with your live storefront traffic.

How does Dirora keep different stores isolated from each other?

Dirora is multi-tenant by design, with strict isolation so one store can never see or affect another's data or performance. We cover the details in our dedicated post on how we handle multi-tenancy.

Does this architecture affect what I pay?

Yes, in a good way. Dirora charges no transaction fees on any plan. The only cut is a small platform fee that falls as you grow — 1.5% on Starter, 0.75% on Pro, 0.25% on Business and 0% on Enterprise. Efficient engineering is what makes that pricing sustainable.

architecturegomicroservices

Mağazanızı oluşturmaya hazır mısınız?

Ücretsiz başlayın - kredi kartı gerekmez.

Başlayın