PoEAA's Service Layer pattern describes two variants for implementing service methods: the operation script style and the domain facade style. What distinguishes them, and how would you choose between them for a given use case?
answer
- operation script = logic in the service method
- domain facade = thin service, rich domain objects
- facade wraps richer subsystem + tx/security
- anemic drift risk in domain-facade
- mix per use case, not all-or-nothing
basics
~20 sOperation script means the service method itself contains the step-by-step logic for the use case. Domain facade means the service method is thin and mostly just calls rich behavior already living on domain objects. Pick domain facade when the logic is reused or complex; pick operation script when the use case is simple and one-off.
solid answer
~40 sIn the operation-script variant, each Service Layer method is essentially a transaction script: it directly implements the use case's logic procedurally inside the service class, using domain objects mostly as data holders. In the domain-facade variant, the real behavior lives on domain objects (a rich domain model), and the Service Layer method is a thin facade that just resolves the right objects and delegates to them, plus handles transaction/security concerns. Operation script is faster to write for simple, low-reuse use cases and avoids over-designing a domain model prematurely; domain facade pays off once business rules are complex, reused across use cases, or need to be unit-tested independent of infrastructure. Many real systems mix both: simple CRUD-ish use cases as operation scripts, core business processes as domain facades.
go deeper
Should recognize that operation script keeps the logic in the service method while domain facade pushes it onto domain objects.
Should describe the concrete cost/benefit trade-off (upfront design cost vs long-term duplication/testability) and give an example of when each fits.
Should recognize erosion from domain-facade back to operation-script logic as a real production risk and describe how code review or design norms prevent it.
Should be able to set team-wide guidelines for when to invest in rich domain objects vs accept operation script, weighing project maturity, team size, and rule complexity, and revisit that call as the system evolves.
## Two variants, one pattern Fowler's Service Layer pattern doesn't mandate one implementation style; it explicitly allows two, and the choice between them is really a choice about where an application's business logic physically lives. ## The operation-script variant In the operation-script variant, a Service Layer method contains the actual step-by-step procedure for the use case, written directly in that method (or in small private helpers within the service class). Domain objects in this style tend to be thin — mostly getters, setters, and simple invariants — because the interesting logic (discount calculation, eligibility checks, state transitions) is written as a sequence of statements inside the service method itself. This is structurally the same shape as Fowler's separate Transaction Script pattern, just wrapped inside a Service Layer's use-case boundary instead of being the whole architecture. ## The domain-facade variant In the domain-facade variant, the domain objects are **rich**: an `Order` aggregate has methods like `applyDiscount()`, `addLineItem()`, or `cancel()` that encapsulate the actual business rules and enforce their own invariants. The Service Layer method in this style does very little on its own: 1. it loads the right aggregate(s) via a repository, 2. calls one or two methods on them, 3. and lets the domain objects do the real work. The Service Layer here really is a facade in the Gang-of-Four sense: a simplified, coarse-grained entry point in front of a more complex, richer subsystem (the domain model), plus the transaction/security wrapping that a facade alone wouldn't include. ## Choosing between them The decision between the two is fundamentally a **cost/benefit trade** about investment in the domain model. Operation script has a lower up-front cost: you write the logic once, in one place, without first designing a class hierarchy or figuring out which aggregate should own which behavior. It's the right choice for use cases that are: - genuinely simple, - low-reuse, - or where the 'business logic' is really just orchestration (fetch this, save that, call this other service) rather than domain rules. It's also often the pragmatic choice early in a project, before the real shape of the domain rules has become clear — over-designing a rich domain model for logic that turns out to be simple CRUD is wasted effort and adds indirection for no benefit. The cost of operation script shows up as the codebase grows: because the logic lives in procedural service methods rather than on the objects it concerns, the same rule (say, 'an order over $10,000 needs manager approval') tends to get re-implemented or copy-pasted into every service method that touches order approval, because there's no natural single place for it to live. This is the same duplication problem Service Layer as a whole was meant to solve, just recurring one level down. ## Where domain facade pays off Domain facade pays off exactly where operation script breaks down: - when a business rule needs to be enforced consistently regardless of which use case triggers it, - when the rule is complex enough that testing it in isolation (a plain unit test on the domain object with no database, no transaction, no service layer) is valuable, - or when multiple Service Layer methods need to reuse the same behavior. Its cost is **design overhead** — you have to decide which aggregate owns which behavior, keep the domain model's invariants consistent, and resist the temptation to leak business rules back out into the service methods 'just this once,' which is exactly how domain models become anemic over time even when the team's intent was to build a domain-facade Service Layer. ## Mixing both in one codebase In practice, most real codebases are not purely one or the other — they mix both variants use case by use case. A concrete example: an insurance-claims platform might implement 'lookupPolicyDetails' as an operation script (fetch the policy row, map it to a DTO, done — there's no real business rule here, just data assembly), while implementing 'submitClaim' as a domain facade, because claim submission triggers several interlocking rules (coverage limits, fraud-risk scoring, required-document checks, state-machine transitions from Draft to Submitted to UnderReview) that are reused by both the customer-facing web flow and an agent's back-office tool, and that the team wants to unit-test without spinning up a database. ## The drift to watch for The failure mode to watch for in production is **drift in the wrong direction** for a given use case: a Service Layer method that started as a clean domain-facade call gradually accreting extra if-statements and business logic directly in the service method because it was 'just one more check,' until eventually the domain object is barely used and the service method has silently reverted to an operation script while still being labeled and reviewed as if it were calling rich domain behavior. Code review discipline — pushing new business rules onto the domain object rather than the calling service method — is usually the only thing that keeps a domain-facade Service Layer from eroding back into operation script over time.
- Can a Service Layer written as operation script be refactored into domain facade later without breaking clients?Yes, and that's one of the pattern's benefits — because clients only depend on the Service Layer's method signatures, you can move logic from the service method down into domain objects incrementally, use case by use case, without changing the public API those method signatures expose to callers.
- Does using operation script mean you don't have a domain model at all?Not necessarily — you can still have domain objects with data and simple invariants, they're just not carrying the business-process logic. The line between operation script and Transaction Script as a whole-architecture pattern is that operation script is still wrapped by a proper Service Layer boundary with transaction/security handling, even if the objects underneath are thin.
- How would you unit-test business logic under each variant?Under domain facade you can typically unit-test the domain object directly with no database or transaction involved, since the rule lives on the object. Under operation script the logic usually can't be exercised without also invoking the service method, so tests tend to need a transaction/persistence layer (or mocks for it), which is slower and more brittle.
Operation script is like a personal assistant who does every task themselves from scratch each time; domain facade is like a dispatcher who delegates to specialists (the domain objects) who already know how to do their jobs well.
saying these in an interview costs you the question
- Says the two variants are mutually exclusive at the application level rather than choosable per use case
- Can't explain why unit-testing is easier under domain facade
- Assumes operation script means 'no Service Layer at all'
- Doesn't recognize logic leaking into service methods as domain-model erosion
- Treats the choice as purely stylistic with no cost/benefit basis