| Author: Abdullah Ahmed | Category: Custom Web Application Development
A small product team is considering microservices because it expects the application to grow. The proposed design separates accounts, orders, notifications and reporting into independent deployments. Before any customer benefits appear, the team now has several services to configure, monitor and troubleshoot. The architecture may eventually be appropriate, but growth alone does not explain why this is the right starting point.
A monolith and a microservices architecture make different trade-offs about deployment, communication, data and ownership. Either can be designed well or poorly. The useful decision follows the application's boundaries, workload and team structure rather than a belief that one label represents modern engineering.
Begin by asking which parts need independence and what the organisation can operate reliably. A clear answer can support a modular monolith, a small number of services or a broader distributed design. The choice should remain connected to evidence as the product develops.
Understand the deployment distinction
A monolithic application is commonly built and deployed as one unit, even when its code contains several well-defined modules. Those modules can represent separate business capabilities without communicating over a network for every interaction.
Martin Fowler and James Lewis describe microservices as an approach that structures an application as a suite of services, with independent deployment and organisation around business capabilities among the characteristic ideas. The term does not prescribe a universal service count or size.
Avoid equating a monolith with an unstructured codebase. Internal boundaries, clear dependencies and deliberate data access can make a single deployment maintainable. Conversely, distributing tightly coupled code across processes does not automatically create useful independence.
For planning, draw the deployment units and the important calls between them. Then identify which changes can be released independently in practice. That reveals more about the architecture than the names assigned to its repositories.
Ask what independence would achieve
A capability may benefit from separate deployment if a team needs to release it on a different schedule without coordinating unrelated changes. Another may need a distinct runtime or an independently managed workload. State the specific benefit before creating the boundary.
Check whether the underlying dependencies support that independence. If every service release requires simultaneous schema and client changes across the application, the deployment separation may add coordination rather than reduce it.
Consider the frequency and consequence of the current constraint. A rare reporting job does not necessarily justify a permanently separate business service. A background worker within a simpler architecture may address the workload with less operational overhead.
Use an observable outcome to evaluate the decision. It might be a shorter release path for one capability or isolation of a measured processing bottleneck. “Prepare for scale” needs a more concrete explanation before the organisation commits to the cost.
Keep business boundaries clear
Identify capabilities with their own rules and meaningful ownership. Orders, fulfilment and customer support may have distinct responsibilities, but their exact boundaries depend on the business. Avoid copying a generic architecture diagram without testing it against real workflows.
Look for operations that need to change together. If two pieces constantly coordinate to preserve one business rule, separating them may create a difficult transaction boundary. The team should understand that cost before deciding where the network belongs.
A modular monolith can help clarify boundaries while retaining a simpler deployment. Define module interfaces and discourage unrestricted access to another module's internals. This requires discipline; the deployment model does not enforce every architectural rule by itself.
Document dependencies that cross boundaries. Whether the call is in-process or remote, the receiving capability should have a clear contract. That preparation can make a later extraction easier if a real need for independent operation emerges.
Consider the team that will operate the design
Several services introduce more deployment, monitoring and incident responsibilities. A team needs the skills and capacity to manage them alongside product development. Count the operating work, not only the code required for each service.
Microsoft's architecture guidance discusses benefits such as independent deployment alongside challenges including distributed-system complexity, data consistency and testing. Evaluate those trade-offs against your actual team and workload rather than treating them as automatic advantages.
Assign ownership that survives staff absence. A service maintained by one specialist without usable documentation or support coverage may become a fragile dependency. The organisation should know who responds when it fails and who can safely change it.
Team boundaries can support service boundaries, but organisational separation alone does not settle the data and workflow design. Two teams still need an agreed contract and a way to coordinate changes that affect shared business outcomes.
Compare data consistency requirements
Within a suitable single-database design, a transaction can protect related changes under the database's supported semantics. When work spans services and separate stores, maintaining the business outcome may require a different coordination and recovery approach.
Identify which changes must be atomic and where temporary disagreement is acceptable. An account profile update and an order acceptance may have different requirements. Avoid using “eventual consistency” as a vague permission for the interface to show unexplained states.
For distributed workflows, define intermediate states, retries and compensation where appropriate. Compensation is a business action that addresses a completed step; it is not necessarily equivalent to reversing a database transaction. Some effects, such as a message already received, cannot simply be undone.
Test partial progress. If one service completes its action and another fails, the system should retain enough information to resolve the workflow. The customer and support team need a truthful view of what is known.
Count the cost of remote communication
A remote call can fail or time out independently of the caller. The application needs deadlines, error handling and recovery behaviour for those boundaries. In-process code also fails, but it does not introduce the same network uncertainty at every interaction.
Avoid long synchronous chains for a task that needs a prompt response unless the design can meet the resulting dependency budget. One slow service can delay the entire chain. Measure the end-to-end experience rather than celebrating the speed of each isolated component.
Use asynchronous messaging where it fits the business process, with explicit delivery and ordering expectations. Consumers may need duplicate handling and reconciliation. A message broker moves communication work into another mechanism; it does not remove the need to define correctness.
Review payload and call volume. A boundary that requires frequent fine-grained exchanges may be poorly placed or need a more suitable contract. The goal is useful separation, not maximising the number of network requests.
Scale according to the constrained workload
A monolith can run on more than one application instance when its state and deployment design support that arrangement. Microservices are not a prerequisite for adding capacity. The database and external dependencies still need attention in either model.
Independent services can be useful when one capability has a distinctly different demand profile. For example, media processing may require resources unlike ordinary request handling. Verify that separating it addresses a measured need and that the shared dependencies can support the resulting traffic.
Consider simpler isolation first where appropriate. A background worker, bounded queue or separate reporting process may solve the problem without establishing a broad set of independently owned business services.
Test realistic data and concurrency before making a capacity claim. The architecture label does not determine query quality, resource limits or recovery behaviour. Those implementation details often explain the actual bottleneck.
Make deployment and observability part of the estimate
For a monolith, the team needs a dependable build, migration and deployment process for the application unit. For several services, it also needs to manage their versions, configuration and compatibility across independent releases.
Include service discovery or routing, credentials, logs, metrics and tracing where the chosen design requires them. These components need maintenance and access controls. They are part of the product's operating cost rather than free infrastructure around the “real” work.
Connect diagnostic information across a business request. Support and engineering should be able to determine which component failed without collecting sensitive payloads unnecessarily. A distributed system that cannot explain an order's path is difficult to recover confidently.
Plan changes to shared infrastructure carefully. A common authentication service or message platform can affect many components. Service independence does not mean every failure is isolated automatically.
Test the system at several boundaries
Use focused tests for business rules and module or service contracts. Preserve checks that demonstrate meaningful outcomes rather than duplicating every internal detail. The design should help teams identify which behaviour a change might affect.
For remote interfaces, test compatibility and representative failures. A client needs to handle unavailable dependencies, changed optional data and repeated events according to the contract. Schema validation alone does not establish all of that behaviour.
Keep a small set of end-to-end journeys covering critical business work. These checks are valuable in either architecture, especially where several components participate. Make failures diagnosable so the suite does not become a collection of opaque alarms.
Exercise deployment overlap. An older consumer may briefly run against a newer service, or application versions may coexist during rollout. The contract and migration sequence should support the combinations the operating process actually creates.
Compare a modular monolith and service extraction
Imagine an order-management application maintained by one small team. Orders, customer records and staff administration change together frequently, while document generation has become resource-intensive. Splitting every domain into a service may be unnecessary to address that specific pressure.
The team could retain clear modules in one application and move document generation into a bounded worker process. If that meets the workload and release needs, the organisation gains useful isolation without adopting a broad distributed architecture.
Later, a separate team may own document processing for several products and need an independent release cycle. At that point, a service boundary may offer a clearer benefit. The existing module contract and job identifiers can help make the transition deliberate.
This is an illustrative path, not a rule that every product must begin the same way. The lesson is to match independence to a concrete responsibility and revisit the choice when the evidence changes.
Recognise warning signs in either approach
A monolith can become difficult when modules reach freely into each other's data, tests are weak and every change has unpredictable effects. Those are reasons to improve boundaries and delivery discipline; extraction may or may not be the appropriate remedy.
A service architecture can become difficult when deployments require constant coordination, teams share databases without clear ownership and failures cannot be traced. Adding more services may worsen those conditions.
Look for work that consistently crosses the same boundary. It may indicate a misplaced responsibility or an unclear contract. Review the business model before treating every coordination problem as a tooling problem.
Maintain a short decision record for important architectural choices. State the benefit expected, the cost accepted and the signal that would trigger review. This helps future maintainers understand the reason for the current shape.
Separate a module boundary from a deployment boundary
A useful module has a clear responsibility and a supported interface to other parts of the application. It does not need a separate server to provide that clarity. Keeping this distinction visible can help teams improve an existing codebase without committing prematurely to remote communication.
For example, an order module can expose operations that enforce its rules while preventing unrelated code from editing its records directly. A reporting module can consume an agreed representation. Those boundaries support reasoning and testing even when both ship in one release.
If later evidence supports extraction, review what changes when the call becomes remote. The contract needs explicit failure and timeout behaviour, the data boundary needs ownership and the deployment needs compatibility rules. Extraction is more than moving the same functions into another repository.
The reverse decision can also be reasonable. If two services constantly deploy together and share one tightly coupled data model, combining their deployment may reduce operating work while the team clarifies the business boundary. Retain useful internal structure rather than treating consolidation as permission for unrestricted coupling.
Measure the effect on the team's actual work. Can a developer identify the owner of a rule, test a change locally and understand its production impact? These questions remain valuable whether the application runs as one process or several.
Avoid making repository count a proxy for architectural quality. A single repository can contain independently deployed services, and several repositories can still require tightly coordinated releases. Inspect the dependency and deployment behaviour rather than inferring it from source-control layout.
This distinction gives the organisation more options. It can invest in clear responsibilities now, obtain evidence about the need for independent operation and choose deployment boundaries when their benefits justify the additional machinery. The resulting architecture is easier to explain because each separation has a practical purpose.
Use a decision table to organise the discussion
| Situation | Useful direction to investigate |
|---|---|
| One team, frequently changing boundaries | A modular monolith with clear internal contracts |
| One unusually heavy background workload | A bounded worker or targeted service extraction |
| Independent teams with stable capabilities | Services with explicit data and operating ownership |
| Many cross-boundary atomic changes | Reconsider boundaries before distributing the workflow |
| Limited monitoring and deployment capacity | Improve operations before multiplying deployable units |
The table is a starting point for investigation, not a formula. Several conditions can apply at once, and the business's constraints may outweigh a general architectural preference.
Choose the smallest design that can meet the demonstrated requirements and be operated well by the responsible team. Preserve clear boundaries and evidence about their limits. That gives the application a practical route to growth without paying for independence before the organisation knows where it needs it.