Sitemap
kumar ash

kumar ash

Decoupling Decision from Orchestration: A Practical Guide to Entities vs. Use Cases in Clean Architecture

5 min readAug 8, 2026

--

When developers first read Robert C. Martin’s (Uncle Bob) Clean Architecture, the concentric circles diagram looks crisp and intuitive. The inner circles represent business intent, while outer circles represent technical mechanisms.

┌─────────────────────────────────────────────────────────────┐
│ Frameworks & Drivers (Web, DB, Devices) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Interface Adapters (Controllers, Gateways, Presenters)│ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Use Cases (Application Business Rules) │ │ │
│ │ │ ┌───────────────────────────────────────────┐ │ │ │
│ │ │ │ Entities (Enterprise Business Rules) │ │ │ │
│ │ │ └───────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

However, when you start writing code, the boundary between Entities (Enterprise Business Rules) and Use Cases (Application Business Rules) begins to blur.

Should validation live in the Entity or the Use Case?

Does checking if an order can be canceled belong to a Domain Service or an Application Interactor?

How do we enforce rules without loading massive aggregate graphs into memory?

If you struggle with these questions, you are not alone. Misplacing logic between these two inner layers leads to either Anemic Domain Models (where Entities are dumb data buckets and Use Cases contain all the business rules) or Bloated Aggregates (where Entities try to do database orchestration).

To build clean, scalable systems, you need three core principles to decide what goes where.

Principle 1: The Pen & Paper Test (Enterprise vs. Application Rules)

To classify a business rule, ask yourself one question:

“If we threw away our software entirely and operated using pen, paper, and physical filing cabinets, would this rule still exist?”

Enterprise Business Rules (Entities)

If the answer is YES, it is an Enterprise Business Rule. These rules represent core business policies, mathematical invariants, domain definitions, and economic realities. They are true regardless of whether the business is run by an AI agent, a web application, or a room full of human clerks.

  • “An account balance cannot drop below zero.” (Inherent financial rule)
  • “A recurring action advances its due date upon completion.” (The definition of “recurring”)
  • “An order cannot be fulfilled if it has zero line items.” (Core commerce invariant)

These rules belong strictly inside the Entities Layer (Domain).

Application Business Rules (Use Cases)

If the answer is NO, it is an Application Business Rule. These rules exist solely because of the software application. They govern user workflows, delivery channels, notifications, UI safeguards, and automated integrations.

  • “Send a PDF receipt via email after checkout.” (Notification workflow)
  • “Lock the login UI for 15 minutes after three failed attempts.” (Software security mechanism)
  • “Auto-archive topics that haven’t been updated in 30 days.” (Automated background policy)

These rules belong inside the Use Cases Layer (Application).

Principle 2: Decision vs. Orchestration

A primary source of confusion is failing to separate making a decision from gathering the facts needed to make that decision.

  • The Rule (Decision): Belongs in the Domain layer per Principle 1.
  • The Coordination (Orchestration): Always belongs in the Use Case layer.

Example: Closing a Topic with Pending Actions

Imagine a project management tool with a business rule: “A Topic with pending actions cannot be closed.”

Get kumar ash’s stories in your inbox

Join Medium for free to get updates from this writer.

Running the Pen & Paper test: Would a clerk stamp a case file “CLOSED” if there were incomplete task slips pinned inside? No. It is an Enterprise Rule.

However, in a high-scale application with millions of notes and actions, a Topic entity shouldn't hold a massive array of ActionItem objects in memory. How do we keep the entity lightweight while letting it enforce the rule?

We pass pre-computed facts into the Entity method.

1. The Entity Owns the Decision

# domain/entities/topic.py
class TopicCloseBlockedException(Exception):
pass

class Topic:
def __init__(self, id: str, status: str):
self.id = id
self.status = status
def close(self, pending_action_count: int) -> None:
"""Enterprise Rule: Topic decides if it can close based on a given fact."""
if pending_action_count > 0:
raise TopicCloseBlockedException(
f"Cannot close Topic {self.id}: {pending_action_count} actions pending."
)
self.status = "CLOSED"

2. The Use Case Owns the Orchestration

# use_cases/close_topic.py
class CloseTopicUseCase:
def __init__(self, topic_repo: TopicRepository, action_repo: ActionRepository):
self.topic_repo = topic_repo
self.action_repo = action_repo

def execute(self, topic_id: str) -> None:
# 1. Fetch data from infrastructure ports (Orchestration)
topic = self.topic_repo.get_by_id(topic_id)
pending_count = self.action_repo.count_pending_for_topic(topic_id)
# 2. Delegate decision logic to the Domain Entity
topic.close(pending_action_count=pending_count)
# 3. Persist the state change (Orchestration)
self.topic_repo.save(topic)

The Use Case does not decide whether a topic can close; it orchestrates the query to get the pending_action_count (a lightweight SQL COUNT()), feeds that fact to topic.close(), and saves the updated entity.

The entity makes the decision; the use case coordinates the facts.

Principle 3: Entity Method vs. Domain Service

Once you identify that a decision belongs in the Domain layer, where inside the Domain should it live? On an Entity Method or in a Domain Service?

Does the decision rule require logic from 
MULTIPLE aggregates collaborating, where
NEITHER aggregate alone owns the rule?
│
┌────────────────┴────────────────┐
YES NO
│ │
┌──────────┴──────────┐ ┌──────────┴──────────┐
│ Domain Service │ │ Entity Method │
└─────────────────────┘ └─────────────────────┘

1. Entity Methods (Single-Aggregate Rules)

Use an Entity Method when the rule is about that object’s own state transitions — things the entity evaluates about itself (or using a fact provided to it).

# domain/entities/action_item.py
class ActionItem:
def reschedule(self, new_due_date: datetime, current_time: datetime) -> None:
"""Entity Rule: Cannot reschedule an action to the past."""
if new_due_date < current_time:
raise InvalidDueDateException("Cannot reschedule to a past date.")
self.due_date = new_due_date

def would_lose_context(self, remaining_note_count: int) -> bool:
"""Entity Rule: Action decides if deleting a linked note leaves it orphaned."""
return self.is_active and remaining_note_count == 0

2. Domain Services (Cross-Aggregate Collaboration)

Use a Domain Service only when a rule genuinely involves logic from multiple distinct aggregate roots collaborating, such that neither aggregate alone can make the decision.

Example Domain Service Trigger

Suppose we have a rule: “A Note can only be attached to an ActionItem if both the Note and the ActionItem belong to the same parent Topic.”

Neither Note nor ActionItem owns this cross-checking rule independently. Both aggregates participate equally in the validation:

# domain/services/topic_alignment_service.py
class TopicAlignmentService:
@staticmethod
def validate_link_eligibility(note: Note, action: ActionItem) -> None:
"""Domain Service: Rule requires double-dispatch logic across two aggregates."""
if note.topic_id != action.topic_id:
raise TopicMismatchException("Cannot link Note and Action from different Topics.")

Warning against “Anemic Service Middlemen”: Do not create a Domain Service if it simply extracts data from Entity A and calls a method on Entity B. If an entity can make the decision independently given a fact, pass the fact directly to the Entity method.

Summary

When designing domain models under Clean Architecture, keep your layer boundaries sharp:

  • Test with Pen & Paper: If a rule holds true without software, place it in the Domain Layer (Entities). If it exists for software workflows, put it in the Application Layer (Use Cases).
  • Decouple Decision from Orchestration: Let Use Cases perform database queries and fetch pre-computed facts. Pass those facts into Entity methods so Entities enforce rules without loading heavy aggregate graphs.
  • Keep Domain Services Rare: Default to Entity Methods. Reserve Domain Services for complex invariants where multiple aggregates must collaborate to make a decision.

--

--