A module boundary or a service boundary?

  1. 01ModuleOne release · local calls
  2. 02ServiceOwn release · network contract
Extract a service when independence justifies its operating cost.

Both approaches can work. A monolith is not inherently unmanageable, and a system does not become resilient merely because it runs as more processes.

For a new product, my default is a modular monolith with explicit internal boundaries. A microservice earns its place when an independent boundary solves a current problem and the organisation is ready to operate it.

What changes when you cross the network

Decision areaModular monolithMicroservices
Release unitThe applicationAn individual service
CallsLocalRemote: timeouts, retries, and partial failures
DataA shared transaction is easierData ownership and consistency need explicit design
ScalingUsually scales as one applicationHot components can scale independently
DiagnosticsFewer service boundaries; replicas and workers still need observabilityLogs, metrics, and traces cross component boundaries
Engineering ownershipBoundaries are primarily enforced in codeOwnership and release boundaries reinforce the separation

A network boundary may contain a failure or propagate it through a dependency chain. Independent deployment works only when contracts remain compatible. Separate data stores reduce organisational coupling but make cross-domain consistency harder.

Seven questions before choosing

1. Is the domain boundary stable?

If responsibility still moves between modules every few weeks, physical separation will freeze an accidental model. Improve the boundary inside one application first.

2. Who owns the code and data?

A service without one accountable owner becomes a shared integration zone. The owning team needs responsibility for the contract, data, SLO, releases, and support.

3. What is delaying releases?

If an independent capability repeatedly waits for a shared deployment window, a separate release may help. If manual testing or governance is the bottleneck, splitting the code will not remove it.

4. Does the workload scale differently?

Independent scaling matters when a small part of the product consumes a disproportionate share of resources. A uniformly growing application can often scale through additional monolith instances.

5. Is a separate failure or access boundary required?

This is a strong signal when the component can degrade independently and its dependencies have timeouts, limits, and recovery behaviour. Otherwise, another remote call simply adds a failure mode.

6. What replaces the shared transaction?

A local transaction does not automatically span independent stores. The design often needs idempotent commands, events, compensation and explicitly permitted intermediate states. A distributed transaction is also possible where participants support it, at the cost of coordination and failure constraints. Consistency is a business decision, not just a messaging detail.

7. Can the team operate it?

Multiple services require automated delivery, observability, secrets management, contract tests, on-call ownership, runbooks, and rollback. An orchestrator does not create those practices on its own.

Arguments that are not sufficient

  • “We may grow one day.”
  • “Large companies do it this way.”
  • “Every module should use its ideal language.”
  • “We already have Kubernetes.”
  • “Microservices are more modern.”

Any of these may appear in the background, but none explains the economics of the decision.

A practical sequence

  1. Separate domains and dependencies inside the monolith.
  2. Measure change lead time, release performance, and recovery.
  3. Choose one boundary with clear ownership and a measurable benefit.
  4. Extract its contract and data without a wholesale rewrite.
  5. Compare the before-and-after evidence.

If the new service did not reduce coupling, release delay, or incident blast radius, the system gained complexity without gaining independence.

This is not a choice between “old” and “modern.” It is a choice between two cost models. The better one is the model the team can evolve and operate without relying on heroics.

Illustrative case: notifications

Imagine a product where applications, calculations, and notifications live in one application. The email provider is sometimes slow, and the team proposes a notification microservice.

First ask what needs isolation. If customer requests wait for email delivery, a background queue inside the monolith may remove the dependency. If notifications have their own team, release cadence, channels, and load profile, the service boundary has a stronger rationale.

Extraction still leaves important questions: how does an event reach the queue consistently with an application change, how are duplicates recognised, who notices an unsent message, and what happens when templates change? Independent deployment does not solve these issues. It lets a team ship their implementation separately.

I would compare two small designs: a module with background processing and a separate service. For each, describe ownership, failure behaviour, data changes, releases, monitoring, and maintenance. If the service’s benefit is unclear, keeping the module offers a cheaper way to test the product.

A decision record that can be revisited

A useful ADR records context, the choice, rejected alternatives, and conditions for review. For example: revisit extraction when notification releases regularly wait for the shared cycle and the area has its own team. That is more useful than an indefinite promise to “move to microservices later.”

The style’s core characteristics are described by James Lewis and Martin Fowler. The example above illustrates my decision process; it is not a claim that one architecture suits every organisation.


Originally published on my Telegram channel @it_underside.

Yours, DPUPP

Related Articles