What is the Transaction Script pattern for organizing business logic, and what does a single script typically do?
answer
- one procedure per use case
- no shared object model
- Gateway/DAO for DB access
- duplication + long-method risk
- fine for simple/CRUD-shaped logic
basics
~20 sA Transaction Script is one procedure that handles a single business request end to end: it reads input, runs the rules step by step, talks to the database, and returns a result. No shared object model, just a top-to-bottom recipe per action.
solid answer
~30 sTransaction Script organizes domain logic as one procedure per business transaction (e.g. placeOrder, transferFunds). Each script directly orchestrates validation, calculation, and persistence for that use case, usually calling the database through a Gateway/DAO. There's no shared object model, so logic isn't reused via polymorphism, though duplication can be reduced with shared subroutines. It's simple to build, test, and reason about for straightforward CRUD-heavy or calculation-heavy work, but tends to duplicate logic and grow into long, tangled methods as business rules multiply and start interacting with each other.
go deeper
Should describe Transaction Script as one procedure per business action, be able to say it doesn't use a shared object model, and note that database access happens inline, typically via a gateway or DAO.
Should additionally explain how to reduce duplication with shared subroutines and identify the signal that scripts are becoming unwieldy long methods.
Should discuss real trade-offs — testing implications, when duplication becomes a genuine maintenance liability — and place Transaction Script within a broader system that may use other patterns elsewhere.
Should defend Transaction Script as a legitimate long-term architectural choice for genuinely simple domains, articulate the cost of abandoning it prematurely, and connect the decision to team velocity and system-wide consistency.
## What a script actually does **Transaction Script** is the simplest of the three classic domain-logic patterns (alongside **Table Module** and **Domain Model**). Mechanically, you write one procedure per business transaction — a use case like `placeOrder`, `calculateShipping`, or `transferFunds` — and that single method does everything: 1. it validates the incoming request; 2. pulls whatever rows it needs from the database (often via a thin Gateway or DAO that just runs SQL); 3. applies the business rules inline as ordinary conditionals and calculations; 4. and writes the results back to the database, usually inside one transaction boundary matching the method's name. There is no object graph representing the domain; data mostly moves through as primitives, DTOs, or raw database rows. Multiple scripts are commonly grouped into a Service class per subject area (e.g. `OrderService`, `PaymentService`) purely for organizational convenience — that grouping does not imply a shared behavioral model, since each method inside is still self-contained. ## Why the pattern exists The pattern exists because a large fraction of real business logic is genuinely simple: - a handful of validations, - a calculation or two, - a database read and write. Building a full object model with associations, identity, and polymorphism for that kind of logic adds real cost — mapping layers, more classes and interfaces, more indirection to read through — for no behavioral payoff. Transaction Script maps directly onto how most engineers already think procedurally, has almost no learning curve, and maps almost one-to-one onto database operations, which makes it fast to write and fast for a new team member to trace end to end. ## The trade-off — duplication versus simplicity The trade-off is duplication versus simplicity. **On the plus side:** - each script is easy to understand in isolation, - there is minimal object-relational mapping overhead since you're usually working close to raw rows or DTOs, - and performance is often good because there's little indirection between the request and the database call. **On the minus side**, similar business steps get repeated across scripts unless someone deliberately factors them into shared subroutines, and even with subroutines the discipline to keep reusing them (instead of re-implementing 'close enough' logic) tends to erode over time. Testing at the level of a whole script often forces either a real database or heavy mocking of the gateway, since business logic and persistence aren't cleanly separated. As the number of business rules grows and the rules start interacting — pricing that depends on customer tier, order type, and an active promotion, for instance — scripts accumulate deeply nested conditionals and become 'long methods' that are expensive and risky to change. ## The failure mode in production In production, the classic failure mode is **duplication drift**: a new business rule (say, a new discount type or a new tax jurisdiction) has to be added to every script that touches pricing, and inevitably one or two scripts get missed or implemented slightly differently, producing inconsistent behavior — a customer gets charged the old tax rate through the batch-invoice script while the checkout script applies the new one correctly. A related symptom is business logic leaking sideways into UI controllers or database stored procedures because there's no natural single home for a piece of shared computation, so the same rule ends up implemented in two or three places with subtly different edge-case handling, which shows up as production bugs that are hard to trace because 'the logic' isn't in one place. Over time, complexity concentrates in a handful of scripts that become the ones nobody wants to touch: - high defect rate, - slow code review, - and a de facto bus-factor risk since only one or two engineers fully understand them. ## Where it shows up in practice In practice, Transaction Script is how a great deal of real transaction-processing software is actually built: - simple CRUD SaaS features, - batch billing/invoicing jobs (a `processMonthlyInvoice` procedure), - request handlers in early-stage APIs, - and simpler ledger/funds-transfer processing. Many 'fat controller, thin model' Rails or Django codebases are effectively Transaction Script wrapped in an MVC skin even though they look object-oriented from the outside, because the controller action (or a thin service it calls) is doing all the orchestration procedurally rather than delegating to behavior-rich domain objects. Recognizing that is useful: the pattern isn't a mistake in those codebases, it's an appropriate match to genuinely simple logic — the mistake would be sticking with it once the rules interact enough that duplication and long-method risk start dominating, which is the signal to consider Domain Model instead.
- How do you reduce duplication between Transaction Scripts without moving to a full Domain Model?Extract the shared step (a calculation, a validation rule) into a reusable subroutine or a small stateless helper function that every script calls, rather than reimplementing it inline in each one. This keeps the procedural style but gives the rule a single source of truth, which is usually enough until the rules start genuinely interacting with each other.
- How do you unit test a Transaction Script that talks directly to the database?Introduce a thin Gateway or Repository interface behind the direct database calls and substitute a fake or in-memory implementation in tests, so the script's logic can be exercised without a real database. This doesn't require adopting a domain model, just isolating the I/O boundary.
- When does duplication across scripts become a real cost rather than premature optimization?When the same rule has to change for a business reason and someone can point to more than one place it lives — that's the moment drift becomes likely. Before that, duplicating a two-line check across scripts is often cheaper than the abstraction it would take to share it.
Like a fast-food order line: one worker follows a fixed checklist front to back for each order, rather than consulting a shared recipe book of ingredient objects that know how to combine themselves.
saying these in an interview costs you the question
- claims procedural organization is inherently bad regardless of context
- doesn't mention the duplication/long-method risk as scripts multiply
- thinks scripts can't call shared subroutines to reduce repetition
- conflates the pattern with stored procedures only
- says it never fits real production systems