DEV Community

Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on

Understanding Domain, Anemic Models, Domain Language, Class Invariants, and Layered Architectures

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 an Order).

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

And then a separate service:

public class OrderService {
    public double calculateTotal(Order order) {
        return order.getItems().stream()
            .mapToDouble(OrderItem::getPrice)
            .sum();
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Good:

processSettlement();
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Presentation Layer (UI Layer)
  • Handles input/output, HTTP requests/responses.
  • Example: REST controllers in Spring Boot, React components in a frontend app.
  1. Business Layer (Service Layer)
  • Contains business logic and domain models.
  • Example: Services in Spring Boot (@Service classes), Domain Services in DDD.
  1. 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)
Enter fullscreen mode Exit fullscreen mode

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)