Crossing the line from describing the problem to building a solution.
π Where this lives: this transition is where most object-oriented projects go wrong, and the failure has a recognisable shape: a beautiful domain model, and then code full of `ApplicationManager`, `LicenceService` and `DataHelper` classes that hold all the behaviour while the domain classes hold only data. The result is called an anaemic domain model, and it is the default outcome unless someone deliberately decides where behaviour goes. That decision is the whole content of this section. Search "anaemic domain model Fowler anti-pattern".
What changes at the boundary
ANALYSIS asked WHAT. DESIGN asks HOW. Concretely, five things
change.
1. THE MODEL GAINS SOFTWARE CLASSES.
The conceptual model contained only domain concepts. The
design model adds the classes that make a running system:
controllers, repositories, factories, adapters, gateways,
view/presentation classes, configuration
None of these has a domain counterpart, and none belongs in
analysis.
2. RESPONSIBILITIES ARE ASSIGNED.
Analysis said "a Payment was created and associated with the
Application". Design says WHICH OBJECT does the creating and
WHICH holds the reference. This is the central design
activity and the subject of the patterns topic.
3. VISIBILITY IS DECIDED.
For object A to send a message to object B, A must be able
to SEE B. Analysis associations are undirected; design must
decide who holds a reference to whom, and how they got it.
4. TYPES, SIGNATURES AND EXCEPTIONS APPEAR.
`approve()` becomes
`approve(officer : OfficerId) : void throws
IllegalTransitionException`.
5. NON-FUNCTIONAL REQUIREMENTS ENTER.
The supplementary specification β response times,
availability, security β had no expression in the use case
model. Now it drives layering, caching, transaction
boundaries and error handling.
THE TWO CENTRAL DESIGN OUTPUTS, produced together and
iteratively:
INTERACTION DIAGRAMS sequence or communication diagrams
showing which objects collaborate to
realise each system operation
DESIGN CLASS DIAGRAM the classes, with types, visibility,
method signatures and navigable
associations
THE ORDER MATTERS AND IS COUNTER-INTUITIVE: DRAW THE
INTERACTIONS FIRST, then read the class diagram off them.
Deciding the interactions is what determines which methods each
class needs β a message sent to an object IS a method on that
object. Teams that draw the class diagram first invent methods
speculatively and then find the collaborations do not need
them.
THE INPUTS TO DESIGN, all from ACtE0804:
use cases β what interactions must be
supported
system operations β the entry points; each becomes a
(from the SSDs) controller method
operation contracts β what each interaction must
achieve, as postconditions
domain model β the source of most design classes
supplementary spec β the non-functional constraints
glossary β the names to use
Layers and the domain model's role
THE STANDARD LAYERED ARCHITECTURE for an OO application β the
same layered organisation from ACtE0802, applied here:
βββββββββββββββββββββββββββββββββββββββ
β UI / PRESENTATION β windows, screens,
β β reports, API
β β endpoints
βββββββββββββββββββββββββββββββββββββββ€
β APPLICATION / SERVICE β coordinates use
β β cases, transaction
β β boundaries
βββββββββββββββββββββββββββββββββββββββ€
β DOMAIN β the business
β β concepts AND their
β β behaviour
βββββββββββββββββββββββββββββββββββββββ€
β TECHNICAL SERVICES / INFRASTRUCTURE β persistence,
β β logging, messaging,
β β security
βββββββββββββββββββββββββββββββββββββββ
THE RULES:
Β· a layer may depend on layers BELOW it, never above
Β· the UI contains NO business logic β a rule broken
constantly, and the reason is that it is convenient in
the short term
Β· the DOMAIN layer must not depend on the UI or on
persistence. This is what makes the domain testable
without a database and portable across UI technologies.
THE DEPENDENCY INVERSION PROBLEM: the domain needs to save
things, but saving is infrastructure, which is below it β so a
direct call would be fine. But the domain must not depend on a
specific database technology. THE SOLUTION: the domain declares
an INTERFACE (`ApplicationRepository`) and infrastructure
implements it. The dependency arrow is inverted, and the domain
depends only on its own abstraction.
domain: interface ApplicationRepository
infrastructure: class PostgresApplicationRepository
implements ApplicationRepository
This is the Dependency Inversion principle, and it is why the
interface lives in the domain package rather than beside its
implementation β a detail that trips people up.
THE ANAEMIC DOMAIN MODEL β the failure this layering is meant
to prevent, and worth seeing side by side:
β ANAEMIC β domain classes are data holders
class Application {
getStatus(); setStatus(s);
getFee(); setFee(f); // getters and setters only
}
class ApplicationService {
approve(app, officer) {
if (app.getStatus() != APPROVED_PENDING) throwβ¦
if (app.getReviewedBy().equals(officer)) throwβ¦
app.setStatus(APPROVED);
app.setApprovedBy(officer);
}
}
The rules live OUTSIDE the object that owns the data. Any
other service can call setStatus(ISSUED) and bypass every
rule β the invariants are unenforceable.
β RICH β behaviour lives with the data
class Application {
private Status status;
private OfficerId reviewedBy;
void approve(OfficerId officer) {
assertTransition(APPROVED);
if (officer.equals(reviewedBy))
throw new SeparationOfDutiesException();
this.status = APPROVED;
this.approvedBy = officer;
}
}
There is NO setStatus. The only way to reach APPROVED is
through approve(), which enforces the rules. The invariant
is now impossible to violate from outside.
THE TEST FOR ANAEMIA: does the class have setters for the
fields that carry business rules? If so, the rules are
elsewhere and can be bypassed.
Worked: one system operation designed
TAKE the contract from ACtE0804 and design the collaboration.
INPUT β the contract:
OPERATION recordPayment(applicationNo, amount, method)
PRE the Application exists, is APPROVED, has no Payment
POST a Payment was created with amount, method, time;
associated with the Application and the Officer;
a Receipt was created and associated with the Payment;
no other Application was modified
STEP 1 β WHERE DOES THE OPERATION ARRIVE?
Each system operation needs a CONTROLLER: the first object
beyond the UI layer that receives it. Two legitimate choices:
a FAΓADE CONTROLLER β one per subsystem
(LicenceSystemController)
a USE CASE CONTROLLER β one per use case
(IssueLicenceHandler)
CHOOSE THE USE CASE CONTROLLER WHEN a faΓ§ade would become
bloated with dozens of unrelated operations β which is the
high-fan-out, low-cohesion problem from the design heuristics
topic. Here: `RecordPaymentHandler`.
STEP 2 β WHO KNOWS WHAT? Walk each postcondition and ask which
object holds the information needed:
"is APPROVED, has no Payment" β the Application knows its
own state β IT validates
"amount equals the fee due" β the Application knows its
fee β IT validates
"a Payment was created" β who has the information to
initialise it? The
Application (it knows the
amount owed) or the
handler (it has the
officer). Either is
defensible; the
Application creating it
keeps the invariant
"a payment matches the
fee" inside the object.
"a Receipt was created" β rendering is presentation,
so a ReceiptFactory or
service, NOT the domain
"persisted" β the repository
STEP 3 β THE INTERACTION:
:RecordPaymentHandler
β
β 1. app := find(applicationNo)
ββββββββββββββββΆ :ApplicationRepository
β
β 2. payment := app.recordPayment(amount, method, officer)
ββββββββββββββββΆ :Application
β β 2.1 assertPayable(amount)
β β 2.2 Β«createΒ» :Payment
β β 2.3 this.payment := payment
β
β 3. save(app)
ββββββββββββββββΆ :ApplicationRepository
β
β 4. receipt := create(payment)
ββββββββββββββββΆ :ReceiptFactory
β
β 5. return receipt
STEP 4 β READ THE CLASS DIAGRAM OFF THE INTERACTION. Every
message becomes a method:
RecordPaymentHandler
+ handle(no : String, amount : Money,
method : PaymentMethod, officer : OfficerId)
: Receipt
- repository : ApplicationRepository
- receipts : ReceiptFactory
Application
+ recordPayment(amount : Money, method : PaymentMethod,
officer : OfficerId) : Payment
throws NotPayableException
- assertPayable(amount : Money) : void
- payment : Payment {0..1}
Β«interfaceΒ» ApplicationRepository
+ find(no : String) : Application
+ save(app : Application) : void
NOTE WHAT DID NOT APPEAR: no `setStatus`, no `setPayment`, no
`ApplicationManager`. The methods that exist are exactly the
messages the collaboration sends β which is why interactions
are drawn first.
STEP 5 β CHECK AGAINST THE NON-FUNCTIONAL REQUIREMENTS:
Β· the whole operation must be ONE TRANSACTION (money and
state must not diverge) β the transaction boundary is the
handler, which is why the application/service layer
exists at all
Β· the audit entry from the contract must be immutable β
appended, never updated
Β· p95 under 2 s β one repository read, one write; no
further external calls on this path
THE WHOLE POINT OF THE EXERCISE: the design was DERIVED from
the contract, postcondition by postcondition, rather than
invented. Each "who knows what?" question has a defensible
answer, and where two answers were defensible the choice was
made on which one keeps an invariant inside an object.
The anaemia test is worth applying to any codebase you meet: does a class have setters for the fields that carry business rules? If it does, the rules live somewhere else and can be bypassed, and the object is a record with a class keyword in front of it.
π Go further: the layering in this topic has a stricter modern formulation in hexagonal architecture (also called ports and adapters, or clean architecture). The domain sits at the centre with no outward dependencies at all; every interaction with the world β HTTP, database, message queue, clock, filesystem β enters through a port (an interface the domain declares) implemented by an adapter outside. The practical payoff is that the entire domain can be tested with in-memory adapters, in milliseconds, with no infrastructure running β which is the dependency inversion above taken to its logical conclusion. Search "hexagonal architecture ports and adapters".
π‘ Exam angle: state what changes between analysis and design β software classes appear, responsibilities are assigned, visibility is decided, types and exceptions are added, non-functional requirements enter. Name the two main design outputs (interaction diagrams and the design class diagram) and state that interactions are drawn first because a message becomes a method. Describe the layered architecture with the rule that dependencies point downward and that the domain layer must not depend on the UI or persistence, and explain dependency inversion via a domain-declared interface. Be ready to explain and identify the anaemic domain model.
Syllabus points
Transition from analysis to design
Create a free account to tick topics off, take notes as you read, watch the video lessons and get a day-by-day study plan built around your exam date.