Skip to main content

Chapters 17–20: Boundaries, Business Rules & Policy

"Software architecture is the art of drawing lines that I call boundaries."

🎓 For New Learners

Chapter 17: Boundaries — Drawing Lines

Boundaries separate things that change at different rates or for different reasons. The most important boundary in any system: between business rules and everything else.

What to draw boundaries between:

  • Business logic ↔ Database
  • Business logic ↔ UI / Web framework
  • Business logic ↔ Third-party services
  • Business logic ↔ Device I/O

The key question: what does the business rule need to know?

A business rule that calculates an order discount needs to know: the order, the customer, the discount policy. It does not need to know whether data comes from MySQL or MongoDB, whether it's serving a REST request or a gRPC call, or whether results are rendered in HTML or JSON.

Draw the boundary between what the rule knows and what it doesn't.

The FitNesse Example

Martin uses FitNesse (a wiki/testing tool) to illustrate boundary drawing. Early in the project, MySQL was considered for persistence. But by delaying the database decision and writing a WikiPage interface, the team was able to build the entire application against an in-memory implementation — and delay the database decision for over a year.

When they finally needed to make the decision, they had much more information — and the cost of any choice was minimal because the boundary was clean.

// The boundary: WikiPage is an interface
public interface WikiPage {
WikiPagePath getPagePath();
WikiPage getChildPage(String name);
void addChildPage(String name);
PageData getData();
}

// Early implementation: in-memory (free the team to move fast)
// Later implementation: file system, then database
// Business logic: never changed

Chapter 18: Boundary Anatomy

Boundaries can take three physical forms:

FormCommunicationCost
Monolith (source-level)Function calls (in-memory)Near zero
Local process (same machine, separate process)IPC / socketsLow
Services (separate machines)Network (REST, gRPC, messaging)High

All three forms use the same architectural principle: the dependency rule. Source code dependencies always point inward. Communication may cross the boundary in either direction at runtime — but the source code only knows about the inner layer's interface.

The cost of boundaries matters: every service-level boundary adds latency, failure modes, versioning concerns, and operational complexity. Don't add service boundaries unless the operational benefit justifies the cost.

Chapter 19: Policy and Level

Every piece of software is a policy — a statement of how to transform inputs into outputs. Policies can be sorted by level: the distance from the inputs and outputs of the system.

  • High-level policy: far from I/O, stable, abstract (e.g., business rules)
  • Low-level policy: close to I/O, volatile, concrete (e.g., format a PDF, read from a file)

The dependency rule restated as a level rule: source code dependencies should point toward higher levels (further from I/O, more stable).

Low Level (volatile, close to I/O)
File reader → Data parser → Business rule calculator → Report formatter → File writer
↑ highest level — most stable

Dependencies should point: → toward the calculator ←
The calculator should know nothing about files or formatting.

Chapter 20: Business Rules

Martin distinguishes two types of business rules:

Entities: Enterprise-wide business rules. The critical rules that make or save money, entirely independent of automation. They would exist even if you ran the business on paper.

// Entity: pure business rule — no framework dependencies
public class Loan {
private BigDecimal principal;
private BigDecimal rate;
private int period;

// Critical business rule: makes/saves money for the bank
public BigDecimal makePayment(BigDecimal payment) {
BigDecimal interest = principal.multiply(rate);
BigDecimal principalReduction = payment.subtract(interest);
principal = principal.subtract(principalReduction);
return principalReduction;
}
}

Use Cases: Application-specific business rules. They automate how a human would interact with entities to achieve a goal. They depend on entities but should know nothing about the UI or database.

// Use Case: orchestrates entities for a specific application flow
public class MakeLoanPaymentUseCase {
private final LoanRepository loanRepo;
private final PaymentGateway paymentGateway;

public PaymentResult execute(MakePaymentCommand cmd) {
Loan loan = loanRepo.findById(cmd.loanId());
BigDecimal reduction = loan.makePayment(cmd.amount());
paymentGateway.charge(cmd.accountId(), cmd.amount());
loanRepo.save(loan);
return new PaymentResult(reduction, loan.getRemainingBalance());
}
}

🔬 Senior Deep Dive

The Database Is Not the Center

Many developers think of their Spring application as: "The database is the center. Everything serves the database model."

Martin explicitly inverts this: the database is a detail. It is a low-level policy. The business rules — entities and use cases — are the center.

Wrong:
DB Tables → JPA Entities → Services → Controllers → (User)
(database-centric)

Right:
(User) → Controllers → Use Cases → Domain Entities → [DB Interface]

[JPA Repository]
(domain-centric — dependencies point inward)

In a database-centric Spring application, the database schema drives the domain model (@Entity classes are shaped by tables). In a domain-centric architecture, the domain model drives the database schema. The JPA mapping layer translates between them.

Entities vs. JPA Entities

A critical distinction for Spring developers:

Domain EntityJPA Entity
PurposeCaptures business rulesMaps to database table
DependenciesNoneJPA, Hibernate
Locationdomain moduleinfrastructure module
TestabilityPlain JUnitRequires DataSource
Should they be the same?NoNo

Separating them:

// domain module: pure business rule
public class Order {
private final OrderId id;
private Money total;
public void applyDiscount(Discount d) { ... }
}

// infrastructure module: JPA mapping
@Entity @Table(name = "orders")
public class OrderJpaEntity {
@Id private Long id;
private BigDecimal totalAmount;
private String currency;
}

// infrastructure module: mapper between the two
public class OrderJpaMapper {
public Order toDomain(OrderJpaEntity e) { ... }
public OrderJpaEntity toJpa(Order o) { ... }
}

More code, but: the domain entity is testable without a database, the JPA entity can be tuned for persistence without affecting business rules, and the two can evolve independently.

Level in Spring: Where Should Logic Live?

Applying the "level" concept to a Spring application:

Level 0 (lowest): HTTP request/response parsing → @RestController body parsing
Level 1: Request validation → @Valid, custom validators
Level 2: Use case orchestration → @Service / use case class
Level 3: Domain entity logic → pure Java domain objects ← highest level
Level 2: Persistence → @Repository / JPA
Level 1: External service calls → HTTP clients, SDK calls
Level 0 (lowest): Network I/O → Spring's embedded Tomcat

Business logic at Level 3 (highest) should have zero dependencies on anything at Levels 0–2. It should only depend on its own domain model.


Summary

ChapterKey Concept
17: Drawing LinesBoundaries separate things that change at different rates; draw between business rules and I/O
18: Boundary AnatomyMonolith / local process / service — all use same dependency rule, different costs
19: Policy and LevelHigher level = further from I/O = more stable; dependencies point toward higher levels
20: Business RulesEntities = enterprise rules; Use Cases = application-specific orchestration