Riguz留言 | 贡献
创建页面,内容为“= Domain-Driven Design (DDD) Guide = This guide provides an overview of key concepts and best practices for implementing Domain-Driven Design (DDD), focusing on the separation of concerns between the domain model, repository, and persistence layer. == Key Principles of DDD == 1. **Focus on the Domain**: - The domain model represents the core business logic and rules. - It should be independent of technical concerns like persistence or frameworks. 2. **…”
 
Riguz留言 | 贡献
 
(未显示同一用户的10个中间版本)
第4行: 第4行:


== Key Principles of DDD ==
== Key Principles of DDD ==
1. **Focus on the Domain**:
* '''Focus on the Domain''':
  - The domain model represents the core business logic and rules.
** The domain model represents the core business logic and rules.
  - It should be independent of technical concerns like persistence or frameworks.
** It should be independent of technical concerns like persistence or frameworks.


2. **Separation of Concerns**:
* '''Separation of Concerns''':
  - Divide responsibilities between the domain layer, application layer, and infrastructure layer.
** Divide responsibilities between the domain layer, application layer, and infrastructure layer.
  - Keep the domain layer free from infrastructure dependencies.
** Keep the domain layer free from infrastructure dependencies.


3. **Ubiquitous Language**:
* '''Ubiquitous Language''':
  - Use a shared language between developers and domain experts to ensure clarity and alignment.
** Use a shared language between developers and domain experts to ensure clarity and alignment.


4. **Repository Pattern**:
* '''Repository Pattern''':
  - Use repositories to abstract persistence logic and provide access to domain objects.
** Use repositories to abstract persistence logic and provide access to domain objects.


== Layers in DDD ==
== Layers in DDD ==
=== 1. Domain Layer ===
<ref> https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice</ref>
 
<syntaxhighlight lang="cpp">
com.example.todo
├── application
│  ├── service
│  └── dto
├── domain
│  ├── model
│  ├── repository
│  └── service
├── infrastructure
│  ├── persistence
│  ├── messaging
│  └── configuration
└── interface
    ├── controller
    └── mapper
</syntaxhighlight>
 
Dependency rule<ref>https://khalilstemmler.com/wiki/dependency-rule/</ref>:
 
[[Image:ddd-dependency-rule.svg|600px]]
 
=== Domain Layer ===
The domain layer contains the core business logic and domain models.
The domain layer contains the core business logic and domain models.


* **Responsibilities**:
* '''Responsibilities''':
  - Encapsulate business rules and behavior.
** Encapsulate business rules and behavior.
  - Represent the core concepts of the domain.
** Represent the core concepts of the domain.


* **Key Components**:
* '''Key Components''':
  - **Entities**: Objects with a unique identity (e.g., `Todo`).
** '''Entities''': Objects with a unique identity (e.g., `Todo`).
  - **Value Objects**: Immutable objects that represent a concept (e.g., `TodoId`).
** '''Value Objects''': Immutable objects that represent a concept (e.g., `TodoId`).
  - **Aggregates**: A cluster of domain objects treated as a single unit.
** '''Aggregates''': A cluster of domain objects treated as a single unit.


* **Best Practices**:
* '''Best Practices''':
  - Avoid adding persistence-related methods (e.g., `save()` or `delete()`) to domain models.
** Avoid adding persistence-related methods (e.g., `save()` or `delete()`) to domain models.
  - Keep domain models focused on business logic.
** Keep domain models focused on business logic.


* **Example**:
* '''Example''':
  <syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  public class Todo {
public class Todo {
      private TodoId id;
    private TodoId id;
      private String title;
    private String title;
      private String description;
    private String description;
      private boolean completed;
    private boolean completed;


      public Todo(TodoId id, String title, String description) {
    public Todo(TodoId id, String title, String description) {
          this.id = id;
        this.id = id;
          this.title = title;
        this.title = title;
          this.description = description;
        this.description = description;
          this.completed = false;
        this.completed = false;
      }
    }


      public void markAsCompleted() {
    public void markAsCompleted() {
          this.completed = true;
        this.completed = true;
      }
    }


      // Getters and setters
    // Getters and setters
  }
}
  </syntaxhighlight>
</syntaxhighlight>


=== 2. Application Layer ===
=== Application Layer ===
The application layer coordinates use cases and orchestrates interactions between the domain and infrastructure layers.
The application layer coordinates use cases and orchestrates interactions between the domain and infrastructure layers.


* **Responsibilities**:
* '''Responsibilities''':
  - Handle application-specific logic (e.g., workflows, use cases).
** Handle application-specific logic (e.g., workflows, use cases).
  - Delegate persistence to repositories.
** Delegate persistence to repositories.


* **Example**:
* '''Example''':
  <syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  public class TodoApplicationService {
public class TodoApplicationService {
      private final TodoRepository todoRepository;
    private final TodoRepository todoRepository;


      public TodoApplicationService(TodoRepository todoRepository) {
    public TodoApplicationService(TodoRepository todoRepository) {
          this.todoRepository = todoRepository;
        this.todoRepository = todoRepository;
      }
    }


      public void createTodo(String title, String description) {
    public void createTodo(String title, String description) {
          Todo todo = new Todo(new TodoId(UUID.randomUUID()), title, description);
        Todo todo = new Todo(new TodoId(UUID.randomUUID()), title, description);
          todoRepository.save(todo);
        todoRepository.save(todo);
      }
    }
  }
}
  </syntaxhighlight>
</syntaxhighlight>


=== 3. Infrastructure Layer ===
=== Infrastructure Layer ===
The infrastructure layer handles technical concerns like persistence, messaging, and external APIs.
The infrastructure layer handles technical concerns like persistence, messaging, and external APIs.


* **Responsibilities**:
* '''Responsibilities''':
  - Implement repository interfaces defined in the domain layer.
** Implement repository interfaces defined in the domain layer.
  - Map between domain models and database entities.
** Map between domain models and database entities.


* **Example**:
* '''Example''':
  <syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  @Repository
@Repository
  public class JpaTodoRepository implements TodoRepository {
public class JpaTodoRepository implements TodoRepository {
      private final SpringDataTodoEntityRepository entityRepository;
    private final SpringDataTodoEntityRepository entityRepository;


      public JpaTodoRepository(SpringDataTodoEntityRepository entityRepository) {
    public JpaTodoRepository(SpringDataTodoEntityRepository entityRepository) {
          this.entityRepository = entityRepository;
        this.entityRepository = entityRepository;
      }
    }


      @Override
    @Override
      public void save(Todo todo) {
    public void save(Todo todo) {
          TodoEntity entity = mapToEntity(todo);
        TodoEntity entity = mapToEntity(todo);
          entityRepository.save(entity);
        entityRepository.save(entity);
      }
    }


      @Override
    @Override
      public Optional<Todo> findById(TodoId id) {
    public Optional<Todo> findById(TodoId id) {
          return entityRepository.findById(id.getValue())
        return entityRepository.findById(id.getValue())
                                .map(this::mapToDomain);
                              .map(this::mapToDomain);
      }
    }


      private TodoEntity mapToEntity(Todo todo) {
    private TodoEntity mapToEntity(Todo todo) {
          TodoEntity entity = new TodoEntity();
        TodoEntity entity = new TodoEntity();
          entity.setId(todo.getId().getValue());
        entity.setId(todo.getId().getValue());
          entity.setTitle(todo.getTitle());
        entity.setTitle(todo.getTitle());
          entity.setDescription(todo.getDescription());
        entity.setDescription(todo.getDescription());
          entity.setCompleted(todo.isCompleted());
        entity.setCompleted(todo.isCompleted());
          return entity;
        return entity;
      }
    }


      private Todo mapToDomain(TodoEntity entity) {
    private Todo mapToDomain(TodoEntity entity) {
          return new Todo(
        return new Todo(
              new TodoId(entity.getId()),
            new TodoId(entity.getId()),
              entity.getTitle(),
            entity.getTitle(),
              entity.getDescription(),
            entity.getDescription(),
              entity.isCompleted()
            entity.isCompleted()
          );
        );
      }
    }
  }
}
  </syntaxhighlight>
</syntaxhighlight>


== Repository Pattern ==
== Repository Pattern ==
The repository pattern abstracts persistence logic and provides access to domain objects.
The repository pattern abstracts persistence logic and provides access to domain objects.


* **Interface in the Domain Layer**:
* '''Interface in the Domain Layer''':
  - Define the contract for persistence using domain models.
** Define the contract for persistence using domain models.
  <syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  public interface TodoRepository {
public interface TodoRepository {
      void save(Todo todo);
    void save(Todo todo);
      Optional<Todo> findById(TodoId id);
    Optional<Todo> findById(TodoId id);
      List<Todo> findAll();
    List<Todo> findAll();
  }
}
  </syntaxhighlight>
</syntaxhighlight>


* **Implementation in the Infrastructure Layer**:
* '''Implementation in the Infrastructure Layer''':
  - Implement the repository interface using database-specific entities and persistence mechanisms.
** Implement the repository interface using database-specific entities and persistence mechanisms.


== Mapping Between Domain Models and Database Entities ==
== Mapping Between Domain Models and Database Entities ==
To keep the domain layer independent of persistence, map between domain models and database entities in the infrastructure layer.
To keep the domain layer independent of persistence, map between domain models and database entities in the infrastructure layer.


* **Domain Model**:
* '''Domain Model''':
  <syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  public class Todo {
public class Todo {
      private TodoId id;
    private TodoId id;
      private String title;
    private String title;
      private String description;
    private String description;
      private boolean completed;
    private boolean completed;


      // Business logic
    // Business logic
  }
}
  </syntaxhighlight>
</syntaxhighlight>


* **Database Entity**:
* '''Database Entity''':
  <syntaxhighlight lang="java">
<syntaxhighlight lang="java">
  @Entity
@Entity
  @Table(name = "todos")
@Table(name = "todos")
  public class TodoEntity {
public class TodoEntity {
      @Id
    @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
    private Long id;


      private String title;
    private String title;
      private String description;
    private String description;
      private boolean completed;
    private boolean completed;


      // Getters and setters
    // Getters and setters
  }
}
  </syntaxhighlight>
</syntaxhighlight>


== Best Practices ==
== Best Practices ==
1. Use **domain models** in the repository interface, not database entities.
* Use '''domain models''' in the repository interface, not database entities.
2. Keep the domain layer free from persistence concerns.
* Keep the domain layer free from persistence concerns.
3. Use mappers (manual or libraries like MapStruct) to convert between domain models and database entities.
* Use mappers (manual or libraries like MapStruct) to convert between domain models and database entities.
4. Avoid adding persistence-related methods (e.g., `save()`) to domain models.
* Avoid adding persistence-related methods (e.g., `save()`) to domain models.
5. Use the **Unit of Work** or **event-driven mechanisms** for automatic persistence if needed.
* Use the '''Unit of Work''' or '''event-driven mechanisms''' for automatic persistence if needed.


== Summary ==
== Summary ==
- The domain layer focuses on business logic and uses repositories to abstract persistence.
* The domain layer focuses on business logic and uses repositories to abstract persistence.
- The infrastructure layer handles persistence and maps between domain models and database entities.
* The infrastructure layer handles persistence and maps between domain models and database entities.
- Keep the domain layer decoupled from technical concerns to ensure clean, maintainable, and testable code.
* Keep the domain layer decoupled from technical concerns to ensure clean, maintainable, and testable code.
 
=FAQ =
== Difference between repository and persistence ==
The repository in the domain layer and the persistence layer in the infrastructure serve different purposes and are part of the separation of concerns in DDD.
 
* The repository in the domain layer is an abstraction (interface) that defines how the domain interacts with persistence.
* The persistence layer in the infrastructure is the implementation of that abstraction, handling the actual database operations.
 
== Should I use domain model or database entity in repository? ==
In the repository interface (defined in the domain layer), you should use domain models because the repository is part of the domain layer and should work with the core business concepts. However, in the repository implementation (in the infrastructure layer), you can map between domain models and database entities to handle persistence.
 
== Should I have a save() method in domain model ==
 
No, the domain model (Todo) should not have a save() method. In Domain-Driven Design (DDD), the domain model is responsible for encapsulating business logic and representing the core concepts of the domain. It should not be concerned with persistence or infrastructure details like saving to a database.
 
== Should I handle messages in repository? ==
 
Message brokers like Kafka, RabbitMQ, or others are not persistence mechanisms in the traditional sense but are instead used for communication and event-driven architectures. Therefore, creating a repository for a message broker is generally not recommended. Instead, you should handle message brokers in the application layer or infrastructure layer.
 
* Do not create a repository for message brokers like Kafka.
* Use the application layer to handle message publishing/consuming.
* Abstract the message broker interaction using interfaces and implement them in the infrastructure layer.
* Leverage domain events to integrate with message brokers in an event-driven architecture.
 
Whether you should create a repository for Redis depends on how you are using Redis in your application.
 
* Use a repository for Redis only if it is a primary data store for domain entities.
* For caching, use decorators or handle it in the infrastructure layer.
* For non-domain purposes (e.g., rate limiting, session storage), handle Redis interactions directly in the infrastructure or application layer.
* Always keep the domain layer free of Redis-specific logic to maintain separation of concerns.
 
== Do I need a service layer? ==
Whether you need a service layer depends on the complexity of your application and how you structure your code. In Domain-Driven Design (DDD), the service layer (often referred to as the application layer) plays an important role in coordinating use cases and workflows.
 
You might skip the service layer if:
 
* Your application is simple
* Your domain logic is self-contained


[[Category:DDD]]
[[Category:DDD]]
[[Category:MicroService]]
[[Category:MicroService]]

2025年8月26日 (二) 12:41的最新版本

Domain-Driven Design (DDD) Guide

This guide provides an overview of key concepts and best practices for implementing Domain-Driven Design (DDD), focusing on the separation of concerns between the domain model, repository, and persistence layer.

Key Principles of DDD

  • Focus on the Domain:
    • The domain model represents the core business logic and rules.
    • It should be independent of technical concerns like persistence or frameworks.
  • Separation of Concerns:
    • Divide responsibilities between the domain layer, application layer, and infrastructure layer.
    • Keep the domain layer free from infrastructure dependencies.
  • Ubiquitous Language:
    • Use a shared language between developers and domain experts to ensure clarity and alignment.
  • Repository Pattern:
    • Use repositories to abstract persistence logic and provide access to domain objects.

Layers in DDD

[1]

com.example.todo
├── application
   ├── service
   └── dto
├── domain
   ├── model
   ├── repository
   └── service
├── infrastructure
   ├── persistence
   ├── messaging
   └── configuration
└── interface
    ├── controller
    └── mapper

Dependency rule[2]:

Domain Layer

The domain layer contains the core business logic and domain models.

  • Responsibilities:
    • Encapsulate business rules and behavior.
    • Represent the core concepts of the domain.
  • Key Components:
    • Entities: Objects with a unique identity (e.g., `Todo`).
    • Value Objects: Immutable objects that represent a concept (e.g., `TodoId`).
    • Aggregates: A cluster of domain objects treated as a single unit.
  • Best Practices:
    • Avoid adding persistence-related methods (e.g., `save()` or `delete()`) to domain models.
    • Keep domain models focused on business logic.
  • Example:
public class Todo {
    private TodoId id;
    private String title;
    private String description;
    private boolean completed;

    public Todo(TodoId id, String title, String description) {
        this.id = id;
        this.title = title;
        this.description = description;
        this.completed = false;
    }

    public void markAsCompleted() {
        this.completed = true;
    }

    // Getters and setters
}

Application Layer

The application layer coordinates use cases and orchestrates interactions between the domain and infrastructure layers.

  • Responsibilities:
    • Handle application-specific logic (e.g., workflows, use cases).
    • Delegate persistence to repositories.
  • Example:
public class TodoApplicationService {
    private final TodoRepository todoRepository;

    public TodoApplicationService(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    public void createTodo(String title, String description) {
        Todo todo = new Todo(new TodoId(UUID.randomUUID()), title, description);
        todoRepository.save(todo);
    }
}

Infrastructure Layer

The infrastructure layer handles technical concerns like persistence, messaging, and external APIs.

  • Responsibilities:
    • Implement repository interfaces defined in the domain layer.
    • Map between domain models and database entities.
  • Example:
@Repository
public class JpaTodoRepository implements TodoRepository {
    private final SpringDataTodoEntityRepository entityRepository;

    public JpaTodoRepository(SpringDataTodoEntityRepository entityRepository) {
        this.entityRepository = entityRepository;
    }

    @Override
    public void save(Todo todo) {
        TodoEntity entity = mapToEntity(todo);
        entityRepository.save(entity);
    }

    @Override
    public Optional<Todo> findById(TodoId id) {
        return entityRepository.findById(id.getValue())
                               .map(this::mapToDomain);
    }

    private TodoEntity mapToEntity(Todo todo) {
        TodoEntity entity = new TodoEntity();
        entity.setId(todo.getId().getValue());
        entity.setTitle(todo.getTitle());
        entity.setDescription(todo.getDescription());
        entity.setCompleted(todo.isCompleted());
        return entity;
    }

    private Todo mapToDomain(TodoEntity entity) {
        return new Todo(
            new TodoId(entity.getId()),
            entity.getTitle(),
            entity.getDescription(),
            entity.isCompleted()
        );
    }
}

Repository Pattern

The repository pattern abstracts persistence logic and provides access to domain objects.

  • Interface in the Domain Layer:
    • Define the contract for persistence using domain models.
public interface TodoRepository {
    void save(Todo todo);
    Optional<Todo> findById(TodoId id);
    List<Todo> findAll();
}
  • Implementation in the Infrastructure Layer:
    • Implement the repository interface using database-specific entities and persistence mechanisms.

Mapping Between Domain Models and Database Entities

To keep the domain layer independent of persistence, map between domain models and database entities in the infrastructure layer.

  • Domain Model:
public class Todo {
    private TodoId id;
    private String title;
    private String description;
    private boolean completed;

    // Business logic
}
  • Database Entity:
@Entity
@Table(name = "todos")
public class TodoEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String description;
    private boolean completed;

    // Getters and setters
}

Best Practices

  • Use domain models in the repository interface, not database entities.
  • Keep the domain layer free from persistence concerns.
  • Use mappers (manual or libraries like MapStruct) to convert between domain models and database entities.
  • Avoid adding persistence-related methods (e.g., `save()`) to domain models.
  • Use the Unit of Work or event-driven mechanisms for automatic persistence if needed.

Summary

  • The domain layer focuses on business logic and uses repositories to abstract persistence.
  • The infrastructure layer handles persistence and maps between domain models and database entities.
  • Keep the domain layer decoupled from technical concerns to ensure clean, maintainable, and testable code.

FAQ

Difference between repository and persistence

The repository in the domain layer and the persistence layer in the infrastructure serve different purposes and are part of the separation of concerns in DDD.

  • The repository in the domain layer is an abstraction (interface) that defines how the domain interacts with persistence.
  • The persistence layer in the infrastructure is the implementation of that abstraction, handling the actual database operations.

Should I use domain model or database entity in repository?

In the repository interface (defined in the domain layer), you should use domain models because the repository is part of the domain layer and should work with the core business concepts. However, in the repository implementation (in the infrastructure layer), you can map between domain models and database entities to handle persistence.

Should I have a save() method in domain model

No, the domain model (Todo) should not have a save() method. In Domain-Driven Design (DDD), the domain model is responsible for encapsulating business logic and representing the core concepts of the domain. It should not be concerned with persistence or infrastructure details like saving to a database.

Should I handle messages in repository?

Message brokers like Kafka, RabbitMQ, or others are not persistence mechanisms in the traditional sense but are instead used for communication and event-driven architectures. Therefore, creating a repository for a message broker is generally not recommended. Instead, you should handle message brokers in the application layer or infrastructure layer.

  • Do not create a repository for message brokers like Kafka.
  • Use the application layer to handle message publishing/consuming.
  • Abstract the message broker interaction using interfaces and implement them in the infrastructure layer.
  • Leverage domain events to integrate with message brokers in an event-driven architecture.

Whether you should create a repository for Redis depends on how you are using Redis in your application.

  • Use a repository for Redis only if it is a primary data store for domain entities.
  • For caching, use decorators or handle it in the infrastructure layer.
  • For non-domain purposes (e.g., rate limiting, session storage), handle Redis interactions directly in the infrastructure or application layer.
  • Always keep the domain layer free of Redis-specific logic to maintain separation of concerns.

Do I need a service layer?

Whether you need a service layer depends on the complexity of your application and how you structure your code. In Domain-Driven Design (DDD), the service layer (often referred to as the application layer) plays an important role in coordinating use cases and workflows.

You might skip the service layer if:

  • Your application is simple
  • Your domain logic is self-contained