Introduction
You face complex enterprise applications that manage persistent data and intricate business rules. Without a solid structure, your code grows entangled and modification becomes risky.
Martin Fowler provides proven design patterns to tame this system complexity. The book combines foundational architectural narratives with a detailed catalog of enterprise application patterns.
You will learn how to organize applications into layers, implement domain logic, and manage object-relational mapping. The text also covers web presentation strategies, session state management, and concurrency controls.
- How do you structure an enterprise application for maintainability?
- Where do you place domain logic to avoid duplication?
- How do you map in-memory objects to relational databases efficiently?
1. Application Layering and Domain Logic
Enterprise applications are effectively structured using three principal layers: presentation, domain, and data source. The presentation layer handles user interaction and display. The data source layer communicates with external systems and databases. The domain layer contains the core business logic, calculations, and rules without depending on the presentation.
Domain logic must remain strictly separated from presentation code to prevent code duplication and cascading maintenance issues.
Placing business rules or conditional display logic inside the presentation layer creates architectural leakage. Separating domain behavior into distinct boolean or processing methods ensures that user interfaces can change or multiply without duplicating business logic.
A system colored products red if sales exceeded the previous month by 10 percent by putting comparison logic directly in the presentation layer, which violated clean layering principles.
Principal Layers
You encounter complex software systems that require disciplined organization to remain maintainable over time. Without clear boundaries, presentation code intertwines with database queries and business calculations. You can avoid this trap by separating your codebase into distinct horizontal tiers.
- Presentation layer for user interaction and display.
- Domain layer for core business logic and rules.
- Data source layer for databases and external communication.
Organizing Domain Logic
Once you establish your layers, you must decide how to structure the business logic within your domain layer. Different problems demand different organizational styles based on complexity.
A Transaction Script organizes business logic as a single procedural routine for each user action. Each action a user wants to perform has its own dedicated script. It takes presentation input, validates and calculates, talks to the database, and replies to the presentation. It is easy to understand and works well with row or table data gateways, but complex domain logic leads to code duplication and a tangled web of routines.
A retailing system might have separate Transaction Scripts for checkout, adding items to a shopping cart, and displaying delivery status.
A revenue recognition script for database products allocates total revenue into three parts over zero, thirty, and sixty days.
A Domain Model organizes business logic around domain nouns and object classes rather than procedural actions. Instead of one large routine holding all logic for a user action, the logic is distributed among domain objects where each object handles the behavior relevant to itself. It handles increasingly complex logic cleanly via strategies and inheritance, but requires a sophisticated data source layer and a steep learning curve.
A leasing system has classes for lease and asset, where shipment objects contain the logic to calculate shipping charges.
Revenue recognition implemented via a Strategy hierarchy where product types dictate different calculation algorithms.
A Table Module organizes domain logic into a single class per database table that operates on a record set. Unlike a Domain Model which has an instance for each individual database record, a Table Module has only one instance for the entire table and works closely with a Record Set. It offers a middle ground between Transaction Script and Domain Model, fitting exceptionally well into environments like Microsoft COM and .NET that rely heavily on record sets.
A C# implementation using datasets where a Contract table module computes revenue recognitions for contract rows.
A Service Layer can be placed over a domain model or table module to act as an application API and encapsulate transactional and security boundaries. It provides a clear entry point for presentation logic. The behavior inside a service layer can range from a thin facade that delegates entirely to underlying domain objects, to rich procedural use-case controllers containing business logic.
A RecognitionService calculates revenue recognitions, triggers email notifications via an email gateway, and publishes messages through middleware.
2. Mapping to Relational Databases
Bridging in-memory objects and relational databases requires specialized mapping patterns and unit of work coordination. Choosing how domain logic communicates with a relational database depends heavily on domain logic complexity and ranges from simple gateways to data mappers. For simple applications with a close correspondence between classes and tables, Active Record is straightforward. For highly complex domain logic, a Data Mapper completely isolates the domain model from the database schema, allowing both to evolve independently.
A Unit of Work tracks modified objects and sequences database writes, while an Identity Map prevents duplicate instances of the same database row in memory.
A Unit of Work acts as the controller for database mapping by keeping track of all read and modified objects and executing a coordinated commit. An Identity Map ensures that each database row maps to a single in-memory object reference, doubling as a database cache.
- Create a Unit of Work as soon as you start work that affects the database.
- Register each created, changed, or deleted object with the Unit of Work.
- Invoke commit() at the end of the business transaction to process insertNew, updateDirty, and deleteRemoved operations.
David Rice provides a Java example using a Layer Supertype called DomainObject, maintaining lists for new, dirty, and removed objects, and managing them through static thread-local storage.
Structural object-relational mapping bridges the gap between object references and relational foreign keys, collections, and class inheritance hierarchies. Foreign key mappings handle single-valued references and collections. Association table mappings handle many-to-many relationships. For class inheritance hierarchies that relational databases do not support natively, developers choose between Single Table Inheritance, Class Table Inheritance, and Concrete Table Inheritance.
Database Mapping Choices
Storing a database primary key inside an in-memory object maintains identity between the object and its corresponding database row. Relational databases use primary keys to differentiate rows, whereas in-memory object systems handle identity automatically. To write data back, the database primary key must be stored directly in the object's fields. Simple keys are generally preferred over compound or meaningful keys because meaningful keys like Social Security numbers are vulnerable to human error, lack immutability, and cause uniqueness problems.
Using an integral field in .NET domain objects where a constant placeholder like -1 represents a new object that has not yet been saved to the database.
- Choose meaningless and simple keys, typically a long integer, for fast equality checking and incrementing.
- Use a separate key table with a name and next available value column to generate keys reliably without table scans.
- Wrap key generation logic in its own class to enable Service Stubs for testing.
Foreign Key Mapping connects object associations to foreign key references in relational database tables. Single-valued references map directly by storing the target object's database ID in the owning record. Multivalued references, such as collections, require reversing the direction of the reference or using alternative update strategies like delete and insert, adding a back pointer to make the association bidirectional, or diffing collections. Cycles in links can cause stack overflows and are resolved through fully formed creation methods with Lazy Load or by creating empty objects and immediately registering them in an Identity Map.
An album containing a single reference to an artist, where the album table stores an artistID foreign key column.
- Handle collections during updates by either deleting and reinserting dependents, adding a back pointer, or diffing collections against the database or initial read state.
- Use a single SQL query joining multiple tables when query performance needs optimization over issuing separate remote calls.
Association Table Mapping stores many-to-many associations by creating an extra link table containing foreign keys to both linked tables. Because relational databases only support single-valued fields, many-to-many associations cannot use a simple foreign key on either table. Instead, a separate link table is created to record the relationship. This link table has no corresponding in-memory object, no independent ID, and uses a compound primary key composed of the primary keys of the two associated tables.
Mapping an employee class with a collection of skills using an employeeSkills link table with columns employeeID and skillID.
- Query the link table and the target table in stages or via a join to load associated objects.
- Update relationship data by treating the link table like a Dependent Mapping, deleting existing link rows, and creating new ones.
Behavioral and Structural Patterns
Dependent Mapping allows one class to handle the persistence of a child class that naturally appears only within the context of a single owner object. Dependents rely on an owner for database persistence, do not have their own Identity Field, and are never stored in an Identity Map. Because they have no outside references and exactly one owner, updates to collections of dependents can be handled simply by deleting all rows linked to the owner and reinserting the current set of dependents.
Tracks belonging to an album where tracks are loaded, updated, and deleted exclusively through the album mapper.
- Ensure the dependent has exactly one owner and no references from any other object.
- Delete all database rows linked to the owner and reinsert all dependents during an update to avoid complex collection diffing.
Embedded Value maps the fields of a small dependent value object directly into the columns of its owner's database table. Small value objects like money or date ranges do not warrant their own database tables. When the owning object is loaded or saved, the embedded value objects are loaded and saved simultaneously by the owner. This pattern allows SQL queries to be executed directly against the dependent values, unlike serialized large objects.
A product offering containing a base cost represented as a Money value object mapped to base_cost_amount and base_cost_currency columns in the product_offerings table.
- Persist all Value Objects using Embedded Value when they do not require independent database identity or separate tables.
Serialized LOB saves a complex graph of objects by serializing them into a single large binary or character object stored in a database field. Rather than mapping a complex object hierarchy across multiple related tables with expensive joins, the entire graph is serialized into a BLOB, CLOB, or XML string. While this avoids complex relational schemas and joins, it prevents querying the internal structure using standard SQL and introduces versioning challenges if class definitions change over time.
Serializing customer departments and their sub-departments into an XML CLOB stored in a departments column of a customers table.
- Choose between BLOB for space efficiency and binary simplicity, or CLOB/XML for human readability and parser availability.
- Carefully manage data duplication and ensure the serialized graph is only reachable from a single owner object.
Single Table Inheritance maps an entire class inheritance hierarchy into a single database table containing columns for all fields of all classes. Every class in the hierarchy stores its relevant data in the single table, leaving unrelated columns empty. A type code or class name column is used during loading to determine which subclass to instantiate. This eliminates database joins and schema alterations when pushing fields up or down the hierarchy, but can result in wasted space and large tables.
Storing cricketers, bowlers, and footballers in a single Players table distinguished by a type column.
- Include a type indicator column in the table to instruct mapper logic on which subclass to instantiate.
- Keep optional columns to the right side of the database table to assist database compression mechanisms.
Class Table Inheritance represents an inheritance hierarchy by creating one database table for each class in the model. Fields in each domain class map directly to columns in the corresponding table, and rows across tables are linked using shared primary key values. This approach normalizes the data so that all columns are relevant for every row, avoiding wasted space. However, retrieving objects requires expensive joins or multiple queries across several tables.
Using separate tables for players and footballers where the footballer table links back to the player table via a shared primary key.
- Use shared primary key values across superclass and subclass tables to correspond to the same domain object.
- Perform joins across component tables to load data, or read the root table first and use type codes to fetch additional table data.
Concrete Table Inheritance maps an inheritance hierarchy by creating a separate database table for each concrete class in the hierarchy. Each table contains columns for the concrete class and all its ancestors, duplicating superclass fields across subclass tables. This keeps each table self-contained and avoids joins when querying concrete mappers. However, it complicates primary key generation across tables, prevents database referential integrity constraints to abstract classes, and requires searching all tables when querying the superclass.
Having separate Cricketers and Footballers tables containing duplicated columns for player attributes like name and ID.
- Ensure primary keys are unique across all tables in the hierarchy rather than just within a single table.
- Check all concrete tables when executing a find operation on the superclass.
3. Web Presentation and Session State
Web presentation architectures separate user interfaces from domain models while session state patterns manage user data across stateless requests.
Web Presentation Patterns
Model View Controller separates presentation from domain logic in Web applications by routing requests through an input controller to model objects, which then hand off data to a view. An input controller extracts information from an HTTP request and delegates business logic to model objects. Once the model interacts with data sources and finishes processing, control returns to the input controller, which decides which view to display and passes the response data using an HTTP session object.
Using an input controller to process an album request, invoke domain logic, and forward data to a view that formats the HTML response.
Input controllers can be structured as individual Page Controllers per action or centralized into a single Front Controller that handles all incoming requests. Page Controller uses a single module on the web server to handle all requests for a specific logical page or action on a web site.
- Decode the URL and extract any form data to figure out all the data for the action.
- Create and invoke any model objects to process the data.
- Determine which view should display the result page and forward the model information to it.
Front Controller consolidates all request handling for a web site by channeling requests through a single handler object that dispatches to specific commands. A FrontServlet instantiates commands dynamically by appending 'Command' to a parameter value from a URL like http://localhost:8080/isa/music?name=barelyWorks&command=Artist.
Template View embeds code scriptlets directly into page structures, whereas Transform View applies a transformation stylesheet to domain data. Two Step View separates screen rendering into two stages to produce a logical screen representation first and then render it into HTML. An Application Controller manages complex screen flow and navigation logic when multiple pages and domain actions do not map in a straightforward manner.
- Determine if the machine is in control of the screen flow; if so, use an Application Controller.
- Determine if the user is in control of the screen flow; if so, you do not need an Application Controller.
Managing Session State
Session state can be stored using three primary choices: on the client, on the server, or in a database.
Stateless servers do not retain state between requests, which allows for object pooling and high scalability, whereas stateful servers require dedicating server resources to individual users. An example of a stateless server object is a web page handler that receives an ISBN number to generate a book description and then forgets the data, whereas tracking visited ISBNs per client IP address requires a stateful server object.
Client Session State stores all session data directly on the client side, requiring the client to transmit the full session state with every request and the server to return it with every response, allowing the server to remain completely stateless.
- Use URL parameters for small amounts of data like session IDs, keeping in mind URL length limits and bookmarking issues.
- Use hidden fields to serialize session state into non-displayed form elements, ensuring data is formatted appropriately.
- Use cookies to pass session data back and forth automatically, while accounting for size limitations and disabled browser cookies.
- Serialize session state into XML or text-based encoding schemes when using hidden input fields.
- Rely on platform detection to automatically switch to URL rewriting if browser cookies are disabled.
Ensure session IDs consist of secure, randomly generated numbers or run simple IDs through a hash function.
Server Session State keeps session data serialized in memory or persistent storage on a server system, providing simplicity in programming and reducing the need for complex data serialization to tabular forms.
- Hold session objects in server memory mapped by a session ID for single-server setups.
- Serialize session state to a memento or binary/text format for persistent storage to manage memory resources.
- Store passivated session state in a shared server or database session table using a Serialized LOB to support clustering and failover.
- Load-test specific application environments before deciding between stateful and stateless session beans.
- Utilize container-managed persistence and passivation provided by an EJB container for stateful session beans.
- Configure in-process state or a separate network state service via the application configuration file without altering application code.
ThoughtWorks load-tested applications with a couple of hundred of concurrent users and found no performance problems stemming from the use of stateful beans under that user load. Kai Yu successfully managed session tables by partitioning them into twelve database segments, rotating them every two hours, deleting the oldest segment, and directing all new inserts into it.
Database Session State stores session data as committed data directly in the database, allowing server objects to remain stateless and enabling easy pooling and clustering at the cost of database read and write overhead.
- Add a boolean isPending field or a session ID pending field to database rows to separate session data from record data.
- Create separate pending tables as exact clones of real tables with an added session ID field to avoid modifying all application queries.
- Run a periodic daemon process utilizing a timestamp table to clean up abandoned or timed-out sessions.
- Partition session tables into twelve database segments.
- Rotate segments every two hours, deleting all data in the oldest segment.
- Direct all new session inserts into the newly cleared segment.
4. Concurrency, Distribution, and Base Patterns
Concurrency controls, distribution strategies, and foundational utility patterns safeguard correctness and performance in enterprise systems.
Concurrency and Offline Locking
Concurrent access to shared data introduces essential problems of correctness such as lost updates and inconsistent reads, while also forcing a trade-off with liveness. A lost update occurs when one transaction overwrites another concurrent transaction's changes without seeing them. An inconsistent read opens when an agent reads pieces of information that are valid individually but incorrect when combined. Martin edits the checkConcurrency method in a file while David quickly edits the updateImportantParameter method in the same file, causing Martin's save to overwrite and lose David's update. Martin counts classes across two packages while David modifies them midway, yielding a grand total that never actually existed.
Concurrency control relies on either optimistic approaches that detect conflicts at commit time or pessimistic approaches that prevent conflicts by locking resources upfront. Optimistic locking lets agents copy and edit data freely, checking for version conflicts only when committing changes. Pessimistic locking requires an agent to check out and lock a resource first, preventing others from editing it until released. Software transactions use the ACID framework—Atomicity, Consistency, Isolation, and Durability—to guarantee reliable execution of bounded sequences of work. Atomicity ensures all steps succeed or roll back entirely. Consistency maintains system resources in a valid state at transaction start and finish. Isolation keeps uncommitted changes hidden from other open transactions. Durability ensures committed results survive system crashes. Transferring money from a savings to a checking account where a server crash midway rolls back the withdrawal so neither or both occurrences happen.
Because business transactions span multiple requests and system transactions, applications must handle offline concurrency to enforce ACID properties across user sessions. Long system transactions create performance bottlenecks and destroy scalability, forcing applications to break business transactions into a series of short system transactions. An online banking user logging in, selecting an account, setting up bill payments across multiple screens, and finally clicking OK to pay. Optimistic Offline Lock prevents concurrent transaction conflicts by assuming low conflict probability and verifying version numbers at commit time.
- Store a version number with each record in the database and domain object.
- Compare the session-stored version to the current database version during pre-commit validation.
- Include the version number in the criteria of any SQL statements used to update or delete a record.
- Inspect the row count returned by the SQL execution; if 0, rollback the system transaction and abort or retry.
A 1 row count indicates success of the SQL update or delete operation. A 0 row count indicates that the record has been changed or deleted by another session. Pessimistic Offline Lock prevents conflicts altogether by forcing a business transaction to acquire a lock before using data, eliminating last-minute failures.
- Determine the lock type required, such as exclusive write lock, exclusive read lock, or read/write lock.
- Build a lock manager that grants or denies requests by mapping locks to owners.
- Define a protocol requiring the business transaction to acquire locks before loading data and release them upon completion.
Coarse-Grained Lock applies a single lock to an entire set of related objects or an aggregate, reducing lock table contention and simplifying locking mechanics.
- Create a single point of contention, such as a shared version object or an aggregate root.
- Set up the domain model so every member of the group points to the shared version or navigates to the root.
- Implement navigation to the root in your object graph using parent links and Lazy Load.
Implicit Lock allows frameworks, Layer Supertypes, or code generation tools to handle offline locking mechanics automatically, preventing developer oversight.
- Assemble a list of mandatory tasks for your locking strategy, such as version storing and read lock acquisition.
- Factor locking code into framework classes, Layer Supertypes, or mapper decorators.
- Wrap standard data mappers with locking mappers that acquire exclusive read locks automatically during find operations.
Distribution and Base Patterns
Developers should avoid distributing objects across different processing nodes due to severe performance penalties caused by slow inter-process calls. When distribution boundaries are unavoidable, systems should use a Remote Facade to wrap fine-grained domain objects and Data Transfer Objects to bundle data across the wire. A Gateway encapsulates access to an external system or resource, shielding the rest of the application from complex specialized APIs. Mike Rettig and Martin Fowler designed a message gateway that translates explicit method calls like sendConfirmation into a generic proprietary messaging service interface and converts error codes into exceptions.
A Layer Supertype is a common superclass for all objects in a specific layer, centralizing shared behaviors and eliminating code duplication. A DomainObject superclass stores and handles ID properties for all domain objects using a Long ID type and associated getter and setter methods. A Registry is a well-known global object used by other objects to find common resources and services when direct associations are absent. Matt Foemmel and Martin Fowler implemented a thread-safe registry using Java thread local variables to maintain thread-specific database finders and session contexts.
A Money class encapsulates monetary amounts and currencies to prevent multi-currency errors and handle rounding issues explicitly.
- Avoid any kind of floating point type for monetary calculations.
- Use an allocator function taking ratio arguments to distribute monetary sums without losing pennies.
Matt Foemmel's conundrum illustrates that splitting 0.05 dollars in a 70/30 ratio yields fractional cents that standard rounding cannot resolve, which requires a specialized allocator method. The long type overflows at 92,233,720,368,547,758.09, requiring BigInteger for larger amounts. A Service Stub removes dependence on problematic external resources during testing by running locally and in memory. ExemptProductTaxService and TestTaxService implement TaxService to simulate tax rates with a flat tax rate of 0.0500 and an exempt tax rate of 0.0000 locally without needing to connect to an external tax Web service. A Plugin links classes during runtime configuration rather than compilation, removing deployment configuration from code.
- Define behaviors with a Separated Interface.
- Set up a factory that reads an external properties file or configuration source.
- Use reflection to construct implementations dynamically based on the configuration file mapping.
10 Key Ideas
- Martin Fowler provides proven design patterns to tame enterprise application complexity by organizing systems into layers, implementing domain logic, and managing object-relational mapping.
- Domain logic must remain strictly separated from presentation code to prevent code duplication and cascading maintenance issues.
- Different problems demand different organizational styles based on complexity, ranging from procedural Transaction Scripts to complex object-oriented Domain Models.
- A Data Mapper completely isolates the domain model from the database schema, allowing both to evolve independently.
- A Unit of Work acts as the controller for database mapping by keeping track of all read and modified objects and executing a coordinated commit.
- For class inheritance hierarchies that relational databases do not support natively, developers choose between Single Table Inheritance, Class Table Inheritance, and Concrete Table Inheritance.
- Model View Controller separates presentation from domain logic in web applications by routing requests through an input controller to model objects, which then hand off data to a view.
- Optimistic Offline Lock prevents concurrent transaction conflicts by assuming low conflict probability and verifying version numbers at commit time, whereas Pessimistic Offline Lock prevents conflicts altogether by forcing a business transaction to acquire a lock before using data.
- When distribution boundaries are unavoidable, systems should use a Remote Facade to wrap fine-grained domain objects and Data Transfer Objects to bundle data across the wire.
- A Money class encapsulates monetary amounts and currencies to prevent multi-currency errors and handle rounding issues explicitly by avoiding floating point types for monetary calculations.