Install
openclaw skills install bookforge-distribution-boundary-designerDistribution design for enterprise systems: decide whether to distribute, where to draw the service boundary, and how to implement it with Remote Facade and Data Transfer Object (DTO). Use when deciding microservices vs monolith, evaluating process boundaries, extracting services, designing remote APIs, choosing coarse-grained API shape, preventing distribution-by-class anti-pattern, applying Fowler's First Law of Distributed Object Design, designing service extraction strategy, determining when distribution is warranted vs cargo-culting microservices, implementing Remote Facade pattern, designing DTOs independent from domain objects, choosing between gRPC vs REST vs message queue vs GraphQL for service boundary, monolith decomposition, service boundary design, remote API design, distribution strategy, when to distribute, process boundary decision, coarse-grained interface design.
openclaw skills install bookforge-distribution-boundary-designerGuides you through the decision of whether to distribute a system across processes or machines, where to draw the boundary if distribution is needed, and how to implement the boundary using the Remote Facade and Data Transfer Object patterns. Grounded in Fowler's First Law of Distributed Object Design and the pattern pair that makes distribution workable when it cannot be avoided.
Use this skill when your team is considering breaking a monolith into services, extracting a subsystem as a remote service, reviewing whether a microservices architecture is appropriate, or designing the interface at an existing remote boundary. Also use it when an existing remote API shows signs of the distribution-by-class anti-pattern (too many fine-grained calls per operation), or when domain logic has leaked into remote shells.
Prerequisites: a description of the system's purpose, team structure, deployment needs, and any known scaling or security zone requirements. A codebase or architecture diagram is helpful for detecting existing boundary problems.
Gather these before proceeding:
Required:
Helpful:
Defaults if not provided:
Sufficiency check: If the only stated motivation is "we want microservices," "clean architecture," or "team size," the filter step will almost certainly return "do not distribute." Be explicit about this.
WHY: The single most common architecture mistake is distributing a system that does not need it. Fowler names this explicitly: "Don't distribute your objects." Distribution incurs latency, partial failure, marshaling cost, and interface rigidity — costs that are paid on every call, forever. Every process boundary is a tax.
Check each of Fowler's seven legitimate distribution reasons. The system should distribute ONLY if at least one applies:
| Reason | Check |
|---|---|
| Different client machines (desktop vs server, browser vs app server) | Does the client run on a different physical machine? |
| Application server ↔ database process boundary | Standard; SQL is designed for this. |
| Web server and app server must be separate processes | Vendor constraint, security zone, or scaling forces? |
| Independent scaling requirements | Does a hot subsystem need to scale at a dramatically different rate? |
| External vendor / purchased package software | Does a package run in its own process with a coarse-grained interface? |
| Security zone boundary (DMZ, internal network) | Is a firewall or network zone required between subsystems? |
| Different hardware or OS requirements | Does a subsystem require specialized hardware or a different OS? |
IF none apply → Recommend modular monolith: keep the application in one process, divide it into packages/modules with clear interfaces. Name this explicitly. Do not proceed to boundary design.
IF one or more apply → Identify only the subsystem pairs that cross a legitimate boundary. Every other subsystem pair should remain in-process.
WHY: The distribution-by-class anti-pattern (one remote component per domain class) multiplies call counts and destroys performance. A remote boundary should span a subsystem — a coherent cluster of domain classes that collaborates internally and exposes a small number of use-case-shaped operations externally.
For each legitimate boundary identified in Step 1:
Signal that a boundary is too fine-grained: "GetCustomer() then GetOrders() then GetLineItems() then GetProducts()..." — four separate calls to display a single order screen. This should be one call: GetOrderSummary(orderId).
WHY: A Remote Facade is a thin coordination shell — its only job is to translate coarse-grained remote calls into sequences of fine-grained domain object calls. All domain logic lives in the fine-grained domain objects, not in the facade. A facade with domain logic becomes a second domain layer that is harder to test, harder to evolve, and violates the principle that the application should be runnable entirely in-process without the remote shells.
For each boundary, design the Remote Facade:
GetOrderSummary(orderId), SubmitPaymentAuthorization(authDTO), not GetOrder(), GetCustomer(), UpdateOrderStatus()if (order.status == SHIPPED) applyLateCharge() in the facade. That belongs in domain objects.WHY: A DTO is a data carrier designed for the wire, not for the domain. Auto-deriving DTOs from domain classes couples the wire format to the domain model — any domain refactoring forces a wire format change, which may require client updates. DTOs designed around use cases are stable because use cases change less often than domain internals.
For each Remote Facade method, design the corresponding DTO(s):
WHY: The three most common mistakes at distribution boundaries are predictable and detectable in advance.
Check each boundary against these anti-patterns:
| Anti-Pattern | Signal | Remedy |
|---|---|---|
| Distribution by class | One remote component per domain class; dozens of remote calls per user interaction | Consolidate to subsystem-level Remote Facades with use-case methods |
| Domain logic in Remote Facade | Conditional logic, business rules, or workflow coordination in the facade | Move logic to domain objects or a non-remote Service Layer; reduce facade to thin delegation |
| Chatty fine-grained remote interface | N calls per screen/operation where N > ~3; individual property getters exposed remotely | Redesign as coarse-grained bulk accessor / command method |
| DTO-domain coupling | DTO fields map 1:1 to domain class fields; DTO imports domain classes | Introduce Assembler; redesign DTOs around use-case needs |
| Distribution without legitimate reason | Motivation is trend, architecture taste, or "clean code" | Recommend modular monolith; document the test that should be passed before re-evaluating |
WHY: The interface style determines coupling tolerance, latency profile, and async capability. Fowler's 2002 guidance was RPC vs XML/HTTP; the modern decision space is richer.
| Style | Best for | Trade-offs |
|---|---|---|
| gRPC (binary, proto) | Internal service-to-service, high throughput, same-platform | Tight schema coupling; not browser-native |
| REST / JSON | External APIs, browser clients, cross-platform, public APIs | Looser coupling; HTTP overhead; no streaming by default |
| Message queue (Kafka, SQS, RabbitMQ) | Fire-and-forget, event-driven, high latency tolerance, decoupled producers/consumers | Async only; harder to test; eventual consistency |
| GraphQL | Multi-client (mobile + web) with varying field needs; avoiding over-fetch | Query complexity; N+1 risk server-side; schema governance overhead |
Fowler's principle still holds: use the simplest mechanism that works. If both sides share a platform, use gRPC or its modern equivalent. Use REST/JSON when interoperability across platforms or external access matters. Use message queues when decoupling and async throughput matter more than latency.
WHY: The decision must be documented so future architects understand WHY the boundary exists and what test should be passed before adding more. Distribution decisions are expensive to reverse.
Produce a distribution design record containing:
Distribution Design Record (distribution-design-record.md) containing:
# Distribution Design Record: [System Name]
## Summary Decision
[Distribute / Do Not Distribute — one sentence]
## First Law Filter Results
| Reason | Applies? | Notes |
|--------|----------|-------|
| ... | Yes/No | ... |
## Recommended Topology
[Modular monolith / specific service boundaries]
## Boundaries (if distributing)
### Boundary: [Subsystem A] ↔ [Subsystem B]
- Legitimate reason: [reason from filter]
- Interface style: [gRPC / REST / queue / GraphQL]
- Remote Facade: [FacadeClass]
- [method(params) → ReturnDTO] — [WHY this use-case shape]
- DTOs:
- [DTOName]: fields=[...], aggregates=[domain objects], assembled by=[AssemblerClass]
## Anti-Pattern Audit
| Anti-Pattern | Result | Notes |
|--------------|--------|-------|
| Distribution by class | Pass/Fail | ... |
| Domain logic in facade | Pass/Fail | ... |
| Chatty interface | Pass/Fail | ... |
| DTO-domain coupling | Pass/Fail | ... |
| Distribution without reason | Pass/Fail | ... |
## Non-Distribution Alternatives Considered
[What modular monolith structure was evaluated and why distribution was preferred]
1. The First Law is a default, not a suggestion. Distribute only when you have a concrete, operational reason from Fowler's list. "We want microservices" is not a reason. "The payment processor must be PCI-compliant in a separate security zone" is a reason. The default is always in-process.
WHY: Remote calls are orders of magnitude slower than in-process calls. Every process boundary is a permanent tax on every call that crosses it. The cumulative cost of unjustified distribution dwarfs the cost of an in-process module boundary.
2. Boundaries are per subsystem, not per class. A Remote Facade corresponds to a subsystem (a cluster of collaborating domain objects), not to an individual class. If every domain class has a remote interface, the boundary design is broken.
WHY: Each fine-grained call to a remote class pays the full inter-process latency cost. A subsystem-level facade batches those calls into one network round-trip per use case.
3. Facades contain no domain logic — none. A Remote Facade is a translation layer: coarse-grained interface → fine-grained domain object calls. Any conditional logic, business rules, or workflow coordination in the facade must be moved to domain objects or a non-remote Service Layer.
WHY: A facade with domain logic becomes a hidden second domain layer. The application can no longer be tested or run in-process without the remote shell, because the logic lives there. This creates a testing and evolution trap.
4. DTOs are designed for the interaction, not the domain. Each DTO is shaped around a specific client use case — what data that client needs to display or send in a single interaction. DTOs are not auto-generated mirrors of domain classes.
WHY: Domain classes change frequently as business rules evolve. DTOs change when the client interaction changes. Coupling these two change rates together means every domain refactoring potentially breaks clients across the wire.
5. The modular monolith is always on the table. When a team says "we need microservices for separation of concerns," the correct counter-offer is: separate your packages/modules/bounded contexts within a single process. You get team ownership, clear interfaces, and independent evolution without network cost.
WHY: The costs of distribution (latency, partial failure, operational complexity, debugging difficulty) are real and ongoing. The benefits of a modular monolith (same testability, same team ownership, same interface discipline) are available without those costs.
6. Send more data per call rather than fewer. When designing DTOs and facade methods, err toward aggregating more data in one call rather than planning a second call. Over-fetching slightly is far cheaper than a second round-trip.
WHY: Remote call latency is fixed overhead per call. Bandwidth is cheap. If a client might need the order's line items after getting the order header, include the line items in the initial DTO.
Scenario: A 12-person engineering team across two squads is building an internal CRM. Engineering lead proposes microservices "for clean boundaries between the sales squad (contacts, deals) and the support squad (tickets, SLAs)."
Trigger: "Should we use microservices for our two-squad CRM?"
Process:
contacts-deals package and a tickets-slas package with explicit module boundaries, shared domain types, and clear internal APIs. Each squad owns their package.CRMService facade handles screen-level operations for both squads.Output: Distribution Design Record stating "Do not distribute internally." Modular monolith with squad-owned packages. One Remote Facade for the browser-server boundary.
Scenario: E-commerce platform. Payments team requires PCI-DSS compliance in an isolated network zone. The search subsystem handles 50x the load of the product catalog at peak.
Trigger: "We need to separate payments for PCI compliance and search for scaling. How do we design those boundaries?"
Process:
PaymentService):
AuthorizePayment(orderId, paymentMethodDTO) → PaymentResultDTOCapturePayment(authorizationId) → CaptureResultDTORefundPayment(paymentId, amount) → RefundResultDTOGetCard(), UpdateCard(), GetAuthorization() — these are fine-grained domain callsPaymentMethodDTO (cardToken, billingAddress, amount), PaymentResultDTO (authorizationId, status, errorCode). Assembled from domain card/billing objects. Not coupled to domain class structure.SearchService):
SearchProducts(query, filters, pagination) → ProductSearchResultDTOSuggestProducts(partialQuery) → SuggestionDTO[]Output: Distribution Design Record with two boundaries (PCI, scaling), facade specs, DTO specs, interface styles.
Scenario: Legacy J2EE system from 2005. Every entity bean (Customer, Order, OrderLine, Product, Address) is a separate remote EJB. Displaying an order summary screen makes 15-20 remote calls. Performance is unacceptable.
Trigger: "Our EJB app is painfully slow. Each screen makes dozens of remote calls. How do we fix this?"
Process:
OrderService Remote Facade with use-case methods: GetOrderSummary(orderId) → OrderSummaryDTO, UpdateOrderStatus(orderId, statusDTO).OrderSummaryDTO aggregates order header + customer name + line items + product names in one call.Output: Refactoring plan with identified chatty calls, new facade spec, DTO design, migration sequence.
references/distribution-reasons-checklist.md — Fowler's seven legitimate distribution reasons as a quick-reference filterreferences/remote-facade-design-guide.md — detailed guidance on Remote Facade granularity, method design, session facade vs remote facade distinctionreferences/dto-design-guide.md — DTO design patterns, assembler pattern, serialization format trade-offs, modern parallels (proto, JSON schema, GraphQL types)references/interface-style-selector.md — gRPC vs REST vs message queue vs GraphQL selection criteriaThis skill is licensed under CC-BY-SA-4.0. Source: BookForge — Patterns of Enterprise Application Architecture by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford.
Install related skills from ClawhHub:
clawhub install bookforge-enterprise-architecture-pattern-stack-selectorclawhub install bookforge-domain-logic-pattern-selectorOr install the full book set from GitHub: bookforge-skills