Enterprise Application Patterns
Learning Objectives
Compare Repository, Unit of Work, Data Mapper, Active Record, Service Layer, DTO, Identity Map, Lazy Load and Specification.
Get an overview of CQRS.
Treat these as options with trade-offs.
Prerequisites: Modules 51, 62, 71, 74.
Intuition and Definition
Active Record The object knows how to save itself (order.save())
Simple CRUD; couples domain to the database Data Mapper A separate mapper moves data between objects and tables
Domain independent of persistence; more code (ORMs automate it)
Collection-like interface for aggregates Clean domain API; can hide important query costs Unit of Work
Tracks new/changed/removed objects and commits them together
One transaction, ordered writes; memory for tracking Identity Map One object per row per session
Consistency, caching; stale data in long sessions Load related data on first access Less upfront loading; N+1 risk Service Layer Application operations over the domain
Clear boundary; may become anemic transaction scripts Data shaped for transfer Decoupling; mapping code Query/business criteria as objects
Reusable rules; can leak into persistence Separate models for commands (writes) and queries (reads)
Optimized reads/writes; more moving parts, often eventual consistency
Why It Matters
Every business application must move data between objects and storage. Knowing the vocabulary and trade-offs lets you choose deliberately — and recognise what your ORM already does.
Syntax and Language Notes
Entity Framework's DbContext is a Unit of Work + Identity Map; DbSet<T> is repository-like; Rails' ActiveRecord and Django models are Active Record; Hibernate/SQLAlchemy are Data Mappers.
Complete Example
A Unit of Work with an Identity Map over a tiny in-memory "database", showing change tracking and a single commit:
class Database: # pretend storage with a statement log
def __init__(self): self.rows, self.log = {}, []
def select(self, id): self.log.append(f"SELECT {id}"); return dict(self.rows[id]) if id in self.rows else None
class Vehicle:
def __init__(self, vin, km): self.vin, self.km = vin, km
class UnitOfWork:
def __init__(self, db):
self.db, self.identity_map, self.snapshots, self.new = db, {}, {}, []
def get(self, vin): # identity map: one object per row per unit of work
if vin not in self.identity_map:
row = self.db.select(vin)
if row is None: return None
v = Vehicle(vin, row["km"])
self.identity_map[vin], self.snapshots[vin] = v, dict(row)
return self.identity_map[vin]
def add(self, v): self.new.append(v)
def commit(self):
stmts = [f"INSERT {v.vin} km={v.km}" for v in self.new]
for vin, v in self.identity_map.items(): # change tracking: compare with snapshot
if self.snapshots[vin]["km"] != v.km:
stmts.append(f"UPDATE {vin} km={v.km}")
self.db.log.append("BEGIN"); self.db.
…same object: True
SELECT V1
SELECT V2
BEGIN
INSERT V3 km=0
UPDATE V1 km=1200
COMMITStep-by-Step Walkthrough
The second get("V1") returns the same object from the identity map — no second SELECT.
Changing km on a loaded object is detected at commit by comparing with the snapshot.
V2 was loaded but not changed, so no UPDATE is issued.
All writes are grouped into one transaction (BEGIN…COMMIT).
Common Mistakes
Generic repositories (IRepository<T> with 30 methods) mirroring the ORM — little value, leaky abstraction.
Active Record in complex domains — persistence concerns spread through business rules.
Long-lived units of work — stale identity maps and memory growth.
CQRS everywhere — significant complexity for systems that don't need separate read models.
Trade-offs and Alternatives
Pick per context: Active Record or transaction scripts for simple CRUD; Data Mapper + Repository + Unit of Work (usually via an ORM) for rich domains; CQRS + read models for high read/write asymmetry or complex reporting.
Real-World Use
Entity Framework, Hibernate/JPA, SQLAlchemy sessions, Rails ActiveRecord, Spring Data repositories, CQRS read models in event-sourced systems.
Practice
Q1. (Easy) Which pattern makes an object save itself?
Answer: Active Record.
Q2. (Medium) What two patterns does an EF DbContext implement?
Answer: Unit of Work and Identity Map.
Q3. (Advanced) When is CQRS worth it?
Answer: When read and write workloads differ a lot (complex queries/reporting vs transactional writes), when reads need denormalized views at scale, or when combined with event sourcing; not for straightforward CRUD.
Interview Question
Q1. "Repository vs Data Mapper?" Answer: A Data Mapper moves data between in-memory objects and database rows, keeping them independent — it's the persistence mechanism (often an ORM). A Repository sits above it and gives the domain a collection-like interface for aggregates (orders.byId(id), orders.add(order)), expressed in domain language and hiding query details. Repositories use mappers; mappers don't need repositories.
Quick Quiz
Q1. What does Unit of Work batch?
Answer: All inserts/updates/deletes of a business transaction into one commit.
Q2. What problem does Lazy Load risk?
Answer: N+1 queries.
Q3. What does CQRS separate?
Answer: Command (write) models from query (read) models.
Summary
Enterprise patterns are persistence and application-structure options with trade-offs.
Your ORM likely implements several; know which, and use them deliberately.
Related: Modules 51, 62, 74, 76.