How does the Domain Model pattern differ from Transaction Script in organizing business logic, and what should actually drive the choice between them?
answer
- behavior lives on objects, not procedures
- polymorphism replaces type-branching
- ORM/mapping cost is the price of admission
- decide by rule complexity, not project size
- watch for overengineering simple CRUD
basics
~20 sDomain Model puts business rules inside objects that represent real concepts, like an Order that knows how to calculate its own total, while Transaction Script keeps rules in separate step-by-step procedures. Pick Domain Model when rules are complex and interact a lot; pick Transaction Script when they're simple.
solid answer
~50 sIn a Domain Model, business objects like Order or Account carry both data and behavior — methods like order.calculateTotal() or account.applyInterest() encapsulate rules and enforce invariants, and objects collaborate via associations and polymorphism. Transaction Script instead keeps each use case as a standalone procedure operating on largely data-only records. Domain Model pays off when logic is rich and rules interact — e.g. pricing depending on customer tier, promotions, and inventory together — and you need to vary behavior polymorphically, like different account subtypes calculating interest differently. It costs more upfront: object-relational mapping complexity, more classes, a steeper learning curve. Fowler's guidance is to use the complexity of the business logic as the deciding factor, not project size — simple CRUD stays Transaction Script even in a large system, and genuinely tangled rules justify Domain Model even in a small one.
go deeper
Should get the basic distinction right — Domain Model puts logic on objects, Transaction Script uses procedures — even with imprecise terminology.
Should explain the decision heuristic (rule complexity, not project size) with a concrete example of rules interacting with each other.
Should discuss the real costs of Domain Model — ORM/mapping complexity, team learning curve — and justify choosing different patterns per subdomain rather than uniformly across a codebase.
Should be able to set review criteria for when a team should invest in a Domain Model, anticipate overengineering risk up front, and connect the choice to long-term maintenance cost and team ramp-up.
## How a Domain Model is put together A Domain Model organizes logic around **behavior-carrying objects** that mirror real business concepts: - an `Order` with a `calculateTotal()` method, - an `Account` with `applyInterest()`, - a `Policy` that knows how to evaluate its own eligibility rules. These objects encapsulate both state and the rules that operate on that state, and they collaborate through **associations** (an Order holds LineItems) and **polymorphism** (a SavingsAccount and a CheckingAccount both implement applyInterest() differently, so calling code doesn't need to branch on account type). Persistence is handled by a separate layer — typically an ORM or a Data Mapper — precisely so the domain objects don't need to know anything about SQL or the database schema. This is the fundamental mechanical difference from Transaction Script: instead of one procedure per use case that does validation, calculation, and persistence inline, the logic is decomposed into many small object-level methods that get composed together, often across multiple use cases, because the same object method (`order.calculateTotal()`) can be reused by checkout, by an admin reprice screen, and by a nightly reconciliation job alike. ## Why it exists Domain Model exists because, as business rules multiply and start interacting with each other, procedural branching grows combinatorially — a pricing calculation that depends on customer tier, an active promotion, and inventory state quickly turns into deeply nested conditionals if written procedurally. Object-oriented encapsulation lets a rule live exactly where it belongs: - discount logic only touches a Discount-related class, - interest calculation only touches Account subtypes, - and calling code doesn't need to know the details, just that it can ask the object to do the right thing. This is what gives Domain Model its **reuse and consistency** properties: because the rule is a method on an object rather than a copy-pasted block of logic, every caller that reaches the object automatically gets the current, correct behavior. ## The trade-off, two-sided The trade-off is real and two-sided. **The benefit** is: - reduced duplication (one source of truth per concept), - better unit testability (domain object logic can be tested with plain in-memory objects, no database needed), - and clean support for polymorphic variation — adding a new Account subtype doesn't require touching existing code elsewhere, which is the Open/Closed Principle in action. **The cost** is the object-relational impedance mismatch: - you now need a mapping layer (Hibernate, JPA, an equivalent Data Mapper) to translate between the object graph and relational tables, - more moving parts overall (repositories, mappers, aggregate boundaries to design), - a steeper learning curve for engineers unfamiliar with rich OO domain modeling, - and a genuine risk of over-applying it — building an elaborate object model with DDD tactical patterns for what is actually simple CRUD, which slows delivery for no behavioral payoff and produces a codebase full of almost-empty classes. ## Failure modes at both extremes Failure modes show up at both extremes. - **Forcing a genuinely simple problem into a rich Domain Model** produces excessive ceremony: many small classes with barely any logic, significant time spent on object-relational mapping concerns instead of shipping business value, and a team that's slower for no real benefit — essentially a self-inflicted complexity tax. - **Leaving a genuinely complex problem as Transaction Script** produces the opposite failure: logic ends up copy-pasted with drift across scripts, or gets crammed into stored procedures where it's hard to test and change safely, and every new rule variant triggers 'shotgun surgery' across many files because there's no natural single home for the rule. ## A concrete scenario, and what should drive the choice A concrete real-world scenario: an e-commerce checkout flow that started with a flat-rate tax and flat shipping fee is a textbook fit for Transaction Script, and many teams run that successfully for years without issue. As the business adds progressive discount tiers, a loyalty program, multi-jurisdiction tax rules, and bundle promotions that all interact with each other, the pricing logic starts to genuinely tangle — at that point teams typically refactor toward a Domain Model, introducing an Order aggregate collaborating with Discount and Tax strategy objects, because the rule interactions no longer fit cleanly inside procedural scripts without heavy duplication or unmanageable branching. Fowler's own framing of the decision is worth internalizing directly: 'the primary trade-off is between the complexity of handling elaborate logic and the simplicity of understanding straightforward operations' — meaning the right question is always 'how complex are the rules and how much do they interact,' not 'how big is this project' or 'which pattern is more modern.'
- What's a concrete signal during development that a Transaction Script codebase should start moving toward a Domain Model?A growing conditional chain that branches on a type or category field, and that same field being switched on in more than one script, is the classic signal — it means the logic wants to become polymorphic behavior owned by subclasses rather than duplicated branching. Rising defect rates from a rule being applied inconsistently across scripts is a second, equally strong signal.
- Can you mix the two patterns in the same system?Yes, and it's common and often correct — simple CRUD screens and reports can stay as thin Transaction Scripts or services, while a genuinely complex subdomain, like pricing or eligibility, gets a rich Domain Model. Fowler explicitly endorses this coexistence rather than forcing one style across the whole codebase.
Like organizing a company either as a checklist a single clerk follows for every customer request (Transaction Script), or as specialized departments and employees who each own their piece of expertise and collaborate on a request (Domain Model).
saying these in an interview costs you the question
- says Domain Model is always superior regardless of logic complexity
- picks a pattern based on team/project size rather than rule complexity
- doesn't mention ORM/object-relational mapping cost as part of the trade-off
- thinks polymorphism doesn't apply or isn't relevant here
- can't give a concrete example of rule interaction that would justify Domain Model