When a Service Layer's methods are also exposed to remote clients through a Remote Facade, how should method design differ from a Service Layer only ever called in-process, and why does that matter?
answer
- remote calls cost latency + can partially fail
- coarse-grained facade batches fine-grained calls
- DTOs not domain objects over the wire
- facade wraps existing Service Layer, no logic duplication
- chatty API = too many round trips
basics
~20 sAcross a network, every call is slow and can fail on its own, so remote-facing methods should be few, chunky, and do a lot per call — pass whole batches of data instead of tiny get/set calls. In-process calls are cheap, so they can be small and frequent.
solid answer
~50 sIn-process Service Layer calls are effectively free — a fine-grained API with many small method calls is fine because there's no network round trip, serialization, or partial-failure risk between them. Remote Facade wraps that same Service Layer with coarse-grained methods designed for a small number of round trips: batching what would be several fine-grained calls into one method that takes and returns whole DTOs, because each remote call costs latency, serialization overhead, and its own independent failure mode. Concretely, this means the Remote Facade layer passes data-only objects (DTOs) rather than domain entities across the wire — domain objects carry behavior, lazy-loading proxies, and internal references that don't serialize safely or securely — and its methods are shaped around client use cases ('getOrderSummary') rather than mirroring internal domain object structure. The Remote Facade is typically implemented as a thin wrapper that assembles/disassembles DTOs and delegates to the same in-process Service Layer used by local callers, so business logic isn't duplicated between the two.
go deeper
Should know that calls over a network are much slower/less reliable than in-process calls, so remote APIs should bundle more work into fewer calls.
Should be able to explain the coarse-grained-vs-fine-grained distinction concretely and why DTOs, not domain entities, cross the network boundary.
Should design a Remote Facade as a thin wrapper over an existing Service Layer without duplicating business logic, and reason about idempotency for retried remote operations.
Should weigh the mapping-layer maintenance cost against chatty-API risk at an architecture level, and decide when a system needs a Remote Facade at all versus keeping the Service Layer in-process only (e.g., in a modular monolith).
## Two complementary patterns The distinction Fowler draws is between an application's Service Layer, which is the boundary for use-case logic, and Remote Facade, a separate pattern for exposing part of that Service Layer to callers running in a different process (a different server, a mobile app, a partner's system). The two patterns are complementary: Remote Facade is typically implemented as a thin wrapper layer sitting in front of an existing, fine-grained (or moderately-grained) Service Layer, translating between the network-facing API and the in-process one, rather than being a wholesale reimplementation. ## What a network call costs The mechanical reason method design has to change comes down to what a network call costs that an in-process call doesn't. An **in-process** method call is, for practical purposes, free: a few nanoseconds of stack manipulation, no serialization, and if it fails it fails as an exception in the same process with a full stack trace and no ambiguity about whether the call happened. A **remote** call, in contrast, has to: - serialize its arguments (turning objects into bytes, e.g., JSON or a binary protocol), - send them over a network with real, variable latency (milliseconds to seconds), - deserialize on the other end, - and — critically — can fail in ways an in-process call cannot: the network can drop the request before it arrives, or drop the response after the server has already applied it, leaving the caller unable to tell whether the operation actually happened. Because of that cost and that ambiguity, you want as few remote round trips as possible per client operation, and you want each one to be self-contained enough that partial failure is meaningful and recoverable, rather than leaving the client and server in disagreement about what state changed. ## Two design shifts Concretely, this produces two design shifts. 1. **First, coarse-grained methods**: instead of a remote client making five separate fine-grained calls — `getOrder(id)`, `getCustomer(order.customerId)`, `getLineItems(order.id)`, and so on — a Remote Facade exposes one method, say `getOrderDetails(orderId)`, that internally makes those several in-process Service Layer/domain calls and assembles one composite result to send back in a single round trip. This trades a small amount of extra data transferred (the client might get fields it doesn't strictly need this time) for a large reduction in round-trip count, which usually dominates real-world latency far more than payload size does. 2. **Second, data transfer objects instead of domain objects**: domain entities often carry lazy-loading proxies (which would try, and fail, to hit the database again when accessed on a remote client), bidirectional object graphs that don't serialize cleanly (or that serialize into huge, cyclic payloads), and sometimes fields that shouldn't be exposed externally at all for security reasons (internal audit fields, other customers' data reachable via a navigable association). A DTO is a purpose-built, flat, serialization-safe snapshot of exactly the fields a given remote operation needs to send or receive, decoupling the wire format from the internal domain model's shape so the two can evolve independently. ## The cost of the mapping layer The trade-off is real complexity cost: maintaining a mapping layer between domain objects and DTOs is extra code, and it's easy for that mapping to drift out of sync with the domain model (a new field added to an aggregate silently doesn't show up in the DTO, or vice versa a DTO field survives after the corresponding domain concept was removed). There's also a design temptation to let the DTO shape leak backward into the domain model — designing entities to be 'DTO-friendly' — which recreates some of the same anemic-model risk discussed for operation script, just triggered by the remote boundary instead of by a Service Layer method. ## One order, two granularities A concrete real-world scenario: a retail company's core Order Service Layer, used in-process by its own web application with a dozen fine-grained methods (`getOrder`, `getOrderStatus`, `getShippingAddress`, ...), also needs to expose order data to a third-party fulfillment partner over a REST API. Rather than exposing all dozen fine-grained methods as dozen separate REST endpoints — which would force the partner's integration to make many slow, cross-internet round trips just to render one order — the team adds a Remote Facade with a single `GET /orders/{id}` endpoint returning one composite `OrderDto` with everything the partner needs (items, shipping address, status) in one response, implemented by calling the existing in-process Service Layer methods internally and assembling the result. The partner integration ends up making one HTTP call where the in-process web app still makes several fine-grained calls — the same underlying Service Layer logic, exposed at two different granularities to two different kinds of caller. ## Symptoms in production In production, symptoms of getting this wrong show up as: - **chatty remote APIs** — a mobile client making a dozen sequential requests to render one screen, dominated by round-trip latency rather than actual work; - or the opposite failure of **leaking internal domain objects** directly as the wire format, which tends to surface as serialization errors on lazy-loaded associations, or occasionally as accidental data exposure when a domain object's full object graph gets serialized further than intended.
- Does Remote Facade replace the in-process Service Layer, or sit alongside it?It sits in front of it as a thin adapter layer — the Remote Facade's job is granularity and serialization translation, not reimplementing use-case logic, so it typically delegates to the same in-process Service Layer methods that local callers use, keeping business logic in one place.
- Why not just serialize domain entities directly instead of maintaining separate DTOs?Domain entities often carry lazy-loading proxies that fail or trigger unwanted database calls when touched outside their original transaction, bidirectional associations that produce huge or cyclic serialized payloads, and internal fields that shouldn't be exposed externally; DTOs are a controlled, purpose-built projection that avoids all three problems.
- How does this pattern interact with a use case that partially fails partway through a remote call — say the network drops the response after the server already committed the change?The client can't distinguish 'never received by the server' from 'received, applied, but response lost,' so remote-facing operations are usually designed to be idempotent (safe to retry) — for example, by having the client generate an idempotency key so a retried request doesn't double-apply the operation.
It's the difference between calling a colleague at the next desk (cheap, ask as many small questions as you like) versus calling an overseas office on an expensive, laggy line (you prepare one thorough agenda and cover everything in a single call).
saying these in an interview costs you the question
- Suggests exposing domain entities directly over the wire as the API response
- Doesn't recognize round-trip count as the dominant latency cost of a remote API
- Thinks Remote Facade should contain its own copy of the business logic separate from the Service Layer
- No answer for what happens when a network response is lost after the server already applied the change
- Assumes in-process and remote APIs should always have identical method granularity