Everything the adaptive question bank can teach and test in Coding Patterns, from foundations through advanced practice. Work through it in order, or start practicing and let the questions find your level.
The context is the part that gets dropped. A pattern's write-up says when it applies and which pressures it resolves, and the same solution can hurt in a different setting. A snippet carries none of that, so reusing the code without checking the context is how a pattern lands where it costs more than it saves.
Because there is nothing to import, every use of a pattern is rewritten for its new home, and each reader has to recognize it to benefit, which is why naming it in code and docs matters. A library trades the other way: you skip writing the code, but you inherit its versions, bugs and upgrade schedule.
Forces are pressures that pull against each other: flexibility against simplicity, speed against memory, decoupling against traceability. A pattern settles them in one direction. If your situation lacks the force a pattern answers, you pay its cost and collect nothing, so name the force before you name the pattern.
State and Strategy both delegate to a swappable object, Adapter and Decorator are both called wrappers, and Bridge is often built the way an object adapter is. A diagram cannot tell them apart. Ask what problem the design was meant to solve and who changes what, and the name follows from the answer.
An interface with one implementation and no second one in sight adds a file to open and a hop to follow on every read. The flexibility stays theoretical until a real variant arrives, and by then you may need a different seam. Wait for the second case; extracting a pattern from working code is cheaper than guessing at it.
The categories sort by the problem a pattern addresses, not by its mechanism. Factory Method relies on inheritance and Abstract Factory on composition, yet both are creational because both answer who decides what gets built. When a pattern seems to fit two categories, ask which question it was invented to answer.
Adapter comes in both forms. A class adapter inherits from the adaptee, so the pairing is fixed when the code compiles; an object adapter holds an adaptee and forwards to it, so a different one can be passed in at run time. Factory Method is class-scoped: you vary the product by writing a subclass, and an existing instance cannot switch.
Peter Norvig showed that 16 of the 23 GoF patterns are simplified or eliminated in Lisp or Dylan; Strategy, for instance, shrinks to passing a function. Idioms are bound to one language's mechanics, as RAII relies on C++ destructors running at scope exit. Ask whether a construct would survive a change of language.
B. Creational patterns
The creation call sits inside an algorithm the base class owns, so the shared logic works with products it never names. The price is a subclass per product variant, even when nothing else about the creator changes. If creation is the only step a subclass would override, passing in a factory object or function is usually lighter.
Consistency is the payoff: a client wired to one concrete factory gets matching widgets, themes or drivers without checking. The cost lands when you add a new kind of product, because the factory interface gains a method and every concrete factory must implement it. Adding a whole new family, by contrast, edits nothing that exists: you write a new concrete factory plus that family's product classes.
Count the products and look for the subclass. One product type made by an overridable method in a class that does other work is Factory Method. A separate object whose whole job is creating several related types, handed to the client, is Abstract Factory. Concrete factories are often built from factory methods, so finding one inside the other is normal.
Construction happens on a separate, mutable builder, so the finished object can be immutable and is handed out only once it is complete. The trade-off: in languages with null safety, a forgotten required step often becomes a run-time error at build() instead of a compile error, unless the builder validates or its types enforce the order.
With overloads like (name), (name, size) and (name, size, color), two adjacent arguments of the same type can be swapped and still compile. A builder names each value at the call site and lets callers skip optional ones. Where your language has named and default parameters, those solve the same problem without a second class.
Cloning copies state the exemplar already holds, so the caller needs no knowledge of its concrete class and skips any expensive setup. That suits types loaded or configured at run time, where a factory would need a subclass per variant. The weak point is the clone method itself: every class in the hierarchy must implement it correctly.
A field-by-field copy duplicates references, not the objects behind them, so adding to the clone's list also changes the original's. Copy every mutable object the clone owns, or keep those parts immutable so sharing is harmless. Deep-copying everything is not automatically right either: shared services and caches should stay shared.
Enforcing one instance is cheap and sometimes reasonable. The trouble comes from the static accessor: any code anywhere can reach the object, so its state becomes global state and its users stop declaring it. Lazy creation adds a hazard in threaded code, where an unguarded check can build two instances. Creating one instance and passing it in avoids both.
Nothing in the method's signature reveals the call, so a test cannot substitute a fake without resetting shared static state, and tests that run one after another can leak state into each other. Making the dependency a constructor or method parameter keeps the single instance but makes the coupling visible and replaceable.
In most mainstream languages, new always yields a fresh object of exactly the class you named. A named method like fromString or of can hand back a cached instance, choose a subclass based on its arguments, or declare an interface return type whose implementation can change later. The cost is discoverability: such methods do not stand out from other static methods.
C. Structural patterns
The adapter is a translation layer, so it is where mismatches in units, error styles and naming get reconciled. Keep it thin: once it starts making business decisions, it becomes a second implementation hiding behind a conversion. If you own both sides, changing one of them is usually simpler than adding an adapter between them.
A facade earns its place by covering the common path in a few calls while leaving the full subsystem available for rare cases. It goes wrong in two directions: forcing every caller through it turns it into a bottleneck, and adding a pass-through for every subsystem option leaves it as complex as what it hides.
Ask who wrote the target interface. If client code already expects a specific shape and the wrapper exists to satisfy it, that is Adapter, usually around one class, and it keeps polymorphism working. If you designed a new, smaller interface to make a whole subsystem easier to use, that is Facade, usually around many classes.
Each wrapper forwards to the object inside and adds work before or after, so order matters: compressing then encrypting is not the same as the reverse. Callers see only the interface and cannot easily tell which layers are present, so debugging means stepping through every wrapper. It fits best when the combination varies per object.
The client keeps calling what looks like the real object, so access control or lazy loading can be added without touching callers. That transparency is also the risk: a remote proxy makes a network call look like a local method, and a virtual proxy can turn an innocent getter into a slow load. Make the expensive cases visible in naming or docs.
Look at where the wrapped object comes from. You build a decorator stack yourself, passing in an object you already have, and you may stack several. A proxy usually creates, locates or guards its subject, may defer creating it until first use, and may refuse a call outright. Same interface, different authority over the real object.
The payoff is recursion without type checks: you total a folder's size by asking each child for its size, whether it is a file or another folder. The risk is a tree that is not really a tree. A child added to two parents, or to its own descendant, makes those recursive operations double-count or never finish.
Put add and remove on the shared interface and a leaf can only refuse them at run time, which the compiler cannot catch. Put them only on the container and the compiler catches misuse, but clients must tell leaves from containers again. The safe form suits a tree that is fixed once it has been built.
Without the split, two dimensions share one hierarchy: three shapes times two renderers means six subclasses, and each new renderer adds three more. Bridge keeps a shape hierarchy and a renderer hierarchy linked by a reference, so the count adds instead of multiplying. With only one implementation, it reduces to a private implementation object.
Timing gives it away. A bridge is planned before either side exists so the abstraction and implementation can grow separately; an adapter appears after the fact because two existing interfaces disagree. The code can look alike, since a bridge is often built like an object adapter, so ask whether the mismatch was designed for or discovered.
Shared state must be immutable, or one change shows up in every object using it. Callers now supply position, size or color on each call, so signatures grow and code that used to read a field must track that data itself. It pays only when the objects are numerous and most of their state genuinely repeats, so measure memory before and after.
D. Behavioral patterns
The context holds a strategy reference and calls it, so adding an algorithm means adding a class or function, not editing a branch. In languages with first-class functions a strategy is often just a lambda, and a full interface is overhead. If the client never actually swaps the algorithm, the conditional you replaced was probably clearer.
Each state class handles the events that matter in that state and sets its successor, so transition rules sit next to the behavior they affect. The cost is that the whole machine is spread across many classes, and no single place shows every transition. Once the states multiply, draw or document the machine separately from the code.
Watch who calls the setter. If outside code chooses and installs the object, and it rarely changes during the object's life, it is Strategy. If the installed object replaces itself on the context as events arrive, it is State. State objects usually know about each other; strategies are written independently and never name a sibling.
The base class calls the subclass, not the other way around, which is a small case of inversion of control. Optional steps are hooks with empty default bodies. The trap is overriding the template method itself, which lets a subclass reorder steps the skeleton exists to protect, so mark it final where your language allows.
The subject knows only that each observer has an update method, so new listeners need no change to the subject. The costs show up at run time: notifications are usually synchronous, so a slow observer stalls the subject, and code that depends on the order observers run in breaks the first time registration order changes.
The leak runs from subject to observer: as long as the subject is reachable, its list keeps every registered observer reachable too, even a screen the user closed long ago, and that observer keeps reacting to events. Pair each subscribe with an unsubscribe in the same lifecycle, or register through weak references where your runtime offers them.
Each peer knows only the hub, so adding a peer touches the mediator instead of every other peer. The coordination rules all land in one class, which helps when reading a workflow and hurts as it grows: a mediator that every feature edits becomes the class nobody can change safely. Split it by workflow before that happens.
Turning a call into an object separates deciding what to do from doing it: an invoker can queue it for a worker, write it to a log, retry it or keep it in a history, without knowing what it does. The cost is a class or closure per operation, which is pure overhead when you only ever execute immediately.
Running a command again cannot reverse it. A 'set price to 20' command must record the old price when it executes, and a 'delete row' command must keep the row. Undo pops the last command and applies the stored reversal; redo is usually a second stack. Commands that touch the outside world, like sending an email, often cannot be undone at all.
Sender and handlers stay decoupled, and you can reorder or insert handlers without touching the sender. The risk is silent loss: a request nobody claims just falls off the end, so put a terminal handler last that logs, rejects or applies a default. Middleware and filter chains often let several handlers act, unlike the strict one-handler form.
Because callers step through items with next instead of indexing, the collection can be an array, a tree or a lazy stream without changing client code, and several traversals can run at once, each with its own position. The catch is that an iterator holds a position into data it does not own, which matters when that data changes underneath it.
Adding or removing elements while looping can skip items, revisit them or read past the end. Java's fail-fast iterators notice a change they did not make and throw ConcurrentModificationException, but only on a best-effort basis, so treat it as a bug detector, never as control flow. Remove through the iterator itself, or collect changes and apply them after the loop.
Each element's accept calls back the visitor method for its own type, which is double dispatch: the code that runs depends on both the element and the visitor. That lets you add a pretty-printer or a type checker as one new class. It also forces elements to expose enough of their state for outside visitors to work on them.
With ordinary methods, a new element type is one new class, but a new operation means editing every class. Visitor flips that: a new operation is one visitor, but a new element type adds a method to every visitor. Choose by which axis changes more often. Languages with sum types and exhaustive pattern matching get much the same effect with less ceremony.
The originator writes and reads the snapshot, while the caretaker that stores it treats it as an opaque token. That keeps an undo history from becoming a back door into private fields. Snapshots of large objects cost memory, so a long history may need a cap, or should store changes rather than full copies.
Each rule becomes a node class, and evaluating a sentence is a recursive walk over the tree, much like Composite. The pattern says nothing about parsing; something else must build the tree. The class count grows with every rule, which is why larger languages usually move to a parser generator or a dedicated evaluator.
E. Choosing between the near-twins
With Strategy, the client installs the behavior and it stays put. With State, the current state object installs its successor as events arrive. With Command, a third party such as an invoker or a queue holds the request and decides when it runs, while the action itself was fixed when the command was created. Ask who calls the setter.
A subject in Observer fires and forgets: it does not know who reacts, and removing every listener breaks nothing in the subject. A mediator holds references to its participants and calls them in a planned order to finish a task. The two combine often, since a mediator may listen for its colleagues' events and then decide what each should do.
Both are an object with one method, so look at time and inputs. A command carries its receiver and arguments and can wait in a queue or history until someone runs it. A strategy gets fresh inputs on every call and exists to be swapped for another algorithm with the same signature. Undo makes sense for commands and rarely for strategies.
Count what is inside. A composite holds a list and combines its children's results, such as summing their sizes. A decorator holds one inner object and adds behavior around its calls. The purpose differs too: a composite groups many items so they act as one, while a decorator enriches a single item. Both can appear in the same tree.
A factory answers one call with a finished object and hides which class you got. A builder takes several calls that configure parts, then produces the object at the end, and the class is often obvious. Reach for a builder when assembly has many optional steps, and for a factory when the choice of implementation is the thing to hide.
A switch keeps the whole machine on one screen, which is easiest to review while states and transitions are few. The trouble starts when several methods each switch on the same enum: adding a state means editing every switch, and missing one is a bug. That repetition, more than the number of states, is the usual signal to move to state objects.
Code drifts into familiar shapes by accident: a class holding one interface reference looks like Strategy, Decorator, Proxy and Bridge at once. Naming it after the closest diagram tells readers to expect flexibility that may not be needed. Start from the problem instead: what varies, who decides, and what must stay stable.
F. SOLID and its real trade-offs
An actor is a group of people who ask for changes, such as accounting or operations. A report class that both computes figures and formats output answers to two groups, so a format request can break the calculation. 'Does one thing' is too vague to apply consistently; asking who would request the change gives a test you can check.
A class per verb, such as OrderValidator, OrderSaver and OrderNotifier, can scatter one rule that always changes as a unit across several files plus the wiring between them. If those pieces answer to the same actor and change together, they belong together. Cohesion is the counterweight: split only along lines where changes really diverge.
The original formulation leaned on inheritance: a finished class stays as it is, and new features go into subclasses. The later, more common reading uses an interface with interchangeable implementations, so new behavior is a new implementation and callers never change. Either way, the existing code must already expose the seam you extend through.
Every extension point is a bet on which axis of change will come. Guess right and new behavior slots in; guess wrong and you carry an abstraction nobody uses while the real change still requires editing. Waiting until a second variant appears, then adding the seam, replaces a guess with evidence.
The test is about behavior, not about whether the types compile. If code written against the base class needs an instanceof check or special handling for one subclass, substitution has already failed. Overrides that throw 'not supported', ignore their arguments or return a different kind of result are the usual ways it breaks.
A caller that sets width to 5 and then height to 4 expects an area of 20; a Square that keeps its sides equal returns 16. The geometry fact is about values, while the contract is about mutation. An immutable Square can be a sound subtype, or Square and Rectangle can be siblings under a read-only Shape.
Reason from the caller's side. The caller has met the base's preconditions, so a subtype that demands more rejects valid calls. The caller relies on the base's guarantees, so a subtype that promises less breaks it. Exceptions follow the same logic: an override may not throw new kinds the base never declared, other than subtypes of the ones it did.
When one interface serves many clients, each client is coupled to methods it never calls. Changing a method used by only one of them can force the rest to rebuild, redeploy or update test doubles that stub methods they ignore. Splitting it into narrower interfaces, each matching one client's needs, keeps those changes from spreading.
A separate interface for every method gives clients a dozen types to assemble and hides which operations belong together. Group by role instead: a reader role, a writer role, an admin role, each holding the methods one kind of client uses together. A class can still implement several roles, and each client depends only on the one it plays.
Without inversion, the policy imports the database code, so a storage change ripples into business rules. With it, the policy declares the interface it needs in its own terms and the storage module implements it, so the source-code arrow points from detail to policy. An interface still shaped by the storage API inverts nothing.
Check the imports. If the policy module must import the implementation's package to reach the interface, the dependency still runs from policy to detail, and the policy cannot be reused without dragging that package along. Moving the interface beside its caller, or into a small package of its own, is what reverses the arrow.
Each principle adds a seam: an interface, a new class, a level of delegation. Seams make change cheap where change happens and make reading harder everywhere. Giving every class an interface also leaves readers wondering what other implementations exist, often only test mocks. Apply the principles where you have seen variation, not uniformly.
G. Coupling, cohesion and connascence
Afferent coupling counts incoming dependents, so a widely used module is hard to change without breaking someone. Efferent coupling counts outgoing dependencies, so a module using many others breaks when any of them changes. Instability is outgoing divided by total: near 0, the module is depended on but depends on little; near 1, the reverse.
Content coupling means one module depends on another's internal code or data, so even a refactor that keeps behavior can break it. Common coupling routes communication through shared global data, so a fault in one writer surfaces in an unrelated reader. In the classic ranking they come first, ahead of external, control, stamp and data coupling.
A boolean like render(page, true) tells the callee which path to take, so the caller must know the callee's internals and every new mode adds another flag. Splitting it into two well-named functions, or passing an object that carries the varying behavior, turns control coupling back into plain data coupling.
Calling send() before setRecipient() compiles but fails, or quietly does nothing. Make the wrong order impossible: require the data in the constructor, return a new object from each step, or offer one method that runs the sequence. Some coupling taxonomies use the same term for actions grouped only because they run at the same time.
When a function takes a whole Customer but reads only the name and email, any change to Customer can ripple into it, and its tests must build a full record. Passing the two fields, or a small type holding just them, narrows the dependency. The opposite mistake is a long list of loose fields that always travel together.
If two modules always change in the same commit, an interface between them doubles the edits and sends readers through one more file to find the real code. Release history shows this: modules that change together are coupled whatever the diagram says. Put seams where changes diverge, and let tightly linked code sit together.
From worst to best, the classic scale runs coincidental, logical, temporal, procedural, communicational, sequential, functional. A Utils class holding a date formatter and a retry helper is coincidental: its parts change for unrelated reasons. High cohesion usually travels with low coupling, because related code that lives together needs fewer connections outward.
A call like copy(src, dst) relies on both sides agreeing on order, and swapping two arguments of the same type still compiles. Named arguments, or a small object with named fields, make the call depend on names instead. Names can still change, but a rename is easy to find and fix, while a reordering hides.
Inside one function you can see both ends of a dependency, so a strong form such as shared ordering is cheap to keep consistent. Across modules, codebases or services, nobody sees both ends at once and the two sides may ship separately. Keep strong connascence close together, and at a distance rely on the weakest form, such as agreeing only on names.
H. Composition, inheritance and delegation
A subclass sees protected fields, depends on which base methods call which, and inherits every method the parent adds later, wanted or not. Those are implementation details rather than a published interface, so a refactor that keeps the base class's public behavior can still break subclasses. That is why inheritance is said to break encapsulation.
Suppose a subclass overrides inc2 to call inc1, and a later base change makes inc1 call inc2: the two now recurse until the stack overflows. The base change looked harmless in isolation. Defenses: mark methods final unless they are designed to be overridden, avoid calling overridable methods from other base methods, and document any self-calls you keep.
A subclass made only to borrow code also inherits every public method of the parent, including ones that let callers break the subclass's rules. A stack built by extending a list still exposes insert-at-index. Holding the list privately and exposing only push and pop keeps the reuse and drops the false promise.
The wrapper chooses which operations to expose and can change its collaborator at run time, neither of which a subclass can do. The cost is forwarding: each exposed operation needs a one-line pass-through method, which some languages generate for you through delegation syntax, embedding or traits.
Three optional features combined by subclassing need a class for every subset, eight in all, and a fourth feature doubles that to sixteen. Wrapping keeps one class per feature and lets each object pick its own stack at run time. Inheritance still wins when the combination is fixed and small, since it avoids the extra objects and forwarding.
Both add methods to many classes without a shared parent. They differ on conflicts: with mixins, the order of application can decide which definition wins, so reordering changes behavior; the classic trait model makes you resolve a name clash explicitly, and composing traits gives the same result in any order.
Following one call can mean bouncing up and down the chain to see which level overrides what, a pattern known as the yo-yo problem. Deep chains usually come from subclassing for reuse. Flattening the hierarchy, or moving shared behavior into collaborators, puts related logic where you can read it in one place.
Inheritance lets a subclass override only what differs from a base full of defaults, which can be far less code than forwarding every method. It fits when every subtype honors the base contract and you control the hierarchy, as with a sealed set of node types. The guideline targets inheritance used only for reuse, not inheritance itself.
I. Dependency injection and inversion of control
A library is code you call; a framework calls you, through callbacks, event handlers or template methods. Dependency injection inverts something narrower: who creates and supplies your collaborators. You can use a framework with no injection, and inject by hand with no framework, so treating the terms as synonyms hides which control actually moved.
Required collaborators become required parameters, so the object cannot be built without them, and its fields can stay immutable. Two warnings come with it: an unwieldy constructor flags a class doing too much, and two classes that each need the other cannot both be built this way, which exposes a cycle worth breaking.
Setters suit optional collaborators with sensible defaults; for required ones, every use must check for null or risk failing later. Two objects that need each other can be wired by creating both and then calling setters, but one of them is used before it is fully set up. Start with constructor injection and switch only when its limits bite.
With a locator, a class calls something like registry.get(Mailer) inside its methods, so you learn its needs only by reading its body, and a missing registration fails at run time. Tests must configure the shared registry. Injection lists the same needs as parameters, and the class depends on no registry at all.
Creating objects in main and passing them to constructors is the whole technique; a container only automates that construction. Hand wiring keeps every connection visible and checked by the compiler. A container starts to pay off when the graph grows large or needs scoped lifetimes, at the cost of resolving through reflection or configuration.
If business classes ask the container for objects, you have rebuilt a service locator, and every class now depends on the container. Keeping all construction in one place, near main or the framework's startup hook, means swapping an implementation is a one-line edit there, and nothing else knows how objects get built.
A typo in a binding or a missing implementation compiles fine and breaks only when the container resolves it. Containers that create long-lived objects eagerly report most of these at startup; anything created lazily waits until first use, possibly in production. A startup test that resolves the whole graph catches them before release.
Each collaborator is a separate reason the class might change, and the constructor makes that list visible instead of hiding it in method bodies. Moving the dependencies into setters or a locator hides the smell without fixing it. Look for clusters of collaborators used by the same methods, and split the class along those lines.
If a domain class needs a service from an outer layer, putting the interface beside the domain means the domain package compiles on its own and the outer package depends on it, not the reverse. Putting the interface in the implementation's package instead keeps the arrow pointing outward, and the domain cannot be built without that package.
The usual alternative is factory code that checks an environment variable and picks an implementation, repeated across many factories, so a new environment means editing and redeploying all of them. A plugin reads the choice from configuration in one place, such as an in-memory ID generator for tests and a database sequence in production.
J. Domain logic patterns
Each request gets one procedure that reads, decides and writes, which is easy to follow and needs no object mapping. The cost appears as rules get shared: a discount rule used by three transactions is copied into three scripts or pulled into helper procedures. When those helpers start forming their own web, the logic is asking for objects.
Behavior lives with the data it works on, so a rule like 'an order over its credit limit cannot ship' has one home and every path reuses it. The price is a mapping layer between the objects and the database, since a rich model rarely matches the table layout. With little logic to organize, that mapping cost buys nothing.
Early on, scripts are quicker because there is no mapping layer to build. As rules start to interact, the duplicated logic in scripts costs more than a model would have. Converting later means rewriting procedures as objects and adding persistence mapping at the same time, so judge how complex the domain will get before choosing.
There is no object per row, so a method like calculateTotal(orderId) takes the row's key and works on the table's data. That avoids object-relational mapping and fits tooling built around in-memory tables, but row-level identity and per-record polymorphism are gone, so rules that differ record by record fit badly.
It is the one place every interface, whether a web controller, a batch import or an API, calls to perform an operation, so the transaction boundary and the sequence of steps are written once. Without it, each interface repeats that coordination. It earns its keep when more than one kind of client needs the same operations.
A thin service opens a transaction, loads the objects, calls one or two domain methods, saves and returns. When services start holding the business rules themselves, they turn into transaction scripts and the domain objects stop doing anything. Watch for if statements about business policy inside service methods.
It keeps the costs of a domain model, including the mapping layer and many classes, while giving up the benefit of rules that live with their data. Every invariant can be bypassed by calling a setter directly. Moving a validation or calculation into the object that owns the data, then making its setters private, is the first repair.
Procedures run next to the data, which saves round trips and can enforce rules for every client. The costs: a vendor-specific language that must be rewritten if the database changes, weaker debugging and test tooling, changes that need separate scripts to reach version control, and errors your application build never sees.
K. Data source architectural patterns
One class per table holds the select, insert, update and delete statements, so SQL stays out of business code and a database specialist can find and tune it in one place. Its methods take keys or criteria and hand back plain rows, which pairs naturally with logic organized per table instead of per object.
Each instance looks just like its record, with a field per column plus methods to load and save it. Keeping business rules out means the domain logic lives elsewhere, often in scripts. Add domain behavior to these objects and you have Active Record; the dividing line is whether the row object makes business decisions.
Reading and writing live on the same object as its rules, so order.save() is obvious and needs no mapper. The fit breaks when the model diverges from the tables, through inheritance, collections or values spanning several columns, because one object per row no longer holds. It suits forms-over-data applications built on create, read, update and delete.
A rule such as an order's discount sits on a class whose methods also load and save, so testing that rule often needs a database, a transaction or a fake that mimics the persistence API. Keeping calculations in methods that touch no persistence, or in plain collaborator objects, lets those tests run in memory.
Domain objects hold no SQL and do not know a schema exists, and the schema knows nothing of the objects, so either side can be refactored as long as the mapper keeps up. The cost is the mapper itself, often a large ORM, plus the work of keeping it in step with both sides. It pays when a rich object model and the tables have diverged.
Ask how far apart the object model and the tables are now, and how far they will drift. One class per table with simple rules: Active Record is less code. Inheritance, value objects and rules that cross several tables: a mapper keeps persistence from bending the model. Switching from one to the other later touches every persisted class.
A gateway wraps an outside system and presents its operations in roughly that system's terms, so its callers still know the resource is there. A mapper sits between two independent parts and converts between them, and the domain side never imports it or learns the schema. If calling code still talks about rows or messages, you have a gateway.
Annotations put table names, column names and fetch rules into domain classes, so the domain package imports the persistence library, and that library may impose rules of its own, such as requiring a no-argument constructor or forbidding final classes. That is a fair trade for less mapping code, as long as you stop calling the model persistence-ignorant.
L. Object-relational mapping patterns
Instead of writing on every setter, it records new, changed and deleted objects and flushes them together, usually inside one database transaction. Writes therefore happen later than the code that caused them, which surprises people: a constraint violation from an early change can surface at commit, or just before a query, far from its cause.
A child row cannot be inserted before the parent it references, and a parent cannot be deleted while children still point at it. In PostgreSQL 17, an ordinary foreign key is checked at the end of each statement, so write order matters. One declared deferrable and initially deferred is checked only at commit, which relaxes ordering inside the transaction.
Without it, loading the same row twice gives two objects, and edits to one are invisible to the other until a save overwrites somebody's change. The map also skips repeat queries for a key already loaded. Its scope is one session or transaction, so a session kept open too long collects objects and can go stale.
It comes in four forms: a field checked for null on access, a virtual proxy with the same interface, a value holder, and a ghost that fills its own fields. All share one trait: a getter can now hit the database, so the cost of a line depends on what was already loaded, not on what the line says.
Load 100 orders, touch each order's customer, and you can get 1 query plus up to 100 more. A join fetch in the query loads everything in one round trip and is the real fix. Batch fetching only shrinks the count by loading several keys per extra query. Log the SQL in tests, since the code looks identical either way.
The proxy needs an open session to run its query, so once the session ends, touching an unloaded field throws instead of loading. The usual trap is a view or serializer that runs after the transaction closed. Fetch what the response needs inside the transaction and copy it into plain objects, instead of holding the session open across the whole request.
In memory, the runtime already knows when two references point at one object; the database tells rows apart only by primary key. Storing that key lets an update target the right row. A key with no business meaning is safer, because a natural key such as an email address can change, and the object's identity would change with it.
An object can reference another directly, but a table can store only a key value, so the reference becomes a column holding the target's primary key. For one-to-many, the key goes on the many side: each order line stores its order's ID, and the order's collection is rebuilt by querying for lines with that ID.
A column holds one value, so a student row cannot list every course and a course row cannot list every student. A link table with two foreign keys records one pairing per row. Once the link itself needs data, such as an enrollment date or a grade, it usually deserves to become a class of its own instead of a hidden table.
Every subclass's fields become columns in one wide table, and a type column says which class each row is. Queries need no joins, but columns for subclass fields cannot be declared NOT NULL, since other subclasses leave them empty; enforcing them takes a CHECK constraint keyed on the type column, or application code. It works best when subclasses add few fields.
Each class in the hierarchy gets its own table holding only its fields, sharing a primary key with the parent's row. Constraints like NOT NULL work again and the schema mirrors the classes. The cost is joins: loading a deep subclass joins every level, and a query over the base type may join all subclass tables to rebuild each row.
Each concrete class's table repeats the inherited columns, so loading one known type is a single-table read. A query for the base type must union every table, and an association pointing at the base type cannot be backed by a real foreign key constraint, since the target row could live in any of several tables.
Callers add, remove and find domain objects as if working with an in-memory collection, and query construction is kept in one place instead of repeated across services. Name its methods after domain questions, such as overdueInvoices(), rather than exposing a generic query builder, or callers end up writing data-access logic again.
If a repository returns an unexecuted query, callers can add filters and joins in the ORM's own query language, and when the SQL runs is no longer obvious. If it returns lazy proxies, a caller touching a field after the session closes gets an error. Return finished results: loaded objects or plain data with everything the caller needs.
M. Web presentation patterns
The domain split pays the most: a model that knows nothing about screens can be tested alone and reused by a web page, an API and a batch job. Desktop toolkits mostly merged view and controller, and lost little. On the web the second split returns on its own terms: the controller reads the request and chooses a response, and the view only renders what it is handed.
It fits a site whose pages are simple and unlike each other, since each URL leads to one obvious file or class. The cost arrives with cross-cutting work: authentication, logging and error handling get copied into every controller unless you move them into helpers or a shared controller superclass. When that shared base keeps growing, a Front Controller is usually the better shape.
Common behavior runs as a chain of filters or decorators wrapped around every request, and the handler then hands off to a command object chosen from the request. You can add or reorder that shared behavior at runtime without touching any page. The risk is a handler that soaks up page-specific logic; keep it to dispatch and cross-cutting work, and let the commands hold the rest.
Designers can edit a template with ordinary HTML tools, its main edge over printing markup from code. The trap is that conditionals, loops and formatting pile up in the page until business rules live there, beyond the reach of unit tests. Keep the page dumb by giving it a helper object that prepares every value, so each marker is a plain lookup rather than a computation.
It is organized around the input, one rule per kind of domain element, where Template View is organized around the layout of the output page. Since each rule is a function from data to markup, you can test it with no web server running. The cost is that page designers cannot see or edit the final layout directly — XSLT over XML was the classic implementation.
Stage one turns domain data into unformatted screen elements such as tables, fields and buttons; stage two turns those elements into HTML. A global restyle, or a second look for another brand, changes only stage two. It struggles when designers want each page to look different, because every visual choice must be expressible in the shared logical vocabulary.
Input controllers stop deciding what comes next and instead ask one object which command to run and which view to show, given the current state. That ends the duplication when several screens must agree on where a user goes after a step. If users can visit screens in almost any order, the extra object buys nothing; reach for it when order and conditions really drive navigation.
Rendering turns into a hidden data-access layer: a loop over rows that reads a lazily loaded field fires one query per row, and nothing in the controller shows it. A slow or failed query then breaks the response midway through writing it. Load everything the page needs before rendering starts, pass it in as a plain view model, and treat any query logged during rendering as a defect.
N. Distribution and session state
The law targets distributed objects, where middleware tried to make one class usable either locally or remotely. That transparency fails, because an interface fit for in-process calls is far too chatty across a network. Microservices keep to the letter of it by exchanging coarse-grained documents rather than remote objects, yet every service boundary still pays the latency and failure cost.
Underneath, the fine-grained objects keep their small methods and expose no remote interface; the facade only translates one bulk call, such as saving an order with all its lines, into many local ones. It holds no domain logic of its own. It shares its shape with the ordinary Facade pattern, but the motive is the cost of each network round trip, not simplicity.
Its fields follow what one remote call needs, not the shape of the domain, so a single DTO often combines parts of several domain objects. It can also own its serialization, giving that mechanism one home you can change later instead of scattering it through the code. Older Java writing called it a 'value object', which collides with the unrelated Value Object pattern.
Once clients deserialize your entities, renaming a field or splitting a class breaks every consumer at the same moment, so the internal model freezes. You also risk sending fields no client should see, or dragging a lazily loaded graph into the payload. A DTO is a published contract you can version on its own schedule while the domain model keeps changing behind it.
Put the mapping on either side and you create a bad dependency: a DTO that knows domain classes drags them onto every client, and a domain object that knows DTOs ties your model to one wire format. A separate assembler depends on both, so neither depends on the other. It is also the one place to edit when either the contract or the model changes.
It lives in URL parameters, hidden form fields or cookies, so any server can take the next request and nothing on the server needs cleanup. The user controls all of it: sign it to detect tampering, encrypt it if it must stay secret, and remember that a signed value can still be replayed later. Size is the other limit, since browsers are only expected to keep about 4 KB per cookie.
Holding the session object in memory is the simplest form, and it ties each user to the node holding it: lose that node and the session is gone. Serializing sessions to a shared store frees requests to land anywhere, at the price of a lookup per request. Either way, sessions abandoned without a logout keep taking space until a timeout reclaims them.
Each request loads the session rows it needs and writes changes back, so any node can serve any request and a crash loses nothing committed. Pending data has to be kept apart from real records, by a flag or separate tables, so other queries do not treat a half-finished order as real. Abandoned sessions leave rows behind, so you also need a cleanup job.
O. Offline concurrency
A user may spend minutes on a form, and a database transaction held open through that think time keeps locks and a connection the whole while, starving everyone else. So each request runs its own short system transaction, and the database no longer protects the edit as a whole. Offline locks rebuild that protection in application code.
Each row carries a version number. The update runs with WHERE id = ? AND version = ? and increments the version; zero affected rows means someone else committed first. The check and the write must share one system transaction, or another session can slip in between them. It costs nothing while you work, but the loser learns of the conflict only at the very end.
The lock is a record in a lock table, claimed before you read the data and released when the business transaction ends. A lock manager that refuses at once instead of making callers wait rules out deadlock between two users. People who walk away mid-edit leave locks held, so you also need timeouts and a way to break stale locks.
Optimistic is the usual default: it allows full concurrency and wastes work only when a conflict actually happens. Switch to pessimistic when conflicts are frequent, or when the losing user would throw away a long edit such as a claim form that took twenty minutes. Applied everywhere, pessimistic locking turns rare conflicts into constant waiting.
Locking a customer and each of its addresses separately forces code to find every member first, and an optimistic check would load the whole group just to compare versions. Instead, give the group one shared version, or lock only the aggregate root and treat that lock as covering every member. The cost is lower concurrency: two users editing different addresses of one customer now collide.
Offline concurrency bugs rarely show up in tests, so a missed lock can ship unnoticed and surface later as overwritten data. Putting acquisition in a framework, a layer supertype or generated code means the ordinary load and save path takes the lock for you. Releasing matters too: a lock that is never freed corrupts nothing, but it slowly shuts everyone else out.
It needs no exotic timing: two browser tabs open on one record are enough, and nothing reports an error. Database transactions alone do not prevent it when the read and the write happen in separate requests. A version check on update turns the silent loss into a detected conflict, and for a plain counter an atomic SET n = n + 1 avoids the read-modify-write cycle.
Each value may be correct at the moment you read it, yet another session committed between your reads, so a total built from them matches no real state. Checking versions only on rows you write misses this. Recheck at commit the versions of rows you only read, or read them in one system transaction at Repeatable Read or stricter; under PostgreSQL's default, Read Committed, each statement can see newer commits.
P. Base patterns
Design the interface from what your code needs, then translate to the foreign API inside it. Keep only translation logic there; rules built on the results belong in the gateway's callers. The payoff is one place to edit when the vendor's API shifts, and one seam to stub in tests. Renaming a vocabulary everyone already knows, just because the names feel awkward, is the misuse.
It runs locally and in memory and returns canned answers for things like tax lookups or credit checks, so tests stay fast and still pass when the real service is down. A stub checks only your side of the conversation. If the real service changes its responses, the stub keeps passing, so pair it with a few tests against the real service or its published contract.
Typical contents are identity-field handling for every domain object, or the shared load and save steps for every mapper. It removes repetition cheaply. The risk is a base class that gathers whatever two types happen to share, until every class inherits unrelated baggage, and in a single-inheritance language it also spends the one superclass slot each type gets.
It solves a bootstrap problem: reaching a finder when you hold only an ID and no object to navigate from. Because callers reach it directly, dependencies vanish from constructors, and tests must reset its shared state between runs. Pick its scope on purpose: one per process, per thread or per session. Passing dependencies in explicitly is usually cleaner when you can.
Override equality, and the hash that must agree with it, to compare every field. Identity then stops mattering, so you can copy a value freely instead of sharing one instance. Money, dates, ranges and coordinates fit. If matching fields could still describe two things you must tell apart over time, such as two customers named Ana Silva, you need an entity instead.
Value semantics invite sharing: two orders may hold the same date object, and if one owner calls a setter, the other's date moves too, an aliasing bug with no visible assignment. Drop the setters and make every change return a new instance. The mutable java.util.Date is the classic offender; Java 8 added java.time, whose types are immutable.
Adding dollars to yen should fail loudly, which only a type pairing amount with currency can enforce. Store amounts as integer minor units or a decimal type, never binary floating point. To split $100.00 three ways, an allocate method returns 33.34, 33.33 and 33.33, handing the leftover cent to one share so the parts still sum to the whole.
Callers stop asking whether a value is null, because the special object answers every message sensibly, such as an unknown customer whose name reads 'occupant'. A Null Object answers by doing nothing; other special cases supply real but unusual behavior, like infinity in arithmetic. If callers still need to tell the case apart, give it an isUnknown query rather than returning null again.
Q. Code smells
A smell is quick to spot, which is its value: even a newcomer can flag a long method or a class that holds only data. Deciding whether it hurts takes a deeper look, because plenty of long methods read fine. Treat each smell as a question to investigate, and let the answer, not the smell itself, decide whether you refactor.
A 40-line function that reads as a flat list of steps at one level is easy to follow; a 12-line one that jumps from business intent to string slicing is not. A practical test is whether a block needs a comment to explain it. If so, extract that block and let its name carry the meaning, so the top function reads as a summary.
If one argument can be derived from another, let the function derive it. If several come from one object, pass the object. If a group always travels together, introduce a parameter object, and if a boolean picks behavior, split the function. Keep a parameter when removing it would force the function to reach into a dependency it should not know about.
A call like book(customer, true) hides what true means, while premiumBook(customer) says it. If both paths share tangled code, keep a private implementation that takes the flag and expose two named public functions over it. The advice targets literal flags at call sites; when the value comes from data, such as a checkbox, passing it through is fine.
Two copies are a promise to keep them in step, and nothing enforces it, so a bug fixed in one survives in the other. Merge them with Extract Function, sliding statements together first if the copies differ in order. The exception is code that only looks alike: if two blocks change for different reasons, joining them couples unrelated features.
You open the same class to support a new database, to add a currency, and to change a report format. Each kind of change wants its own home. Split it with Extract Class or Split Phase so that one reason to change maps to one module. It mirrors Shotgun Surgery, where a single change scatters across many modules.
Adding one field means editing a mapper, three validators, two views and a report, and missing any of them is a bug. Gather the pieces with Move Function and Move Field, or Combine Functions into Class. Inlining small classes that existed only to hold a fragment is often part of the fix, since splitting too eagerly is one way this smell starts.
When a method calls four getters on an Order and one on its own class, move it to Order. If only part of it envies, extract that part first and move just the part. If it draws on several classes, place it with the one whose data it uses most. Strategy and Visitor break this rule on purpose, to keep varying behavior apart from the data.
A start and end date, or a street, city and postal code, recur together in fields and parameter lists. A quick test: delete one of the values and ask whether the rest still mean anything. If not, they form one concept. Once it is a type, behavior such as range checks or address formatting finally has an obvious place to live.
A String email accepts 'not an email', and an int amount forgets its currency and lets you add cents to dollars. Wrapping each in a small type puts validation in one constructor and lets the compiler refuse mix-ups, such as passing a customer ID where an order ID belongs. Type codes kept as strings, like priority 'high' or 'rush', are the same smell.
Typical signs are an abstract class with one subclass, a parameter no caller ever passes, or a hook whose only user is a test. Each one makes the code harder to read and change, a running cost paid on every feature until it is removed. Collapse the hierarchy, inline the class or drop the parameter. Refactoring to keep code easy to change is not speculation.
Ignoring a few inherited methods is mild, and pushing them down to the subclasses that use them usually fixes it. It turns serious when the subclass reuses the parent's code but will not honor its interface, so callers holding the parent type get surprises. Then replace inheritance with delegation: keep the old parent as a field and expose only what fits.
A block headed by a comment such as 'compute the late fee' is a function waiting for that name; extract it and the comment usually becomes redundant. Comments still earn their place when they explain why: a workaround for a vendor bug, a rule from a regulation, an approach tried and rejected. A stated assumption often reads better as an assertion.
It grows by gravity: each new feature finds it convenient to add one more field and method there. Spot it by how many modules import it and by fields that only a few of its methods touch. Break it apart along clusters of fields that are used together, moving the methods with them. A facade that only delegates is not one, because it owns no logic of its own.
R. Refactoring: the discipline and the core moves
A refactoring may change speed, memory use or private interfaces, but not the results a caller sees. Fixing a bug while restructuring breaks that promise, since the old wrong answer was observable behavior someone may depend on. Note the bug, finish the refactoring with the tests still green, then fix it as a separate change.
With the refactoring hat on, you change structure and add no new behavior, so existing tests should keep passing untouched. With the feature hat on, you add behavior and its tests and leave structure alone. You may swap hats every few minutes; what matters is that each commit belongs to one hat, so review stays easy and a failure points at one kind of change.
Every step is checked by rerunning the suite, so a slow suite means fewer checks and bigger, riskier steps. Without tests, first write characterization tests that pin down current behavior, bugs included. Tool-driven renames in a statically typed language are the safest moves you can make without tests, yet even those can miss calls made through reflection or names built from strings.
Each step leaves code that compiles and passes its tests, so you can stop or commit at any moment. If a test fails, revert to the last green state and take a smaller step rather than debugging forward. Long chains of tiny steps reach large redesigns while the code stays shippable throughout, which a big-bang rewrite cannot promise.
Name it for what it achieves rather than how: isEligibleForDiscount, not checkAgeAndTotal. A one-line function is fine if its name says more than the line. The friction is local variables: those the fragment only reads become parameters, but if it assigns several of them, it is not one idea yet, so split variables or extract queries first.
Inlining also works as a reset: when a group of functions is split badly, inline them into one body and extract again along better lines. Check first that no subclass overrides the function, because inlining a polymorphic method silently drops the override. Recursive functions, or ones with several return points, take more care.
A name like quantityDiscount explains one piece of a long expression and gives a debugger something to show. Prefer it when the meaning matters only inside one function. If the same name would help elsewhere in the class, extract a function instead so others can call it. Its inverse, Inline Variable, removes a name that says no more than the expression.
A function with few callers can change in one direct step, which is simpler and just as safe. Migration earns its overhead with many callers or a published API, where you cannot edit every caller at once; mark the old function deprecated and leave it forwarding until outside users have moved. Renames deserve the effort, since a name is the first thing every caller reads.
A function that reads three fields of another module and none of its own sits in the wrong place, and every change to that data means editing two modules. Moving it lets data and behavior change together. With many callers, leave a forwarding function behind, retarget the callers, then delete it. Check what it calls too, since its helpers may want to move with it.
One sign is a group of fields that change together, plus methods that use only them, such as an area code and number inside a Person. Pull them into a TelephoneNumber and the owner gets simpler. Another clue is subtyping that affects only some features. Each new class should make sense on its own, or you have only moved the clutter.
After other refactorings, a class can end up as a thin pass-through with one or two methods. Folding it into its main user removes a hop every reader has to follow. Inlining is also how you regroup responsibilities: merge two awkwardly divided classes into one, then extract again along a better line.
Hide Delegate adds person.manager so callers stop chaining person.department.manager and no longer depend on Department. The cost grows with every feature callers want, since each needs another forwarding method. Once a class is mostly forwarders, Remove Middle Man lets callers reach the delegate directly. Rebalance as usage changes.
amountInvoiced(start, end) becomes amountInvoiced(range). The new type can then enforce that start is not after end, and grow behavior such as range.includes(date) that used to be scattered across callers. It also lets you add a field without touching every signature. Make it a value object: immutable and compared by its fields.
S. Refactoring conditionals, data and hierarchies
A test like date >= summerStart && date <= summerEnd becomes isSummer(date), and each branch gets a name such as summerCharge(). The reader sees the decision and the alternatives without decoding the logic. It costs a few small functions, and it pays most when the condition is complex or the branches are long.
Three early returns of zero, for an expired card, a frozen account and a missing address, merge into one cannotShip() test that is easy to name. Skip the merge when the checks are truly independent reasons that only happen to share a result today, because joining them hides the fact that they could later diverge.
A guard clause says this case is rare, so handle it and leave. An if-else says both paths are equally normal. Use guards for the special cases and keep if-else where the branches really are peers. The old rule of a single exit per function matters less than clarity, and deep nesting hides which line is the normal outcome.
Make one class per case, move each branch into an overriding method, and have a factory pick the class when the object is created. The type is then examined once, at creation, rather than in every function that asks about it. Behavior shared by most cases can stay in the superclass, with subclasses overriding only where they differ.
Classes make adding a new case easy and adding a new operation hard, since every class must change; a switch does the reverse. With one switch over a stable set of cases, a hierarchy adds files and indirection and buys nothing. Count how many places branch on the same type: a single spot says keep the switch, and repeats across several functions argue for classes.
Find every caller that checks for the same missing value and then does the same thing, such as showing 'occupant' for an unknown customer. Create a class with the expected interface that gives that answer, and return it wherever the missing value used to appear. Callers that react differently still need an explicit check, so leave those alone.
A returned code like -23 travels on silently if any caller forgets to check it, while an exception keeps propagating until something handles it. Use exceptions for conditions callers do not expect in normal flow. If callers routinely test for the case first, such as an index past the end, the reverse move, Replace Exception with Precheck, fits better.
Route every read and write through a getter and setter first. Then you can rename, move, validate or log changes in one place instead of hunting down every reference. Wrapping a reference does not stop callers from mutating the object behind it, so when that matters, have the getter hand back a copy, or apply Encapsulate Record. Immutable data needs this far less.
A temp computed once and used across a long function ties every extracted piece to it, since each piece must receive it as a parameter or recompute it. A query method gives every piece its own access. It suits a temp assigned once from a calculation that returns the same result each time; a temp that accumulates, or depends on order, cannot become a query.
A phone number starts as a string, then needs formatting, then area-code lookups, and that logic spreads to every caller until a small class gathers it. Priority stored as 'normal' or 'rush' can become a Priority that knows its own ordering, so higherThan replaces string comparisons. Start with a wrapper around the old value, then move behavior in.
Expose methods such as addCourse and removeCourse, so your class can validate and keep its own bookkeeping, and have the getter return a copy or a read-only view. A read-only view still reflects later changes, while a copy is a snapshot, so choose deliberately. A setter that installs a caller's list should copy it, or the caller keeps a live handle.
The first phase turns raw input into a clean intermediate data structure; the second works only from that structure. Each phase can then be read, tested and changed without the other. A compiler is the classic case: parse, then analyze, then generate. Signs you need it are parsing steps mixed into calculations, or one function handling two input formats.
input.filter(isProgrammer).map(toName) reads as names of programmers, where a loop with a push says only that some list gets built. Each stage is named and does one job. Keep stages free of side effects. When one loop does two jobs, split the loop first, then convert each part. A loop measured as a hot spot can stay a loop.
A subclass fixes one axis of variation for good: by inheritance an order can be a priority order or an international one, not both, and it cannot become priority later. A delegate field can be swapped at runtime and combined with others. Inheritance stays reasonable for a single stable axis; switch when a second axis or a runtime change appears.
T. Anti-patterns
The definition separates it from a plain bad idea: an anti-pattern looks right at first and keeps getting chosen, which is why it deserves a name. Without a documented better alternative it is just a hard problem, not an anti-pattern. By the usual rule of three, a practice should be seen at least three times before it earns the label.
The giveaway is reframing the problem to suit the tool: graph-shaped data forced into tables, or every task turned into a new service because building services is what the team knows. Familiarity cuts learning time, so the choice looks efficient at first. Ask what you would pick if every option were equally familiar, then weigh that against the cost of the mismatch.
It forms two ways: throwaway code that kept working and kept being extended, and a once-clean design eroded by piecemeal growth under changing requirements. Each expedient change was locally sensible and shipped sooner. A full rewrite is one option and a risky one; carving out one well-bounded area at a time and keeping it clean usually goes better.
It usually starts as provisional or exploratory code that shipped, then hardened as other code built on it. Each year it stays, deleting it looks riskier. Prove what depends on it with tests, call logging or production usage data, then remove it in small steps. Version control keeps a copy, so there is no need to keep dead code around just in case.
The database version is one table of entity, attribute and value columns: flexible on paper, but the database can no longer index, type-check or join it sensibly, so the application redoes that work more slowly. A rules engine that grows variables, loops and conditions is the code version. Use the real language, or keep the configuration a small, fixed vocabulary.
Without a profiler you usually guess wrong about where time goes, and micro-tuning rarely gains more than a few percent while making code harder to change. Choosing an algorithm or data structure is different: a poor choice can cost orders of magnitude and be expensive to fix late, so weigh it at design time. Set a target, measure, then tune the hot spot.
price * 1.05 hides a 5% tax rate; a named TAX_RATE explains it and changes in one place. If 30 means a timeout in one spot and a page size in another, a find-and-replace can change the wrong one. A name like SIXTEEN = 16 fixes nothing. Literals such as 0 and 1 as loop bounds, or 2 in an even-number test, rarely need a name.
The program carries on in a state it never meant to reach, and the failure surfaces later somewhere unrelated, with no stack trace to follow. Catch only what you can actually handle. Otherwise let it propagate, or wrap it in a new exception that keeps the original as its cause. A rethrow that discards the original type and trace is a quieter form of the same fault.
If your interface accepts or returns the vendor's request objects, error types or enums, every caller still imports the SDK, and switching vendors means editing all of them. The test: could you write a fake implementation without depending on the vendor library? If not, translate into your own types at the boundary, and convert the vendor's errors there as well.
U. Domain-driven design and architecture-level patterns
If experts say 'policy' and the code says ContractRecord, every conversation and bug report needs a mental translation, and meanings drift inside it. Use the experts' terms in class and method names, and treat awkward wording as a modeling problem to fix. The language holds within one bounded context; another team may rightly use the same word differently.
Equality compares the ID, not the attributes: a customer who changes name and address is still the same customer. The ID may come from outside, such as an account number, or be generated by the system, but it must match what the domain means by the same thing. An ID assigned only when the database saves leaves a new object without identity until then.
An order and its lines form one unit: outside code keeps references to the order only, and the order enforces rules that span its lines, such as a credit limit. References to inner members may be handed out only for the length of one operation. Repositories load and save whole aggregates through the root, never single inner parts.
Keep each aggregate consistent synchronously inside its own transaction, and let changes to other aggregates follow asynchronously, usually by publishing a domain event. That keeps locks small and lets aggregates live on different nodes. If a rule seems to demand updating two aggregates atomically, treat it as a hint that a boundary is drawn in the wrong place.
To sales, a product has a price and a pitch; to the warehouse, it has a size and a shelf. One unified model must satisfy both and fits neither well. Instead, each context gets its own internally consistent model, and the points where contexts meet are mapped explicitly. Boundaries usually follow teams and the language each one speaks.
The downstream team builds it, and it talks to the other system through that system's existing interface, translating in one or both directions so your code sees only your own model. It earns its cost when the upstream model is weak, as legacy models often are. The alternative is to conform: adopt the upstream model wholesale, which is cheaper but gives up your own design.
Name it in the past tense, such as OrderShipped, and make it immutable, since it records history. A command like ShipOrder can be refused; an event cannot, because it already happened. Record when it occurred and, separately, when your system noticed it, since the two can be days apart. An event ID lets a receiver spot duplicates.
A port is an interface stated in the application's own terms, such as saving an order. An adapter translates between that port and one technology: a web UI, a SQL database, an in-memory fake for tests. Inside and outside replace top and bottom, so the database is just another adapter. The six sides mean nothing; the shape only leaves room to draw ports.
In onion, clean and hexagonal designs, inner code never names anything from an outer ring, so the database and web framework can change without touching business rules. When inner code must call outward, it declares an interface and the outer ring implements it. A classic layered stack breaks the rule wherever domain code calls the data layer below it directly.
The write side enforces the rules; the read side serves screens and reports, possibly from its own store updated after each change. The split helps when reads and writes differ sharply in shape or load. For most systems one shared model is simpler and the split adds risk. Apply it to one bounded context rather than a whole system, and consider a reporting database first.
Current state becomes a cache: you can discard it and rebuild by replaying events, reconstruct the state at any past date, or replay with a bug fixed. Snapshots shorten the replay. During a replay, calls to external systems must be switched off or answered from a recording, or you resend emails and charges that already happened.
Rebuilding from millions of events is slow, so you end up managing snapshots. Stored events are normally left as written, so your code must keep reading every old event shape unless you run a copy-and-replace migration into new streams. A question like which customers owe over $500 needs a separate read model kept in sync. If you only need an audit trail, a log beside normal tables costs far less.
V. Object-relational structural and metadata mapping
An Employment row gets salary_amount and salary_currency columns instead of a link to a money table. Loading needs no join, and there is no identity to manage. Two values of the same type in one row need column prefixes, such as billing and shipping addresses. A value that must be shared between owners or queried alone needs its own table.
An album's mapper loads and saves its tracks, so tracks need no mapper of their own. That only works while nothing outside the album points at a track. Updates are often done bluntly: delete all of the album's track rows and insert the current set. Once another table needs to reference a track, the track needs its own identity and mapper.
It suits a self-contained graph read and written whole, such as an organization chart or a user's saved settings. An opaque binary blob is invisible to the database, but a JSON column is not entirely: PostgreSQL 17 can index jsonb with GIN and change one key through a subscripted UPDATE, though that still locks the whole row. Avoid either form when objects inside it are also referenced from outside, since the copy inside goes stale.
An abstract mapper loads and saves the fields every subclass shares, and each concrete mapper calls it before handling its own fields. A top-level mapper works out which concrete class a row belongs to, from a type code or from the table that holds it, and hands it to the right concrete mapper. The arrangement holds for all three storage schemes; the SQL inside the mappers is what changes.
Annotations, XML or a table state which field maps to which column, and generic code reads that to perform every load and save. Adding a field becomes one line of metadata. With the reflective variant, a misspelled column surfaces only when the code runs, so add a startup test that loads every mapped type; the code-generation variant catches more at build time.
Callers build criteria from classes and fields, and the query object turns itself into SQL, so a renamed column changes in one place. Underneath it is an interpreter, which means you are maintaining a small query language. A hand-built one rarely pays off unless you already run a domain model with data mappers; most teams use the criteria API their ORM provides.
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.