skip to content

Data Source Patterns

Row Data Gateway, Table Data Gateway, Active Record and Data Mapper are four points on a scale from convenience to decoupling. You will learn why Active Record suits simple CRUD and why a rich domain model usually needs a Data Mapper.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

In Martin Fowler's data source architectural patterns, what is the Active Record pattern, and what two responsibilities does a single Active Record object combine into one class?

level: juniorimportance: must knowfreq 70%

answer

  1. one class, one row, self-saving
  2. combines data + behavior + persistence
  3. 1:1 class-to-table mapping
  4. fat model / Rails ActiveRecord
  5. hard to unit-test without a DB

basics

~10 s

Active Record is a design where one class both holds the data fields of a database row (like name, price) and has methods to save, load, and delete itself from that row's table.

solid answer

~30 s

Active Record is an object that wraps a single row of a database table, exposing that row's columns as fields and adding save(), delete(), and finder methods directly on the object. It combines domain data and simple business logic with persistence logic in one class, mapped 1:1 to a table. It's simple and fast to build for CRUD-heavy, low-complexity domains, but it couples your domain objects directly to the database schema.

go deeper

for a junior

Can describe that Active Record objects both hold data and know how to save/load themselves; may not yet see the testing or coupling downsides.

for a middle

Explains the 1:1 table-to-class coupling and can name at least one concrete downside (testability, fat models).

for a senior

Discusses when Active Record is the right fit (simple CRUD, low domain complexity) versus when it breaks down, and can compare it against Data Mapper/Table Data Gateway.

for a principal

Frames the choice as an architectural decision tied to team size, domain complexity growth trajectory, and migration cost — recognizes it as a deliberate simplicity/coupling trade rather than a technology limitation.

## What an Active Record object is The **Active Record** pattern, as codified in Martin Fowler's *Patterns of Enterprise Application Architecture*, describes an object that wraps one row of a database table and puts both the data and the logic to read and write that row on the same class. Concretely, a class like `Product` has: - **instance fields** mirroring the table's columns (`name`, `price`, `sku`); - **instance methods** like `save()`, `update()`, and `delete()` that run the corresponding SQL for that specific row; - **class-level or static finder methods** like `Product.find(id)` or `Product.findAll()` that query the table and return fully-populated instances. Any business rules that apply to a product — say, a discount calculation or a validation that price can't be negative — typically live as additional instance methods on the very same class. There is no separate layer standing between the object a developer manipulates in memory and the row that object represents in the database. ## Why it exists This pattern exists to minimize indirection. When a domain concept maps cleanly onto a single table, and the business logic attached to it is modest, introducing a separate mapping layer is pure overhead: extra classes, extra configuration, extra places to look when tracing a bug. Active Record collapses domain representation and persistence into one place so a developer reading the `Product` class sees everything about how a product behaves and how it's stored in one file. This is precisely why it became the default in frameworks aimed at getting CRUD-heavy applications running quickly: - **Ruby on Rails'** ActiveRecord ORM, most famously; - **PHP's Laravel** (Eloquent), later adopted in similar form; - **Django's** model layer, more loosely. ## The trade-off The trade-off is **coupling**. Because `save()` and `find()` are literally methods on the domain object, that object cannot exist, in any meaningful sense, independent of a database connection and a schema. Business logic that lives alongside those methods inherits that dependency: - you cannot easily construct a `Product` in a unit test and exercise its discount-calculation method without either running against a real (or in-memory) database or mocking out the persistence methods on the very class you're testing; - the class also violates single-responsibility in the classic sense — it has at least two reasons to change: a change to a business rule, and a change to the database schema. For a simple domain this rarely bites; for a growing one, it does. ## Failure modes 1. **The 'fat model' problem.** In production, the most common failure mode is what practitioners call the 'fat model' problem: as an application grows, more and more logic gets bolted onto the Active Record class because it's the path of least resistance — it's already there, already has the data loaded, and adding one more method is easier than designing a new collaborator. Over time a class meant to represent one row in one table accumulates validation rules, notification logic, formatting helpers, and authorization checks, becoming hundreds of lines that mix unrelated concerns and resist safe refactoring, since so much of the codebase reaches into it directly. 2. **The second table.** A second, related failure mode surfaces when the domain concept the class represents doesn't actually correspond to one table any longer — for instance once an `Order` needs line items from a second table — and developers either bolt ad hoc loading code onto the `Order` class or begin manually wiring multiple Active Record objects together, both of which erode the pattern's original simplicity. ## Where it shows up A concrete real-world illustration: Ruby on Rails applications are built around ActiveRecord by default, and the well-documented 'fat model, skinny controller' guidance that Rails teams eventually adopt is a direct response to this exact failure mode — pushing logic out of Active Record classes into separate service or concern objects once a model's responsibilities outgrow a single row's worth of behavior. The pattern itself isn't wrong; it's a deliberate trade of long-term flexibility for short-term simplicity, and understanding when that trade stops paying off — usually when business logic complexity or the object's table-shape stops being simple — is the actual skill being tested when this pattern comes up in an interview.

  • How does Active Record typically implement finder methods like findById — as instance methods or class-level/static methods?
    As class-level or static methods, since you don't yet have an instance when you're trying to look one up; the class itself acts as the entry point for queries, while instance methods like save() and delete() operate on an already-loaded row.
  • What happens to the Active Record pattern when a domain object needs to be assembled from multiple tables, e.g., Order and OrderLines?
    It strains: the Order class either grows extra ad hoc loading and saving logic to stitch the related table together, or developers wire several Active Record objects to call each other's save/delete methods in a fragile order, which blurs the pattern's original 1:1 class-to-table simplicity.
  • Why is a class built around the Active Record pattern hard to unit-test in isolation from the database?
    Because persistence calls are hard-coded as methods directly on the class holding the business logic, so exercising a business rule also exercises save/find machinery on the same object; tests end up needing a real or in-memory database, or heavy mocking of the object's own persistence methods.

Like a filing folder that also knows how to file and retrieve itself from the cabinet — the folder (data) and the filing clerk (persistence) are the same object.

saying these in an interview costs you the question

  • Says Active Record and Data Mapper are the same thing
  • Doesn't mention the 1:1 class-to-table coupling
  • Thinks Active Record is only a Rails-specific concept, not a general PoEAA pattern
  • Can't explain why it makes unit testing without a database hard
  • Believes Active Record handles complex multi-table domain logic well

context

open as a page

What is the structural difference between the Row Data Gateway and Table Data Gateway patterns, and how does that difference affect how many gateway objects exist at runtime for a table with 10,000 rows?

level: middleimportance: must knowfreq 60%

basics

~20 s

Row Data Gateway makes one gateway object per database row (so 10,000 rows means up to 10,000 objects), while Table Data Gateway makes one single object for the whole table that you call with row IDs as parameters.

open as a page

What specific problem does the Data Mapper pattern solve that Active Record does not, and what's the concrete cost of adopting Data Mapper instead of Active Record?

level: middleimportance: must knowfreq 65%

basics

~20 s

Data Mapper keeps your business objects completely unaware of the database — a separate 'mapper' class does all the saving and loading — so your business logic stays clean and testable, but you now have to write and maintain that extra mapping layer.

open as a page

In a system where a domain object is naturally assembled from multiple related tables (e.g., an `Order` with its line items and an inheritance hierarchy of payment types), why does the Active Record pattern typically strain, and what concrete signs in the codebase indicate a team should migrate toward Data Mapper?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Active Record assumes one class equals one table, so once a business object is really made of several tables or types, forcing it into a single self-saving class gets messy — you start seeing bloated classes and awkward save-ordering workarounds, which is a sign to switch to a pattern that separates the domain object from its database mapping.

open as a page

Ranking Row Data Gateway, Table Data Gateway, Active Record, and Data Mapper by how tightly they couple domain/business logic to the database, where does each sit, and how does that coupling level affect your ability to unit-test business rules without a database?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Active Record couples business logic and database access the most, because they're the same class. Data Mapper couples them the least, since the business object never touches SQL. Row Data Gateway and Table Data Gateway sit in between — no business logic lives in them at all, but whatever logic calls them still needs a real or fake gateway to run its tests.

open as a page

As a technical lead choosing among Row Data Gateway, Table Data Gateway, Active Record, and Data Mapper for a new service, what factors should drive the decision, and under what conditions would you deliberately mix more than one of these patterns within the same codebase?

level: principalimportance: should knowfreq 35%

basics

~20 s

Pick the simplest pattern that fits how complex your business rules and data relationships are today, and how much they're expected to grow — simple CRUD favors Active Record or a gateway, complex evolving domains favor Data Mapper. It's fine to use different patterns in different parts of one system if each part's complexity genuinely differs.

open as a page