Hi there! I'm Maneshwar. Currently, I’m building a private AI code review tool that runs on your LLM key (OpenAI, Gemini, etc.) with flat, no-seat pricing — designed for small teams. Check it out, if that’s your kind of thing.
Modern software engineering thrives on clarity — clarity in how we model business problems, define responsibilities, and enforce rules in our code.
In this post, we’ll explore five important concepts that often come up in enterprise software discussions: Domain Models, Anemic Models, Domain Language, Class Invariants, and Layered Architectures.
1. Domain Models
A domain model is a conceptual map of your business problem space. It represents the entities, value objects, and relationships that exist in your domain — along with the rules that govern them.
In object-oriented programming (OOP), a domain model is typically implemented using classes and interfaces, where:
- Each class represents a business concept (e.g.,
Customer
,Order
,Invoice
). - Relationships between classes mirror real-world associations (e.g., a
Customer
places anOrder
).
Purpose:
- Keeps the software aligned with the real business problem.
- Bridges the gap between technical and non-technical stakeholders.
- Serves as the blueprint for system behavior.
Example (Java):
public class Order {
private List<OrderItem> items;
private Customer customer;
public Order(Customer customer) {
this.customer = customer;
this.items = new ArrayList<>();
}
public void addItem(OrderItem item) {
items.add(item);
}
public double totalAmount() {
return items.stream().mapToDouble(OrderItem::getPrice).sum();
}
}
Here, the Order
class both holds data and contains behavior (addItem
, totalAmount
) — this is a proper domain model.
2. Anemic Models
An anemic model is a domain model that contains only data (fields and getters/setters) but no real business logic. All the "intelligence" lives in service classes elsewhere.
While it might seem clean at first, this violates encapsulation — behavior is separated from the data it operates on, leading to:
- Bloated service classes
- Higher coupling
- Lower cohesion
Example (Anemic Java model):
public class Order {
private List<OrderItem> items;
private Customer customer;
// only getters & setters
}
And then a separate service:
public class OrderService {
public double calculateTotal(Order order) {
return order.getItems().stream()
.mapToDouble(OrderItem::getPrice)
.sum();
}
}
Why it's an anti-pattern:
You lose the benefits of OOP, making the system feel like procedural programming with objects as mere data containers.
Framework note:
This is common in some Spring Boot + JPA applications when DTOs and Entities are overused without keeping logic inside entities.
3. Domain Language
A domain language (also known as Ubiquitous Language, from DDD) is the shared vocabulary between developers, business analysts, and domain experts.
- It avoids translation errors between "business talk" and "developer talk".
- The words used in code, documentation, and meetings should match.
Example:
If your business calls a customer payment "settlement", your code shouldn’t call it PaymentProcessor
— it should be SettlementProcessor
.
Bad:
processPay();
Good:
processSettlement();
Real-world use:
- DDD encourages modeling code with terms from domain language.
- Frameworks like Axon Framework for Java and Laravel for PHP fit well with this practice.
4. Class Invariants
A class invariant is a rule that must always hold true for an object throughout its lifecycle.
For example, in a BankAccount
class, the balance should never be negative (unless overdrafts are allowed).
Example (Java):
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
if (initialBalance < 0) throw new IllegalArgumentException("Negative balance not allowed");
this.balance = initialBalance;
}
public void withdraw(double amount) {
if (amount > balance) throw new IllegalArgumentException("Insufficient funds");
balance -= amount;
}
}
Key points:
- Invariants are enforced in constructors and mutator methods.
- They protect the object from entering invalid states.
5. Layered Architectures
A layered architecture separates your application into layers, each responsible for a specific concern.
Common 3-layer structure:
- Presentation Layer (UI Layer)
- Handles input/output, HTTP requests/responses.
- Example: REST controllers in Spring Boot, React components in a frontend app.
- Business Layer (Service Layer)
- Contains business logic and domain models.
- Example: Services in Spring Boot (
@Service
classes), Domain Services in DDD.
- Data Access Layer (Persistence Layer)
- Interacts with the database or external storage.
- Example: JPA repositories in Spring Boot, DAOs in Java EE.
Example folder structure (Spring Boot):
src/
├── controller/ (Presentation Layer)
├── service/ (Business Layer)
├── repository/ (Data Access Layer)
└── model/ (Domain Models)
Benefits:
- Modularity
- Easier testing
- Clear separation of responsibilities
Final Thoughts
- Domain Models keep your code close to the business.
- Avoid Anemic Models — keep behavior and data together.
- Use a Domain Language everyone understands.
- Maintain Class Invariants to avoid invalid states.
- Organize with Layered Architectures for maintainability.
If you keep these principles in mind, your code will not only be correct — it will be alive with meaning and structure.
LiveReview helps you get great feedback on your PR/MR in a few minutes.
Saves hours on every PR by giving fast, automated first-pass reviews. Helps both junior/senior engineers to go faster.
If you're tired of waiting for your peer to review your code or are not confident that they'll provide valid feedback, here's LiveReview for you.
Top comments (0)