# How Dify Implements Domain-Driven Design: Layered Architecture and Code Patterns

> Discover how Dify implements Domain-Driven Design using a layered architecture. Explore entity isolation, service orchestration, and strict dependency rules for cleaner code.

- Repository: [LangGenius/dify](https://github.com/langgenius/dify)
- Tags: architecture
- Published: 2026-02-25

---

**Dify implements Domain-Driven Design through a strict layered architecture that isolates pure domain models in `api/core/entities/`, orchestrates business logic via application services in `api/services/`, and enforces dependency direction through import-linter rules and repository protocols.**

Dify, the open-source LLM application development platform by LangGenius, demonstrates practical Domain-Driven Design (DDD) principles through its Python codebase. The repository separates business logic from framework concerns using a clean architecture that isolates pure domain models, orchestrates use cases through service layers, and implements dependency inversion via repository protocols. This article examines the specific file structures, import rules, and code patterns that enforce these DDD boundaries.

## DDD Layered Architecture in Dify

Dify organizes its codebase into distinct layers that mirror classic DDD tactical patterns, ensuring that business logic remains independent of frameworks and infrastructure concerns.

### Domain Layer: Pure Business Logic

The **domain layer** resides in `api/core/workflow/entities/` and `api/core/entities/`, containing pure business objects that model workflows, nodes, variables, and providers. These entities have **no knowledge of persistence, Flask, or any framework**.

In [`api/core/workflow/entities/workflow_execution.py`](https://github.com/langgenius/dify/blob/main/api/core/workflow/entities/workflow_execution.py), the `WorkflowExecution` class is implemented as a Pydantic `BaseModel` that only describes the state of a workflow run. It includes methods like `new()` for entity creation without any database or HTTP dependencies, keeping the domain logic clean and highly testable.

### Application Layer: Service Orchestration

The **application layer** lives in `api/services/` (e.g., [`api/services/workflow_service.py`](https://github.com/langgenius/dify/blob/main/api/services/workflow_service.py)) and contains use-case-oriented services that orchestrate domain objects. The `WorkflowService` validates workflows, loads variables, and coordinates execution through `WorkflowEntry` without directly touching HTTP handlers or database connections.

Methods such as `sync_draft_workflow`, `publish_workflow`, and `run_draft_workflow_node` receive plain data objects and return domain entities, ensuring that business rules execute within the service layer rather than in controllers.

### Infrastructure and Ports

Dify implements the **ports and adapters** pattern through repository **protocols** defined in `api/core/workflow/repositories/`. The `WorkflowExecutionRepository` protocol acts as a port that the service layer depends on, while concrete implementations reside in `api/core/db/` or `api/extensions/`.

The **infrastructure layer** includes `api/controllers/` for Flask request handling, `api/core/db/` for SQLAlchemy persistence, and `api/extensions/` for external integrations. These components depend on the application layer but never on the domain layer directly, maintaining proper dependency direction.

## Enforcing DDD Boundaries

Dify employs specific technical mechanisms to prevent architectural violations and keep the domain isolated.

### Import Architecture Rules

The codebase enforces a strict import hierarchy through linter rules documented in [`api/core/workflow/README.md`](https://github.com/langgenius/dify/blob/main/api/core/workflow/README.md). The dependency direction flows as: `graph_engine → graph_events → graph → nodes → node_events → entities`.

Domain entities never import from infrastructure layers, ensuring that `api/core/workflow/entities/` remains free of Flask or SQLAlchemy imports. This prevents accidental coupling between business logic and technical implementation details.

### Repository Pattern with Protocols

Instead of concrete database classes, Dify defines **abstract contracts** using Python's `Protocol` class. The `WorkflowExecutionRepository` in [`api/core/workflow/repositories/workflow_execution_repository.py`](https://github.com/langgenius/dify/blob/main/api/core/workflow/repositories/workflow_execution_repository.py) declares methods like `save()` and `get_by_id()` without implementation details.

This decouples the domain from SQLAlchemy-specific code, allowing services to depend on abstractions while concrete repository implementations handle the actual persistence logic.

### Domain-Only Models

Entities such as `Agent`, `ProviderConfig`, and `ModelSettings` are simple Pydantic models with type hints and validation logic but no ORM code. Validation rules—for example, credential policy compliance—live inside domain-level helper methods rather than in controllers or database layers.

These models reside exclusively in the `entities/` packages, ensuring that configuration and validation logic remains part of the domain rather than infrastructure.

### Cross-Cutting Concerns

Dify implements a **layer system** described in [`api/core/workflow/README.md`](https://github.com/langgenius/dify/blob/main/api/core/workflow/README.md) that provides injection points for debugging, execution limits, and event publishing. This middleware approach wraps the core graph engine without contaminating domain logic, handling concerns like logging and performance monitoring through the layer system rather than embedding them in entity classes.

## Code Implementation Examples

### Creating Domain Entities

Domain entities provide factory methods that construct instances without framework dependencies:

```python
from core.workflow.entities import WorkflowExecution
from core.workflow.enums import WorkflowType
from libs.datetime_utils import naive_utc_now

execution = WorkflowExecution.new(
    id_="exec-001",
    workflow_id="wf-123",
    workflow_type=WorkflowType.CHAT,
    workflow_version="v1.0",
    graph={"nodes": [], "edges": []},
    inputs={"question": "What is DDD?"},
    started_at=naive_utc_now(),
)

```

The `new` classmethod lives entirely within the domain model in [`api/core/workflow/entities/workflow_execution.py`](https://github.com/langgenius/dify/blob/main/api/core/workflow/entities/workflow_execution.py), free from any database or web framework code.

### Defining Repository Ports

Repository interfaces declare contracts without implementation:

```python
from typing import Protocol
from core.workflow.entities import WorkflowExecution

class WorkflowExecutionRepository(Protocol):
    """Port used by services to persist WorkflowExecution."""
    def save(self, execution: WorkflowExecution) -> None: ...
    def get_by_id(self, execution_id: str) -> WorkflowExecution | None: ...

```

Only the contract is declared in [`api/core/workflow/repositories/workflow_execution_repository.py`](https://github.com/langgenius/dify/blob/main/api/core/workflow/repositories/workflow_execution_repository.py); concrete SQLAlchemy implementations exist elsewhere in the infrastructure layer.

### Service Layer Coordination

The service layer orchestrates domain logic and persistence:

```python
from services.workflow_service import WorkflowService
from api.core.db.session_factory import session_factory

# inject concrete repository implementation via factory

service = WorkflowService(session_maker=session_factory)

# business use-case: publish a draft workflow

published = service.publish_workflow(
    session=db.session,
    app_model=my_app,
    account=current_user,
    marked_name="Release 1.0",
)

```

`WorkflowService` coordinates domain validation, repository persistence, and side effects while remaining agnostic to HTTP transport details.

### Controller Delegation

Flask controllers act as thin adapters that translate HTTP requests to service calls:

```python
from flask import Blueprint, request, jsonify
from services.workflow_service import WorkflowService

bp = Blueprint("workflow", __name__)

@bp.post("/apps/<app_id>/workflows/publish")
def publish_workflow(app_id: str):
    data = request.json
    service = WorkflowService()
    workflow = service.publish_workflow(
        session=db.session,
        app_model=get_app(app_id),
        account=get_current_account(),
        marked_name=data.get("name", ""),
        marked_comment=data.get("comment", ""),
    )
    return jsonify(workflow.dict())

```

The controller simply translates HTTP requests to service method calls; all business logic remains encapsulated in the service layer.

## Summary

- **Domain Isolation**: Business logic lives in `api/core/entities/` as pure Pydantic models without framework dependencies.
- **Dependency Direction**: Import-linter rules enforce that domain layers never depend on infrastructure, following the hierarchy ending at `entities`.
- **Repository Abstraction**: Protocol classes in `api/core/workflow/repositories/` decouple services from SQLAlchemy implementations.
- **Service Orchestration**: The `api/services/` layer coordinates use cases without direct HTTP or database interaction.
- **Framework Decoupling**: Flask controllers in `api/controllers/` act as thin adapters, keeping business rules testable and independent of web transport.

## Frequently Asked Questions

### How does Dify prevent domain models from depending on infrastructure?

Dify enforces strict import architecture rules documented in [`api/core/workflow/README.md`](https://github.com/langgenius/dify/blob/main/api/core/workflow/README.md) that prohibit domain entities in `api/core/entities/` from importing Flask, SQLAlchemy, or other infrastructure concerns. The linter validates that dependencies flow only in one direction toward the domain layer, ensuring entities remain pure Pydantic models.

### What pattern does Dify use for database access in DDD?

Dify implements the **repository pattern** using Python's `Protocol` classes. Services depend on abstract repository interfaces (e.g., `WorkflowExecutionRepository`) declared in `api/core/workflow/repositories/`, while concrete SQLAlchemy implementations live in `api/core/db/`. This decouples domain logic from persistence technology.

### How are business rules enforced in Dify's architecture?

Business rules execute within the **application service layer** in `api/services/`. The `WorkflowService` validates workflows, checks credentials through domain methods, and coordinates execution before calling repositories. This keeps business logic out of controllers and ensures rules are tested independently of HTTP transport.

### Where does input validation occur in Dify's DDD implementation?

Input validation occurs at multiple levels: Pydantic models in the domain layer enforce type constraints and business invariants, while domain-specific helpers validate credential policies and configuration rules. The service layer performs use-case validation before persisting changes, ensuring all business rules execute within the domain or application layers rather than in infrastructure controllers.