-
Notifications
You must be signed in to change notification settings - Fork 87
(DDD) Domain Driven Design basic explanation
- The access is always from inside to outside, never opposite.
Value objects describe some characteristic or attribute but are not fundamentally unique. They are used to measure or describe things in the domain; they must be immutable and based on value rather than identity.
In DDD, a Controller is part of the presentation layer (not a DDD core concept), typically used in web applications to handle HTTP requests and delegate business operations to the application layer (use cases).
Repositories are used to abstract how you retrieve domain entities from persistent storage, allowing your domain logic to be agnostic of the data access layer.
The Domain is the heart of your application, encompassing the business logic, entities, value objects, and domain services. It represents the problem space your application is solving. No direct code example since it's a conceptual area consisting of multiple components (entities, value objects, etc.)
An Aggregate is a cluster of domain objects (entities and value objects) that can be treated as a single unit. An Aggregate has a root entity, known as the Aggregate Root, which controls access to the elements within the Aggregate.
A Use Case represents a specific business goal or functionality. It encapsulates application logic and acts as an intermediary between the presentation and domain layers.
Entities are objects with distinct identities that run through time and different states. Entities are defined not by their attributes but by a thread of continuity and identity.
- Represent domain concepts with a distinct identity.
- Focus on business logic that pertains to the entity itself.
- It should be persistent and ignorant (unaware of the database or storage mechanisms).
- Represent descriptive aspects of the domain with no conceptual identity.
- They are immutable after creation.
- It can be used to validate and encapsulate complex logic for domain attributes.
- Consists of one or more entities considered as one unit for data changes.
- Controlled through a single Aggregate Root, the only point of interaction for external objects.
- Protect business invariants across the entire aggregate.
- Provide an abstraction layer over the data mapping layer to the domain.
- Access the data layer to return domain objects.
- Use cases interact with the domain layer through repositories; direct domain access to repositories is avoided.
- Coordinate high-level business processes.
- Orchestrate the data flow to and from the domain entities and the outside world.
- Use repositories to retrieve domain entities and execute business logic.
- It should not contain business logic that belongs to the domain model.
- Serve as the entry point for interactions from the external world (e.g., user interface, API requests).
- Delegate to application services (use cases) to execute complex business functionalities.
- Should not contain business logic; focus on handling HTTP requests, validating input, and returning responses.
// User.ts - Represents a user in the system.
export class User {
constructor(public id: string, public name: string, public email: string) {}
}
// Email.ts - A simple value object example for user's email.
export class Email {
constructor(public readonly value: string) {
if (!value.includes('@')) {
throw new Error('Invalid email');
}
}
}
// CreateUserUseCase.ts - Handles the business logic of creating a user.
import { UserRepository } from '../infrastructure/UserRepository';
import { User } from '../domain/User';
import { Email } from '../domain/Email';
export class CreateUserUseCase {
constructor(private userRepository: UserRepository) {}
async execute(name: string, email: string): Promise<User> {
const emailVO = new Email(email); // Using Value Object
const user = new User(Date.now().toString(), name, emailVO.value);
await this.userRepository.save(user);
return user;
}
}
// UserRepository.ts - Interface for user repository.
import { User } from '../domain/User';
export interface UserRepository {
save(user: User): Promise<void>;
findById(id: string): Promise<User | null>;
}
// UserController.ts - A controller for handling requests related to users.
import { CreateUserUseCase } from '../application/CreateUserUseCase';
export class UserController {
constructor(private createUserUseCase: CreateUserUseCase) {}
async createUser(req: { name: string; email: string }) {
const { name, email } = req;
try {
const user = await this.createUserUseCase.execute(name, email);
console.log('User created:', user);
// In a real app, this would be an HTTP response
} catch (error) {
console.error('Error creating user:', error);
// Handle error, e.g., return HTTP 400/500
}
}
}