DDD + Clean Architecture: Stop Putting Business Logic in the Application Layer
Code Example
🔒 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 settersThat’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.

