What are microservices, and why split a monolithic architecture?
A microservice delivers a specific business capability through an explicit interface and its own deployment. In a monolithic architecture, the main application components run as one deployable unit. A large codebase is not automatically a reason to split it. A small team can operate one application effectively for a long time with clear modules and a reliable release pipeline. Extraction becomes useful when one capability needs a different release rhythm, separate scaling, or a failure boundary that protects other workflows. The boundary should remove more friction than its network and operating costs introduce.
Begin by asking what actually prevents delivery. Slow builds, fragile deployments, conflicting team changes and an unstable external dependency are different problems. Moving missing tests or a manual release process onto separate servers does not repair them. A modular monolith creates enforceable package boundaries and named ownership within one deployment. Establishing that internal boundary first gives you evidence about whether it is coherent before you add remote calls, independent infrastructure and a second set of failure modes.
Describe the constraint through observed behaviour. If one busy component forces you to scale the whole application, measure resource usage by capability. If teams wait for each other's releases, record the reasons and waiting time. Write one sentence connecting the current baseline to an acceptable outcome. Avoid assuming that every slow release or expensive server means the application needs a microservices architecture.
A service boundary selection exercise
The first deliverable is a comparison of candidate boundaries. Draw one business workflow and mark the owners of its decisions, data sets and external contracts. AWS's business-capability decomposition guidance starts from value-producing capabilities such as sales or customer service. Include someone who understands the business rules alongside the developer who knows the code. An attractive package structure does not explain who may change a price, approve an invoice or correct a historical transaction.
| Observed situation | First option | Evidence for extraction |
|---|---|---|
| Missing tests, fragile releases | Improve the release pipeline | The boundary must reduce the problem |
| Shared writes to the same data | Establish ownership in the monolith | Identify one authoritative writer |
| Different business release rhythms | Trial an independent boundary | Releases need no coordinated deployment |
| Load concentrated in one component | Measure the scaling candidate | Savings must cover extra operations |
| External dependency disrupts every workflow | Separate the failure boundary | Failure must remain contained |
Choose three candidates and write a one-page decision record for each: business purpose, data owner, consumers, synchronous calls, shared transactions, expected benefit and added cost. Pricing may be a coherent capability; a generic database access layer used by every feature usually is not. Trial the candidate with manageable dependencies and a measurable benefit. A total score must not hide a blocker such as a financial invariant that still requires a shared transaction.
What should be ready before extracting a service?
Create the proposed boundary inside the monolith first. Other modules should use its interface rather than reach directly into pricing tables. Document request and response fields, mandatory values, errors and compatibility expectations. A pricing request might include the product, customer group, currency and calculation time; its response might contain the total, tax and rule version. Tests capturing existing behaviour with concrete cases reveal unexpected changes while the implementation moves. Define the meaning of every monetary field, including whether tax is included and how rounding is applied.
Resolve data ownership alongside code ownership. Microsoft's data considerations for microservices explains how direct access to shared tables couples services through their schemas. Separate physical database servers are not mandatory; data and schema boundaries are the concern. If changing a service's schema still requires every team to deploy together, the intended deployment autonomy has not arrived. Describe transitional access explicitly and give it an owner and an exit condition.
For each data set, record its authoritative writer, reading interface and freshness requirement. Where order, price and inventory changes currently share a transaction, identify which business rule must remain atomic. A report that tolerates delayed updates and a payment amount that must be correct belong to different consistency decisions. Avoid unexplained writes to two applications. Design failure handling, retries and reconciliation before moving the write path. If the invariant cannot be separated sensibly, a larger boundary or a modular monolith may be the better operating choice.
The service owner must also own deployment, monitoring, security updates and incident response. Prepare a delivery pipeline, authorised service identity, secret management, request identifiers and dependency latency records. Set timeout and retry limits within an end-to-end latency budget. The right response to an unavailable dependency follows the business rule: silently reporting success is unsuitable for a payment that has not been confirmed. Also decide what information support staff need to trace a request across both applications without exposing unnecessary customer data.
Trial one reversible extraction
Start with shadow reads or calculations that have no side effects. The old application remains authoritative while the new implementation produces results only for comparison. Microsoft's Strangler Fig pattern describes replacing functionality incrementally behind a shared entry point. It allows you to investigate one boundary while the rest of the monolith continues serving users. Put routing behind a switch and preserve the old path's capacity throughout the trial.
Compare results produced from the same inputs and rule version. Two quotes calculated against different price data are not a meaningful parity test. Classify differences, approve intended changes and fix unexplained results before exposing users. Then begin with a small cohort chosen for its risk and traffic. A routing switch may be enough to reverse a pure calculation trial. Where the new service writes records, you must also prove that the old application can understand and process them. Write the data rollback plan before moving that ownership.
Hypothetical example: pricing in an order application
Suppose a six-person team releases its order application four times a month, while pricing rules change twice a week. Over the previous six weeks, eight of twelve price changes waited for a main application release, with a median delay of three days. The system calculates 20,000 quotes daily. Pricing is a candidate business capability, and the initial objective is independent price changes while preserving order performance. All these figures are hypothetical; they are not a Hatson customer result or a claim about typical projects.
The team first establishes an internal pricing interface and ownership of the rules. It compares 2,000 historical quotes using identical inputs and records rounding and tax differences separately. After resolving discrepancies, it sends 5% of traffic to the new service. For this example, acceptance means zero unexplained price differences, end-to-end p95 latency at most 800 milliseconds and a price-change wait below one day. These are local business targets rather than universal migration thresholds. The team records the rule version with every comparison so a changing dataset cannot distort the result.
After two releases, waiting time improves but infrastructure and on-call work add four hours per week. The team evaluates that cost alongside the delivery benefit. If each price change still needs a simultaneous order-team release, it pauses further extraction. It revisits the data boundary and may keep pricing as a module in the single application. Independent rollback, stable error rates and a team able to operate the service are part of the result. Counting deployments alone would overlook the new maintenance burden.
A microservices migration test and measurement plan
- Record the baseline: Measure blocked deployments, change lead time, failure rate and maintenance hours over the same period. Separate the candidate capability from unrelated application changes.
- Exercise the contract: Specify expected responses for missing values, old clients, an unsupported currency, unauthorised requests and incompatible versions. Verify both implementations against those expectations.
- Inject failures: Stop the service, delay its response and interrupt its network connection. Observe timeout limits, retries and whether unrelated workflows remain available without creating a growing request queue.
- Reconcile the data: Compare amounts and record counts after duplicate requests, concurrent updates and cancellations. Assign an owner to every unexplained difference rather than accepting a successful HTTP response as proof.
- Practise rollback: Return traffic to the old route and verify capacity and the readability of new records. Check users with transactions already in progress as well as new requests.
- Apply the stop rule: After two releases, pause new extractions if the expected benefit is absent. Change the boundary or reconsider the original constraint before funding another service.
Common mistakes
- Using lines of code or team size as the sole extraction threshold instead of measuring a business bottleneck.
- Splitting technical layers and adding a long remote call chain to every user request.
- Preserving shared database writes while multiplying deployments and expecting independent delivery to emerge.
- Excluding monitoring, on-call work, security maintenance and reconciliation from the cost estimate.
- Treating a routing switch as sufficient rollback without testing how the old path handles newly written data.
How Hatson Tech builds this
In our custom software development work, we start by mapping workflows, release waits and data ownership, then define a candidate boundary through contracts and acceptance tests. For the broader choice of intervention, see the application modernisation decision guide; for moving a selected capability, see the Strangler Fig migration plan. A useful pilot produces a measured decision as well as an implementation. Sharing the current bottleneck and one candidate capability gives discovery a concrete scope and makes its success criteria reviewable before further services are added.