Optivem Journal

Optivem Journal

Clean Architecture

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

Code Example

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

🔒 Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders & Senior Software Developers apply TDD in Legacy Code.


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

If you’re used to CRUD, you probably see entities as simple data structures 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