Software Development

What is a microservices architecture: a guide for developers

By James KillickAugust 3, 2026

TL;DR: A microservices architecture splits an app into small, independent services, each owning its own data and its own deployment. It gives you targeted scaling and team autonomy, but only once four things are in place: clear service boundaries, automated CI/CD, real observability, and teams that can own a service end to end. Skip those and you get a distributed monolith: all the complexity, none of the upside. A modular monolith gets most teams most of the benefit for a fraction of the cost, so audit that first.

A microservices architecture organises an app as a collection of small, independently deployable services, each aligned to one business capability and talking to the others over lightweight APIs or messaging. Each service owns its own data, runs as its own process, and is looked after by a small, autonomous team.

Here is the short version: microservices are worth it when your teams are hitting deployment bottlenecks, your scaling needs genuinely need to scale differently, or a regulator demands service isolation. If you are a small team or an early-stage product, a modular monolith will serve you better.

A microservices architecture is not really a technical call. It is a team call. The services follow the teams. If the teams are not ready, the services will not work either.

Five things have to be in place before microservices actually function:

  • Service boundaries defined by bounded contexts, not technical layers
  • Independent data ownership: one database per service, no shared schemas
  • APIs or async messaging for every inter-service call
  • CI/CD automation with its own pipeline per service
  • Observability across every service from day one

How do microservices compare to a monolith?

The choice between a monolith and microservices shapes every decision that follows it. Here is how the two stack up on what actually matters.

DimensionMonolithMicroservices
CodebaseSingle deployable unitMany independent codebases
DeploymentDeploy everything at onceDeploy each service independently
ScalingScale the whole applicationScale individual services
Operational costLower, one runtime to manageHigher, distributed system overhead
Team ownershipShared codebase, needs coordinationEach team owns a service end to end
Test complexitySimpler integration surfaceContract testing and distributed tracing required
Developer speed (early)Fast, no service boundaries to crossSlower, API contracts and pipelines per service
Developer speed (at scale)Slows as the codebase growsStays high with clear boundaries

A monolith is faster to build and cheaper to run at small scale. AWS's own comparison is blunt about it: microservices bring real operational complexity, and a team needs to earn that complexity by actually outgrowing what a monolith can do.

The case for migrating is strongest when deployment bottlenecks are slowing several teams down, when parts of the product genuinely need to scale differently, or when a regulator requires service isolation. Outside those cases, a modular monolith, a single deployable with enforced module boundaries, gets you most of the benefit of microservices for a fraction of the cost.

Pro tip: before you commit to full decomposition, ask whether the pain is architectural or organisational. A modular monolith with clear ownership boundaries often fixes the same problem without the distributed-system overhead.

What are the core characteristics of a microservices architecture?

Strip away the tooling and what is left is a small set of design rules. Follow them and you get a system that scales and changes on its own terms.

Autonomy and single responsibility. Each service does one business capability and nothing else. A payments service handles payments. An identity service handles identity. A service that tries to do too much turns into a distributed monolith, which is the worst of both worlds.

Bounded context and domain-driven design. Service boundaries should map to bounded contexts: the natural divisions in the business where one model applies. This is where domain-driven design earns its keep. A bounded context gives a team clear ownership and stops the model leaking across services.

Independent data ownership. No two services share a database. Each service owns its schema and is the only thing that writes to it. This is what makes independent deployment possible. Without it, a schema change in one service can quietly break another.

Loose coupling and well-defined APIs. Services talk to each other only through published interfaces: REST endpoints, gRPC contracts, or async message schemas. Internal details stay hidden. That is what lets a team rewrite a service without dragging every other team into the change.

Polyglot stacks and team ownership. Microservices allow polyglot development: a team can pick the language, framework and database that fits its service. That is genuinely useful when it solves a real problem, a high-throughput event processor in Go, a data-heavy analytics service in Python, but it adds cognitive load when used without discipline. Teams are usually kept small, each owning a service from design through to running it in production.

Pro tip: do not split services too early. Start coarse and split only when there is a clear operational or ownership reason. Splitting too soon is one of the most common ways microservices adoption fails.

What are the real benefits of microservices?

The benefits are real. They also come with conditions.

  • Independent deployment. Teams ship changes to their own service without coordinating a full release. Deploys get more frequent and a bad one affects less.
  • Targeted scaling. A checkout service under Black Friday load scales on its own, separate from the product catalogue or the user profile service. You pay for the load you actually have, not the peak of the whole app.
  • Team autonomy. Ownership is clear. A team controls its own roadmap, tech choices and release schedule without waiting on a shared codebase.
  • Resilience and fault isolation. A circuit breaker stops a failing service from taking the rest of the system down with it. One service going down does not have to mean the whole app goes down.

The metrics that move with a well-run microservices setup are deployment frequency, mean time to recovery, and how efficiently you use resources at scale. Those are the numbers worth tracking to check the architecture is actually paying off.

The distributed-system tax is real, and it grows with every service you add. Managing network failures, versioning, observability and saga logic can eat a large share of engineering capacity once services start talking across process boundaries. That is the number to understand before you start a migration. The benefits above are real, but only once you have absorbed that cost.

Which design patterns do microservices actually use?

The microservices.io pattern catalogue documents what practitioners have converged on. These are the ones that matter most in practice.

  • API gateway: one entry point for external clients. It handles authentication, rate limiting, routing and protocol translation, so clients never call services directly.
  • Service registry and discovery: services register their location on startup. Other services look them up instead of using hardcoded addresses. Consul or Kubernetes-native DNS usually handle this.
  • Circuit breaker: detects a failing downstream service and stops sending it requests for a while, so the rest of the system can keep working.
  • Bulkhead: isolates the resource pool for each service or dependency, so a slow downstream call cannot exhaust the thread pool for the whole app.
  • Sidecar and service mesh: a sidecar proxy, Envoy for example, runs next to each service and handles cross-cutting concerns like mutual TLS, retries and observability, so the service does not have to. A service mesh such as Istio or Linkerd coordinates the sidecars across the fleet.
  • Saga pattern: breaks a long-running distributed transaction into a sequence of local transactions with compensating steps for rollback. There is no distributed ACID transaction across services, so a saga is how you handle that.

Synchronous versus asynchronous communication

REST over HTTP and gRPC are the standard for synchronous calls. gRPC is worth the setup cost for high-throughput internal calls where latency and payload size matter. REST is simpler and enough for most cases.

Async messaging through queues (RabbitMQ, AWS SQS) or event streams (Apache Kafka, Azure Service Bus) decouples services in time. The producer does not wait on the consumer, which helps resilience and throughput, but it introduces eventual consistency: the consumer might process the event seconds or minutes after it was published.

Pro tip: avoid deep synchronous call chains. If service A calls B, which calls C, which calls D, you have built a distributed monolith in disguise. Any failure at D travels straight back up the chain. Use async events for anything that can tolerate eventual consistency, and keep synchronous chains to two hops at most.

How do you deploy and run microservices?

Containers and orchestration are the default. Containers package each service with its dependencies into one portable unit. Kubernetes schedules those containers across nodes and handles health checks and autoscaling.

Serverless functions (AWS Lambda, Google Cloud Functions, Azure Functions) suit event-driven, stateless workloads with spiky traffic. They cut operational overhead but add cold-start latency and limit your runtime choices.

How do you manage data and transactions across services?

Data is where microservices get genuinely hard. Database-per-service is the foundation: each service owns its schema and is the only writer to its own store. No other service reads it directly.

  • Anti-corruption layers: when you integrate with a legacy system or a third-party API, an anti-corruption layer translates between the external model and your service's own model, so outside concepts do not leak into your bounded context.
  • Materialised views: a service publishes events that another service consumes to build its own local read model. That avoids cross-service queries while keeping the data where it is needed.
  • Saga pattern for distributed transactions: when one business action spans several services, create an order, reserve stock, charge a card, a saga coordinates the sequence as local transactions with compensating steps if one fails.
  • Eventual consistency: most cross-service data flows are eventually consistent, not strongly consistent. Design for that. Strong consistency across service boundaries needs synchronous coordination, which brings tight coupling with it.

Pro tip: design every service operation to be idempotent, safe to retry without side effects doubling up. Use idempotency keys on payment and order operations. It makes retries, saga compensation and redelivered messages safe by default, and it will save you from a class of production incident that is otherwise brutal to debug.

How do you observe and test a distributed system?

Observability is not optional here. A failed request might have touched a dozen services. Without distributed tracing, finding the cause is guesswork.

Test typeGoalCommon tools
Unit testsCheck service logic in isolationJest, pytest, JUnit
Contract testsCheck API compatibility between servicesPact, Spring Cloud Contract
Integration testsCheck behaviour against real dependenciesTestcontainers, Docker Compose
End-to-end testsCheck critical user journeys across servicesPlaywright, Cypress
Chaos testsCheck resilience under failureChaos Monkey, Gremlin

Contract testing is the one most teams underinvest in. Consumer-driven contracts let the consumer of a service define what it expects, and the provider checks against that on every build. That removes the need for a shared end-to-end environment for every change, and it is what makes independent deployment actually work at scale.

The three pillars of observability are metrics, distributed traces and logs. OpenTelemetry has become the standard for instrumenting all three, exporting to Datadog, Grafana, Honeycomb or your cloud provider's own tooling.

Pro tip: define service level objectives and indicators per service before you go live. An alert on "error rate above 1% for 5 minutes" tells you more than an alert on CPU usage. High-cardinality tags in your traces, user ID, tenant ID, order ID, are what actually make distributed debugging possible.

When should you actually choose microservices?

Team readiness decides whether microservices work, not traffic volume. Four things need to be true first:

  1. Domain-driven design: clear bounded contexts that map to team ownership. Get this wrong and your service boundaries will be wrong too, and you will spend years re-cutting them.
  2. DevOps and automated CI/CD: a pipeline per service, automated testing, and the ability to deploy independently with no manual gate.
  3. Observability: distributed tracing, metrics and centralised logging in place before you extract the first service.
  4. Organisational alignment: autonomous teams that own a service end to end. Conway's Law means your architecture will mirror how your teams communicate whether you plan for it or not.

Signs you are actually ready:

  • Several teams are stuck waiting on the same deployment pipeline
  • Different parts of the product genuinely need to scale differently
  • Regulatory or compliance rules demand service isolation, payments or PII handling are common triggers
  • The team has outgrown what one codebase can support without constant coordination

Pro tip: if you cannot name which team owns a proposed service end to end, you are not ready to split it out. Sort the team structure first, then let the service boundaries follow. Devwiz's guide for CTOs covers how to weigh this kind of architecture call against your product stage and team shape.

How do you migrate from a monolith to microservices?

Migration is almost always incremental. A big-bang rewrite is high risk and rarely the right call.

  1. Identify bounded contexts. Map the monolith's domain model and find the natural seams: parts that change independently, are owned by different teams, or need different scaling. Those are your candidate services.
  2. Use the strangler fig pattern. Route specific functionality to a new service while the monolith keeps handling everything else. The monolith is the fallback, the new service is the target. Shift traffic across as confidence grows.
  3. Build an anti-corruption layer first. Before cutting over, translate between the monolith's data model and the new service's model, so the monolith's concepts do not leak into the new service.
  4. Stand up the new service and cut over gradually. Run it alongside the monolith's existing functionality and use feature toggles to control which traffic goes where.
  5. Stabilise, then move on. Once the new service is carrying production traffic, retire the monolith's equivalent code. Instrument it, set service level objectives, and confirm observability is working before you extract the next one.

Consumer-driven contract testing is what makes this safe. It checks that the new service's API meets every consumer's expectations before you cut over.

A realistic timeline on a mid-sized monolith: 4 to 8 weeks to map bounded contexts and prepare, 6 to 12 weeks for the first extraction, then another 4 to 8 weeks to stabilise before the team has the confidence and tooling to move faster on the next one.

Pro tip: avoid dual-write patterns, writing the same data to both the monolith's database and the new service's database at the same time. They create consistency bugs that are hard to spot and harder to fix. Use event sourcing or a clean cutover with a migration script instead.

Where do microservices deliver the most value?

Microservices earn their keep where scale, team structure or regulation puts real pressure on a monolith.

High-scale consumer platforms, e-commerce, streaming, ride-sharing, are the classic case. Checkout, search, recommendations and accounts all have different traffic patterns and different owners. Scaling them separately makes sense both technically and financially.

Multi-tenant SaaS with different scaling needs is another strong fit. If your analytics pipeline processes millions of events an hour while billing handles a few thousand transactions a day, running them as separate services lets you size each one correctly instead of over-provisioning the smaller one.

Regulatory isolation matters a lot in finance and health tech. Putting payment processing or PII handling into its own service with its own data store and access controls makes audits far more manageable and shrinks the blast radius of a security incident.

Polyglot runtime needs, where one part of the system genuinely needs a different language or runtime for performance, are well served by microservices. A machine learning inference service in Python can sit next to a high-throughput API in Go without either one holding the other back.

Microservices are overkill for early-stage startups, single-team codebases, and products where the domain model is still being worked out. The overhead of running a distributed system is a tax on iteration speed that a small team usually cannot afford yet.

Pro tip: traffic volume alone is not the signal. A monolith can handle serious load with horizontal scaling and a good cache. The real signals are team coordination overhead, deployment bottlenecks and regulatory isolation, not request counts.

How Devwiz implements microservices in practice

We have shipped over 200 apps since 2015, including work for NSW Government (Justice and Corrective Services), Briometrix, Vivid and Huskee. When a project calls for microservices, we run it through the four pillars above before we write a line of service code.

Our checklist for a microservices build:

  • Bounded-context discovery: workshop with the client's domain experts to map business capabilities and find service boundaries before any code gets written.
  • Platform provisioning: stand up the CI/CD pipeline, container orchestration and observability stack before the first service goes live.
  • CI/CD per service: every service gets its own pipeline with automated unit, contract and integration tests. No shared deployment gate.
  • Observability from day one: tracing and SLO dashboards get built into the initial platform setup, not bolted on after launch.
  • Security and governance: zero-trust networking between services, mutual TLS and API gateway authentication from the start, with versioning and deprecation rules documented before the first external consumer connects.

For Australian projects, we default to hosting in Sydney to meet Australian Privacy Act data residency requirements, and we document that choice as part of the architecture record before we provision anything.

Our CARED case study is a good example of what a multi-service platform looks like in practice: four mobile apps and three web platforms on the Microsoft stack, wired into healthcare and government systems, all needing to meet strict data residency and compliance requirements.

Pro tip: run a modular monolith audit before you commit to microservices. On plenty of projects, enforcing module boundaries in the existing codebase fixes the coordination and deployment problems without the full distributed-system overhead. It is a standard first step for us.

Key takeaways

Microservices give you independent deployment and targeted scaling, but only once four things are in place first: domain-driven design, automated CI/CD, real observability, and teams that can own a service end to end.

PointDetails
DefinitionIndependently deployable services aligned to business capabilities, each owning its own data
Distributed-system taxNetwork failures, versioning and observability can absorb a large share of engineering capacity once services cross process boundaries
Four pillars requiredDomain-driven design, automated CI/CD, observability, and team alignment, all before decomposition
Modular monolith firstCaptures most of the benefit at a fraction of the cost. Audit this before you migrate
Devwiz approachBounded-context discovery and platform setup before the first service extraction, with Australian data residency as the default

The part most teams get wrong

Here is a conversation that plays out in a lot of architecture reviews. The team is frustrated with deployment speed, someone proposes microservices, and the decision gets made on the strength of what Netflix or Uber does at their scale. The org chart does not change. The CI/CD pipeline does not get rebuilt. Observability does not get set up. Six months later the team is running a distributed monolith: all the complexity of microservices, none of the independence.

The distributed-system tax is the part that gets glossed over in most guides. When a large share of your engineering capacity goes into handling network failures, versioning and observability, that is not a temporary migration cost. It is the ongoing price of running a distributed system, and for a team of five or ten engineers it can be crippling.

Conway's Law is not a throwaway line. Your architecture will reflect how your teams communicate whether you plan it that way or not. Deliberately shaping team structure to produce the service boundaries you want, sometimes called the inverse Conway manoeuvre, is one of the most underused tools in this whole playbook. Sort the teams first, then let the services follow. The technical work is the easy part.

Our honest advice: start with a modular monolith audit. Enforce the module boundaries you wish you already had and see if that fixes the coordination and deployment problems on its own. If it does, you have saved yourself a lot of overhead. If it does not, you now have a much cleaner starting point for pulling services out.

Devwiz builds and migrates microservices platforms for Australian teams

If your team is hitting the deployment bottlenecks or scaling limits that make microservices worth considering, we can help you work out whether decomposition is actually the right call, and build it properly if it is.

We run architecture reviews, bounded-context workshops and modular monolith audits for Australian founders, CTOs and engineering teams. If microservices are the right fit, we handle platform setup, CI/CD, observability and service extraction from your existing codebase. If a modular monolith is the better fit, we will tell you that instead.

Our work spans custom AI app development through to full platform builds, and our web app development service is the right place to start if you are building or scaling a multi-service platform.

Get in touch to book an architecture review or talk through your migration options.

Frequently asked questions

What is a microservice vs an API?

An API is an interface, a contract for how a service can be called. A microservice is the deployable unit that implements business logic and might expose one or more APIs. Mixing the two up leads to bad architecture decisions, like treating every API endpoint as its own service.

What are the four pillars of microservices?

Domain-driven design with clear bounded contexts, automated DevOps and CI/CD, observability through distributed tracing and metrics, and organisational alignment through autonomous teams with end-to-end ownership. All four need to be in place before you decompose.

Do microservices require a specific programming language or framework?

No. Microservices support polyglot development, so each service can use whatever language, framework and database fits its job. The only real constraint is that services talk to each other only through well-defined APIs or messaging, never shared code or a shared database.

What are the main types of microservices communication?

Synchronous, REST over HTTP or gRPC, and asynchronous, message queues like RabbitMQ or AWS SQS, or event streams like Apache Kafka. Synchronous calls are simpler but create temporal coupling. Async messaging improves resilience and throughput but introduces eventual consistency.

When should you use a modular monolith instead of microservices?

For small teams, early-stage products, and any org that has not yet built autonomous team ownership and automated CI/CD. A modular monolith captures most of the benefit of microservices at a fraction of the operational cost, and gives you a cleaner starting point if you do decompose later.

About James Killick

10+ years building digital products · 200+ apps shipped since 2015

James is a co-founder of Devwiz and an AI product specialist. Since 2015 he has helped ship 200+ apps for founders, businesses and government, including work for NSW Government, Briometrix and Huskee. He builds AI-first platforms and writes about turning a proven program into software. He also hosts the Up in the AI podcast.

More articles by James · James's personal site · LinkedIn · AI Orchestrators

Tags: Microservices, Software Architecture, DevOps

Browse all Devwiz articles·See our case studies