skip to content

Application Architecture Patterns (PoEAA)

Fowler's Patterns of Enterprise Application Architecture: the standard vocabulary for organizing domain logic, mapping objects to relational data, defining a service boundary, and moving data across it. Most frameworks you use are implementations of these, so knowing the names explains a lot of behavior.

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

questions

page 2 of 2

A team's REST controller returns a JPA entity directly, and in production some responses hang or return enormous, deeply nested JSON payloads for orders with many related records. What serialization concerns cause this, and how does introducing a DTO/assembler layer fix them?

level: middleimportance: should knowfreq 65%

basics

~20 s

When you serialize a database object straight to JSON, its linked objects (like an order's items, and each item's link back to the order) can form a loop the serializer walks forever, or pull in huge amounts of related data no one asked for. A DTO fixes this by only including the exact fields you choose, with no loops and no surprise data.

open as a page

A REST controller returns a lazily-loaded domain entity directly to a JSON serializer, and the response either throws mid-serialization or silently balloons to include far more data than expected. What's causing this, and what's the safer pattern?

level: middleimportance: should knowfreq 45%

basics

~20 s

The serializer walks every field it can see, including lazy ones, so touching a lazy field during serialization either triggers a slow chain of hidden loads (sometimes recursively, pulling in huge amounts of data) or throws if the loading context is already gone. The fix is to convert to a plain response object with only the fields you actually want, before serializing.

open as a page

A team writes unit tests against an in-memory implementation of a Repository interface, then runs integration tests against the real database-backed implementation. What can go wrong when the two implementations' behavior diverges, and how would you catch it?

level: middleimportance: should knowfreq 55%

basics

~20 s

The fake in-memory version might behave slightly differently than the real database, like ignoring case or not enforcing uniqueness, so tests pass on the fake but fail for real. Catch it with one shared test suite run against both.

open as a page

How does the Unit of Work pattern relate to the Repository pattern, and what's the difference in what each one is responsible for?

level: middleimportance: should knowfreq 45%

basics

~20 s

A Repository is where you go to find and fetch objects, like a filing cabinet drawer for one type of thing. The Unit of Work is the assistant that remembers everything you changed across all the drawers and files it all away together at the end.

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

What are the real costs of adopting a DTO/Assembler layer across every API endpoint, and under what circumstances would a senior engineer decide NOT to introduce one?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Every DTO means extra classes and mapping code to write and keep in sync, which slows down small changes and adds files to maintain. For a tiny internal tool with one trusted client and a short lifespan, that overhead may not be worth it.

open as a page

In the pipes-and-filters integration pattern, what makes a processing step a good 'filter,' and what do you give up by chaining many small filters instead of one larger processing step?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A good filter does one transformation, takes input from a pipe and puts output on the next pipe, and doesn't know or care what's upstream or downstream. Chaining many small ones is flexible and reusable, but adds latency and more places for failures to hide.

open as a page

Two entities, Student and Course, have a many-to-many relationship with no extra data on the relationship itself. Explain how Association Table Mapping represents this in the relational schema, and how the mapping has to change if the relationship later needs an attribute like an enrollment date.

level: seniorimportance: should knowfreq 55%

basics

~20 s

Association Table Mapping adds a third table with two foreign key columns, one pointing to each side, so many Students can link to many Courses without duplicating data on either table. Once the relationship needs its own attribute, like an enrollment date, that link table gets a real primary key and becomes a full entity itself.

open as a page

You're reviewing a codebase where every entity has its own Repository interface exposing generic methods like findAll(), save(), delete(), plus a handful of custom finders, on top of an ORM. What are the costs of this layer versus using the ORM's session/EntityManager directly, and when would you skip introducing a Repository at all?

level: seniorimportance: should knowfreq 50%

basics

~10 s

If a Repository just re-exposes the ORM's own save/find/delete methods, it's extra boilerplate with no real decoupling benefit. Skip it for small, simple apps where the ORM's API is already good enough.

open as a page

When a Service Layer's methods are also exposed to remote clients through a Remote Facade, how should method design differ from a Service Layer only ever called in-process, and why does that matter?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Across a network, every call is slow and can fail on its own, so remote-facing methods should be few, chunky, and do a lot per call — pass whole batches of data instead of tiny get/set calls. In-process calls are cheap, so they can be small and frequent.

open as a page

A codebase built its entire Service Layer in the operation-script style - each service method contains its own procedural business logic rather than delegating to domain objects. Over two years, the same 'is this customer eligible for expedited shipping' check has been copy-pasted into four different service methods and now disagrees in one of them. What does this indicate about how the Service Layer has scaled, and what are the practical remedies?

level: seniorimportance: should knowfreq 50%

basics

~20 s

It means the same business rule now lives in several places instead of one, so they can quietly drift apart and disagree. The usual fixes are pulling the shared rule out into a single helper or domain method that every service method calls, then deleting the old copies.

open as a page

What production failure modes commonly show up when a Unit of Work (such as an ORM session or persistence context) is kept alive across a broader scope than a single logical operation - for example, across an entire HTTP session instead of a single request?

level: seniorimportance: should knowfreq 50%

basics

~20 s

If you keep the 'save later' tracker open too long, it piles up memory for every object it ever touched, starts returning old data instead of fresh data, and can accidentally save changes the user made way earlier than expected.

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

As a system's business logic grows more complex over time, how would you decide whether — and how — to migrate domain logic from a Transaction Script style toward a Domain Model, and what does that migration actually cost?

level: principalimportance: should knowfreq 40%

basics

~20 s

Watch for pain signals — like duplicated rules and tangled functions — before deciding to switch styles, then move gradually, pulling shared rules into small rich objects piece by piece, instead of doing a risky big rewrite of everything at once.

open as a page

When a business transaction spans multiple services that each have their own local database, why can't a normal ACID transaction hold it together, and how does a saga's use of compensating actions address that?

level: principalimportance: should knowfreq 55%

basics

~20 s

Each service's database can only guarantee atomicity for its own local changes, not across services over a network. A saga breaks the transaction into a sequence of local steps, and if a later step fails, it runs 'undo' actions (compensations) for the steps that already succeeded, instead of a true rollback.

open as a page

At what point does defaulting to Lazy Load across a whole persistence layer become the wrong architectural choice, and what goes wrong with naive lazy initialization when multiple threads share the same not-yet-loaded object?

level: principalimportance: should knowfreq 30%

basics

~20 s

If lots of code paths always need the 'lazy' data anyway, or if the app is highly concurrent, lazy loading adds complexity and race-condition risk without saving much. Two threads racing to load the same not-yet-loaded field can both start loading at once, corrupt the field, or hand back different values.

open as a page

As the tech lead redesigning persistence for a system with a deep object graph - Order pointing to LineItem, LineItem to Product, Product to Supplier, Supplier to Address - some screens need only order totals while others need the full graph. How do you decide, association by association, whether to default to lazy or eager loading, and what alternative to loading full domain objects would you consider for read-heavy endpoints?

level: principalimportance: should knowfreq 45%

basics

~20 s

There's no single right default: pick lazy or eager per association based on how often each screen actually needs it, and for read-heavy endpoints, skip loading full objects altogether and query a flat projection with just the columns that screen displays.

open as a page

As a system's API and domain model evolve over years, what failure modes tend to emerge specifically in a mature DTO/Assembler layer, and how would a principal engineer detect and correct them?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Over years, mapper classes tend to grow messy: they pick up business logic they shouldn't have, DTOs quietly start looking just like the database tables again, and old fields never get cleaned up. Fixing this means regularly auditing mappers for logic that snuck in and removing DTO fields no client actually uses.

open as a page

A generic Repository<T, ID> interface with methods like findById, findAll(Specification spec), and save(T entity) is shared as a base type across a dozen unrelated entity types in a large system. What failure modes tend to show up as the system grows, and how do teams typically evolve away from it?

level: principalimportance: nice to knowfreq 30%

basics

~10 s

One generic Repository shared by every entity forces them all into the same shape of querying and saving, which breaks once some entities need different rules. Teams usually replace it with entity-specific, purpose-built interfaces.

open as a page

For a small internal tool with a single web UI and simple CRUD use cases, a senior engineer proposes skipping a Service Layer and having controllers call the domain/persistence layer directly. When is that a defensible architectural choice, and what do you give up by doing it?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

It's defensible when there's only one client and the logic is simple - you avoid writing and maintaining an extra layer for no real benefit. What you give up is a single reuse point if a second client (like an API or batch job) shows up later, so you may need to add it in then.

open as a page

In what situations would you deliberately avoid relying on a Unit of Work / ORM-managed persistence context, and use explicit, hand-written persistence logic instead?

level: principalimportance: nice to knowfreq 35%

basics

~20 s

When you're changing huge numbers of rows at once (bulk jobs), or writing across multiple independent services/databases where one shared transaction isn't possible, the automatic 'track everything then save' approach costs more than it helps - explicit, targeted SQL or separately coordinated writes work better.

open as a page

showing 31–52 of 52