Vivek Jalondhara. All writing

Architecture

Start a SaaS Backend with Modules Before Adding Microservices

By Vivek Jalondhara · 5 min read
Published

Use explicit ownership and small module interfaces first. Split deployment when an observed constraint justifies the network boundary.

A service diagram can look organised while the underlying business rules remain tangled. Moving booking, pricing, and notifications into separate processes does not decide who owns a reservation transition or what happens when only half an operation succeeds.

This is an argument for beginning with explicit modules in a hypothetical Node.js booking backend. It is a proposed starting point, not a claim about the Booking Management System’s deployment or a rule that every application should remain one process.

Separate ownership before deployment

Define a booking module by the decisions it owns: whether a reservation can be created, changed, or canceled. Its callers should ask for those operations rather than update its tables directly. The boundary is visible in inputs, outcomes, and access rules, even when all modules run in the same application.

A notification module owns formatting and delivery attempts, but should not decide whether a booking exists. An availability module may supply schedule rules without independently committing reservations. Write down those responsibilities using a concrete request. A folder named bookings does little if every other module still edits its state through shared database helpers.

Expose a small public interface

The package example exports one booking entry point. Node’s exports field can define supported package entry points and reject unlisted package subpaths. It helps communicate which code callers are intended to use. It is not a security sandbox and cannot replace repository import rules or review.

Keep the public operation close to the user action. A createBooking input should contain the validated intention and necessary actor context, not a raw HTTP request object or an arbitrary database transaction assembled by the caller. The operation can then be exercised from an API handler or worker without copying its rules.

Keep one authoritative transaction where it helps

If creating a reservation, acquiring occupancy, and saving its replay result belong to one PostgreSQL transaction, keeping them in one process can make that guarantee direct. A module boundary does not require a separate database or forbid a shared transaction coordinated by the owning operation.

Do not turn that convenience into permission for every module to write every table. Name the owner of each invariant and require callers to go through it. A transaction that spans unrelated responsibilities can become too broad, but a network split that removes atomicity has its own cost. Compare the actual recovery behaviour, not only the directory layout.

Recognize the costs a service split introduces

A remote operation can time out after its work succeeds. Deployments can expose incompatible versions, and events can arrive late or more than once. Microsoft’s architecture guidance treats these coordination and operational concerns as part of microservice design. They do not disappear because each individual codebase is smaller.

For the booking example, extracting notifications might create a useful failure boundary: slow delivery should not hold the reservation response open. Extracting occupancy prematurely could instead turn one local invariant into a distributed workflow. The answer depends on ownership, recovery needs, and deployment constraints rather than the number of folders.

Choose an extraction trigger you can observe

Write a concrete reason to deploy a module separately: its resource usage interferes with another workload, its release cadence is independently constrained, or its isolation requirements cannot be satisfied within the current deployment. Measure the relevant behaviour before changing the topology. A hypothetical future traffic spike is not a complete extraction plan.

Also name the conditions under which the current arrangement remains appropriate. A small team with a coherent release process may gain little from operating additional services. Modules preserve an opportunity to separate later, but they should solve present ownership problems even if extraction never happens. Avoid adding remote-call abstractions solely to simulate a possible future.

Rehearse the boundary without building a platform

Call the booking interface from a small test with an unavailable slot, a denied actor, and a successful reservation. Verify that unrelated modules cannot quietly bypass the intended write path. Review circular imports and direct table access as concrete boundary failures, not just stylistic preferences.

If extraction becomes justified, inventory the interface’s data, transactions, failure outcomes, and compatibility obligations. A boundary that was clear in one process still needs a network contract and durable recovery. Starting with modules earns a simpler initial system and better evidence for later decisions; it does not promise that splitting the system later will be automatic.

Declare the booking package’s supported entry point

Educational package manifest. The referenced public.js would expose booking operations; internal repositories and helpers are not separate supported package imports. This is an interface declaration, not a complete package.

Illustrative example · JSON · Simplified for explanation

{
  "name": "@example/bookings",
  "private": true,
  "type": "module",
  "exports": {
    ".": "./src/public.js"
  }
}
How the example fits together
  1. Assign ownership of booking invariants
  2. Expose the supported operation
  3. Keep transaction rules inside the owner
  4. Extract only for a concrete constraint

Conceptual flow for the example above.

Tradeoff to keep in mind

Package exports discourage accidental deep imports but do not enforce security or prevent all filesystem access. They also do not make direct database writes by another module safe.

Related project: Booking Management System →

References & further reading

Related posts