Microservices Architecture and Service Decomposition Questions
Decomposing a system into services along bounded contexts, defining service boundaries and ownership, and managing the tradeoffs against a monolith. Covers cohesion, coupling, data ownership per service, the distributed-monolith anti-pattern, and when a modular monolith beats microservices. Emphasizes decomposition reasoning rather than any single framework.
Describe a pragmatic process to decompose a large, high-traffic monolith using Domain-Driven Design: the steps from domain discovery, through forming bounded contexts, to extracting the first services and the domain events that decouple them. Name the pitfalls to avoid (shared-database anti-patterns, premature splitting, boundaries with no clear owning team) and how you would validate a candidate boundary (spike tests, consumer usage data) before committing to extraction.
Sample Answer
Direct answer
A pragmatic Domain-Driven Design (DDD) driven decomposition process for a large, high-traffic monolith runs in four stages: domain discovery with the people who actually understand the business, drawing candidate bounded contexts from that discovery, validating those candidates against real usage data before committing to extraction, and extracting the first service behind a boundary that also decouples it with domain events rather than direct calls.
Structured elaboration
Domain discovery typically uses a facilitated exercise like event storming, where domain experts and engineers map out the business events that happen in the system (OrderPlaced, PaymentAuthorized, InventoryReserved) without worrying about current code structure; clusters of related events and the language used to describe them are the raw material for candidate bounded contexts. From there, candidate contexts get validated against real signals before any code moves: does this candidate context have a genuinely different team owner, a genuinely different release cadence, or genuinely different scaling needs from its neighbors? A lightweight way to validate is a lower-cost spike, like standing up the candidate service's interface behind a feature flag while the implementation still lives in the monolith, or examining real consumer-usage data (which callers actually depend on this piece of functionality, and how tightly) before extraction.
Once a candidate is validated, extraction proceeds by first identifying the domain events the new context needs to publish or consume (OrderPlaced triggering a downstream Inventory reservation, for example) so that the new service is decoupled from its neighbors through events rather than direct synchronous calls into the old monolith's internals. Pitfalls to avoid at this stage: a shared-database anti-pattern, where the new service and the old monolith both read and write the same tables during a transition period (this defeats the purpose of the extraction and makes rollback harder, not easier); premature splitting, extracting a context before its boundary and ownership are validated, which tends to produce a service that has to be pulled back or merged with another later; and lack of ownership, extracting a service without a clear team committed to operating it, which leaves it as an orphaned piece of infrastructure nobody prioritizes maintaining.
Worked example
For a monolith that needs to split along bounded contexts under real traffic, a practical sequencing is: run event storming with the Payments and Fulfillment domain experts, notice that "Order" means two different things in each conversation, treat that as the boundary signal, validate by checking how many call sites in the current monolith actually cross that boundary versus stay within it (a high cross-boundary call count is a red flag that the proposed split doesn't match how the code is actually used today), and only then extract Fulfillment as its own service, publishing an OrderPlaced event that Fulfillment subscribes to instead of the monolith calling into Fulfillment's code directly.
Trade-offs and pitfalls
The single most common failure in this process is skipping the validation step and going straight from a whiteboard exercise to extraction, because the whiteboard boundary that looked clean in a discovery workshop often doesn't match how the current code and its call graph are actually structured; validating against real usage data before extracting is what prevents a costly "extract, discover it's wrong, and merge it back" cycle.
Explain the architectural principles loose coupling, high cohesion, separation of concerns, and single responsibility as they apply to drawing service boundaries. For each principle, give a concrete example of a boundary decision it would push you toward, and explain how violating it shows up later (harder deployments, blurred ownership, cascading changes).
Sample Answer
Direct answer
Loose coupling means two components can change independently without breaking each other; high cohesion means everything inside one component is closely related and serves one clear purpose. Separation of concerns and single responsibility are the design habits that get you there: separation of concerns keeps distinct kinds of logic (say, business rules versus data storage versus presentation) from tangling together, and single responsibility says each unit (a service, a class, a module) should have one reason to change.
Structured elaboration
At the service-boundary level, loose coupling shows up as: Service A can deploy a new version without Service B needing to change or even redeploy, as long as A's external contract (its API) stays the same. High cohesion shows up as: everything Service A does relates to one clear responsibility (an Order service handles order lifecycle, not also user authentication and email formatting). Violating loose coupling typically looks like two services sharing a database table directly, so a schema change in one silently breaks the other; violating high cohesion typically looks like a "God service" that has accumulated unrelated responsibilities over time because it was the easiest place to add one more feature.
Concretely: if Service A calls Service B synchronously and depends on B's internal implementation details (say, the exact error codes B's database driver happens to return) rather than B's documented API contract, that's tight coupling, because a change B makes to its internals, even one that doesn't change its documented behavior, can break A. The fix is depending only on the contract, not the implementation, and versioning that contract explicitly when it does need to change.
Worked example
Single responsibility applied to service boundaries: an Order service that also sends marketing emails when an order ships has taken on a second responsibility unrelated to order-lifecycle management; a change to the email template now requires redeploying and retesting the Order service, and a bug in the email-sending code can take down order processing even though the two are logically unrelated. Splitting email notifications into their own service (triggered by an OrderShipped event rather than embedded in the Order service's code) restores single responsibility: each service has one reason to change, and a failure in one doesn't take down the other.
Trade-offs and pitfalls
Violating these principles rarely happens all at once; it's usually death by a thousand small compromises, each individually reasonable ("it's faster to just add this one field to the existing service instead of creating a new one") that accumulate into a service nobody wants to touch because it has too many unrelated responsibilities and too many hidden dependents. The counter-pressure is real too: splitting too aggressively in the name of single responsibility produces services so narrow that every business operation requires coordinating five of them, trading one kind of complexity (a tangled service) for another (an over-fragmented one); the goal is a boundary that's cohesive enough to reason about on its own and loosely coupled enough to change independently, not maximal fragmentation.
Explain how Domain-Driven Design concepts like bounded contexts, aggregates, and ubiquitous language influence microservice boundaries. Give an example mapping DDD concepts to services for a payments-and-billing domain, and name one mistake architects commonly make when they equate every code module to its own microservice.
Sample Answer
Direct answer
A bounded context is a boundary within which a specific business concept has one precise, consistent meaning; the same word can mean different things in different contexts (an "order" means a customer purchase in the Sales context and a warehouse picking task in the Fulfillment context), and a bounded context is the boundary that keeps those meanings from colliding. It's the natural starting point for drawing service boundaries because a coherent bounded context is already a low-coupling, internally-consistent unit of the domain.
Structured elaboration
A bounded context is defined by its ubiquitous language, the shared vocabulary that a team and its stakeholders use consistently within that boundary; when the same term needs different definitions depending on who's using it, that's usually the signal that you're looking at two contexts, not one. Within a context, an aggregate is the consistency boundary for a single business transaction (for example, an Order aggregate that enforces its own invariants, like "an order's total must match the sum of its line items," as a single atomic unit), and aggregates are the building blocks a bounded context is made of.
For a payments-and-billing domain, a Payments bounded context might own the concepts of a transaction, an authorization, and a settlement, each with its own aggregate and its own precise meaning of terms like "amount" (post-fees or pre-fees, for example); a Billing bounded context might separately own the concept of an invoice and a subscription, where "amount" means something contractual rather than transactional. Even though both contexts touch money, mapping them to two separate services (rather than one "Money" service) follows directly from the fact that their ubiquitous languages and consistency boundaries genuinely differ.
Worked example
The common mistake architects make is equating every code module or every noun in the domain to its own microservice, treating "Bounded Context = Service" as a mechanical, one-to-one rule rather than a starting point for judgment. A bounded context is a good place to START looking for a service boundary because it's already internally coherent, but a very small, tightly-related pair of bounded contexts (say, Authorization and Settlement within Payments, if they share the same team, the same consistency requirements, and change together) can reasonably stay in one service, while a genuinely large bounded context might still need to be split further along a different axis (like read/write access patterns) once it's grown large enough.
Trade-offs and pitfalls
The risk of treating bounded contexts too literally as service boundaries is producing more services than the org can operate well, each one technically "correct" by the Domain-Driven Design (DDD) definition but not justified by any real difference in scaling, ownership, or release cadence. The opposite risk, ignoring bounded contexts entirely and drawing service boundaries along purely technical or organizational lines, tends to produce services whose internal data model is internally inconsistent, because two different meanings of the same term (like "order") end up living in the same service without a clear boundary between them.
A company is moving from roughly 20 to 200 services. Explain Conway's Law's practical impact on the resulting architecture and reliability, and propose an organizational structure and set of team boundaries (platform/infra teams, service-owning teams, shared libraries) that improves ownership clarity and reduces cross-team coupling at that scale.
Sample Answer
Direct answer
Going from 20 to 200 services multiplies the coordination surface roughly with the number of services, not linearly with headcount, so the organizational structure that worked at 20 services (loose conventions, informal coordination) breaks down well before 200; the fix is introducing explicit platform and infrastructure teams that own shared concerns, clear per-service ownership with no orphaned services, and enough standardization that a new service doesn't require reinventing deployment, observability, and on-call practices from scratch.
Structured elaboration
Conway's Law's practical impact at this scale: at 20 services, informal cross-team communication (a Slack message, a quick sync) is usually enough to coordinate a shared concern; at 200, the number of possible pairwise team interactions grows far faster than the team count itself, and informal coordination stops scaling, showing up as duplicated effort (multiple teams independently solving the same infrastructure problem), inconsistent practices (some services have solid observability, others none), and slower cross-cutting changes (a security fix that needs to land in every service takes far longer to propagate without a shared mechanism). The organizational fix mirrors the technical one: introduce dedicated platform/infrastructure teams whose job is providing the shared capabilities every service team would otherwise reimplement (a deployment pipeline template, a standard observability stack, a shared authentication library), reducing the coordination surface from "every team talks to every other team" to "every team talks to the platform team."
Worked example
A concrete structure: product-facing teams each own a small, clear set of services end to end (their own on-call, their own release cadence), a platform team owns the shared deployment pipeline, service templates, and core infrastructure every other team builds on, and a smaller number of specialist teams (security, data platform) own concerns that genuinely need central expertise and shouldn't be duplicated 200 times. Team boundaries at this scale should be reviewed periodically (not fixed forever at whatever they were when the org had 20 services), since a boundary that worked well at 20 services can become a bottleneck at 200 if, for example, one team ends up owning far more services than it can operate well.
Trade-offs and pitfalls
The most common failure at this scale is under-investing in the platform team's capacity relative to how many product teams depend on it, turning the platform team itself into the new coordination bottleneck; the platform team's own roadmap needs to be resourced and prioritized as seriously as any product team's, since if it can't keep up with demand, product teams start working around it with one-off solutions, which recreates the inconsistency the platform team existed to prevent. Reliability at scale also depends on this structure: a shared, well-maintained deployment and observability platform, rather than 200 independently-invented ones, is what makes it possible to have a consistent incident-response process across the whole fleet.
Discuss the main approaches to data ownership across microservices: a single owning service as source of truth, replicated read models kept in sync via events, and API composition at query time. For a product that needs low-latency pricing reads alongside eventually-consistent inventory data, which approach (or combination) would you choose, and how would you support a query that needs fields owned by two different services without a cross-service join?
Sample Answer
Direct answer
The three main approaches to data ownership across microservices are: a single service acting as the source of truth that everyone reads from directly (simplest to reason about, but couples every reader to that service's availability and latency), replicated read models kept in sync via events (readers query their own local, eventually-consistent copy, trading a small consistency lag for independence and speed), and API composition at query time (a caller assembles the answer by calling several owning services and combining the results, keeping strong consistency but adding latency and coupling every read to every owner's availability).
Structured elaboration
Single-source-of-truth reads are the right default when correctness matters more than the reading service's independent availability and latency, and when the read volume is low enough that hitting the owning service directly doesn't become a bottleneck. Replicated read models are the right choice when a consumer needs low-latency, high-volume reads and can tolerate a short staleness window (seconds, not hours); the owning service publishes a canonical event stream whenever its data changes, and consumers build their own read-optimized copy from that stream. API composition sits in between: it avoids duplicating data (nothing is replicated, so there's no staleness to manage) but every composed read now depends on every service it calls being up and fast, which can turn a simple query into a slow, fragile fan-out if not designed carefully (with timeouts, partial-result handling, and caching where it matters).
Worked example
For a product needing low-latency pricing reads with eventual consistency acceptable for inventory: pricing, which changes relatively rarely and needs to be read extremely fast on every product page, is a strong candidate for a replicated read model, cached aggressively in the reading service and refreshed via a Pricing-changed event stream. Inventory, where eventual consistency is explicitly acceptable, can also use a replicated model, refreshed on its own change stream, with the understanding that a very recent update might lag by a second or two before every reader sees it. Where a query needs fields owned by two different services and there's no dedicated read model built for that exact combination, the fallback is API composition: call Pricing and Inventory in parallel, with a timeout and a documented fallback (like showing a cached last-known price) if one of them is slow, rather than letting the whole page hang on the slowest owner.
Trade-offs and pitfalls
The most common mistake with replicated read models is treating the replicated copy as a second source of truth and allowing writes against it, which reintroduces exactly the data-ownership ambiguity the pattern was meant to avoid; a replicated read model should always be read-only, with all writes going through the owning service's canonical event stream. The most common mistake with API composition is skipping timeout and partial-failure handling, so a single slow downstream service degrades every caller's latency instead of being isolated to just the field it owns; a well-designed composition layer degrades gracefully (showing what it has, with a clear fallback for what it couldn't fetch in time) rather than failing the whole request.
Unlock Full Question Bank
Get access to all 34 Microservices Architecture and Service Decomposition interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.