Optivem Journal

Optivem Journal

Clean Architecture

DDD + Clean Architecture: Stop Putting Business Logic in the Application Layer

Your Clean Architecture isn’t clean if the domain is anemic

Valentina Jemuović's avatar
Valentina Jemuović
Feb 27, 2026
∙ Paid

When people try Clean Architecture… they often end up with bloated application layers and empty domain layers.

Application Layer:
   - Validate input
   - Load entities
   - Perform business logic
   - Call repositories
   - Call gateways
   - Publish events

Domain Layer:
   - Entity with getters and setters

That’s an anemic domain, not a rich domain.

The domain doesn’t actually do anything. All the rules are in the application layer.

In Clean Architecture, we’re supposed to have a rich domain, which fits in with DDD.

❌Empty Domain

Many developers are used to CRUD. They think of entities as just data structures

You’re used to CRUD. You think of entities as just data structure, with public getters and setters:

class Order {
    private int id;
    private List<Item> items;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }
}


class Item {
    private String sku;
    private int quantity;
    private boolean expired;

    public String getSku() {
        return sku;
    }

    public void setSku(String sku) {
        this.sku = sku;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public boolean isExpired() {
        return expired;
    }

    public void setExpired(boolean expired) {
        this.expired = expired;
    }
}

Looks fine… but the business rules live elsewhere (application layer).

Your application service does this:

public void addItemToOrder(Order order, Item item) {
    if (item.isExpired()) {
        throw new ExpiredException();
    }

    List<Item> items = order.getItems();
    if (items == null) {
        items = new ArrayList<>();
    }

    if (items.size() > 10) {
        throw new LimitExceededException();
    }

    items.add(item);
    order.setItems(items);
    orderRepository.save(order);
}

All the rules (expiration check, limit check) live in the application layer.

The entity is just a data container.

Basically, you’ve moved all the complexity into a layer that’s supposed to orchestrate, not decide.

❌Bloated Application Layer

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2026 Valentina Jemuović, Optivem · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture