What Real Problem Does DDD Solve? Cutting Through the Confusing Concepts
The article explains why traditional three‑layer architectures let business logic bloat inside services, demonstrates the pitfalls of anemic domain models with a 7,000‑line SalaryService example, and shows how DDD’s rich domain model restores rule ownership and maintainability.
First Diagnose the Disease, Then Prescribe the Cure
Most projects start with a clean three‑layer architecture (Controller → Service → DAO) that is easy for newcomers. As business complexity grows, all logic piles into the Service layer, creating a maintenance nightmare. Controller → Service → Dao Consider a payroll system where SalaryService initially contains straightforward calculations. Over time, product, HR, and finance requests force repeated modifications, inflating the method to 600 lines after six months.
@Service
public class SalaryService {
public BigDecimal calculate(Long employeeId, YearMonth month) {
// ... many DAO calls and business rules ...
}
}Anemic Model: Business Logic Homeless
The entities Employee, AttendanceRecord, and Performance are pure data containers with only getters/setters. All decisions are outsourced to the Service, which Martin Fowler labeled the "Anemic Domain Model" anti‑pattern in 2003.
public class Employee {
private Long id;
private String name;
private BigDecimal baseSalary;
private String department;
// getters & setters only
}Consequences: business rules are scattered across the codebase, hard to locate, and risky to change because they lack a clear home.
Rich Model: Return Behavior to Objects
DDD’s answer is simple: let each object own its behavior. The Employee class now knows how to calculate its bonus, and the PerformanceLevel enum encapsulates the varying bonus formulas.
public class Employee {
private EmployeeId id;
private String name;
private Money baseSalary;
private PerformanceLevel performanceLevel;
/** Bonus calculation lives inside the domain object */
public Money calculateBonus() {
return performanceLevel.calculateBonus(this.baseSalary);
}
public void promote(PerformanceLevel newLevel) {
if (newLevel.isLowerThan(this.performanceLevel)) {
throw new DomainException("晋升等级不得低于当前等级");
}
this.performanceLevel = newLevel;
DomainEventPublisher.publish(new EmployeePromotedEvent(this.id, newLevel));
}
}
public enum PerformanceLevel {
A {
@Override
public Money calculateBonus(Money baseSalary) {
return baseSalary.multiply(new BigDecimal("0.2"));
}
},
B {
@Override
public Money calculateBonus(Money baseSalary) {
return baseSalary.multiply(new BigDecimal("0.1"));
}
},
C {
@Override
public Money calculateBonus(Money baseSalary) {
return Money.ZERO;
}
};
public abstract Money calculateBonus(Money baseSalary);
}Now the rule "Performance A bonus is 20%" resides in PerformanceLevel.A. Changing the percentage requires editing a single, obvious place.
Value Object Example: Money
Instead of using raw BigDecimal, DDD introduces a Money value object that carries currency, precision, and domain‑specific operations, preventing bugs such as adding amounts of different currencies.
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money multiply(BigDecimal factor) {
return new Money(this.amount.multiply(factor), this.currency);
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new DomainException("不同币种不能直接相加");
}
return new Money(this.amount.add(other.amount), this.currency);
}
// immutable, no setters
}Value objects are a cornerstone of DDD’s tactical design.
When DDD Helps and When It Doesn’t
DDD shines in systems with high business complexity and long lifecycles, where rules change frequently, multiple teams collaborate, and the project is expected to be maintained for years. The upfront modeling cost is higher, but benefits accrue over time.
Frequent, complex rule changes (e.g., payroll, tax compliance)
Large teams needing clear boundaries
Projects with >3‑year maintenance horizon
Micro‑service decomposition that requires domain‑driven boundaries
DDD is less suitable for simple CRUD admin panels, short‑lived marketing pages, tiny teams (<3 people), or early prototypes where speed outweighs modeling.
Key Takeaways
Business logic must have a home – avoid turning Service into a garbage dump.
Code should speak the domain language – class and method names match business terminology.
Defining clear bounded contexts matters more than low‑level technical details.
Next article will dive into "Ubiquitous Language": how to embed the shared domain vocabulary directly into code, making naming the best documentation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tinker Programmer
Solving problems with code, sharing practical tech insights, and leveling up together!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
