← Engineering

RECORD / ENG-SYSTEM-BOUNDARIES · REV / 2026-07-04 · STATE / PUBLISHED

Architecture · Jun 12, 2026 · 7 min read

BY / Birdor Engineering · Systems Architecture

Designing system boundaries before service boundaries

A practical way to turn an ambiguous product surface into stable technical seams before choosing infrastructure.

Problem

Teams often start by naming services. That makes deployment topology feel like architecture, while the actual product responsibilities remain unclear. The result is an interface that is difficult to explain and even harder to change.

Summary

Define the unit of responsibility first. Model the inputs it accepts, the decisions it owns, and the signals it publishes. Only then decide whether it needs to become a package, a process, or a separate service.

Boundary sketch

PLATE / MERMAID REFERENCE
flowchart LR
  Client[Client intent] --> Policy[Policy boundary]
  Policy --> Runtime[Runtime boundary]
  Runtime --> Store[Persistence adapter]
  Policy --> Events[Published events]

The important observation is that policy can change without forcing the runtime or persistence adapter to change. This is the seam to protect.

Implementation

Start with a small interface that describes the decision, not the storage operation.

export type AccessRequest = {
  actorId: string;
  resource: "workspace" | "project";
  action: "read" | "write";
};

export function decideAccess(request: AccessRequest) {
  return request.action === "read" ? "allow" : "evaluate";
}

Keep adapters outside the policy module. The policy should be testable with values in, values out.

Trade-offs

This approach introduces a little more naming work at the beginning. It pays back when a product gains more entry points, because policy remains legible rather than being copied into handlers.

Conclusion

Service boundaries are an implementation choice. Responsibility boundaries are the durable engineering artifact.