Skip to content

(DDD) Domain Driven Design basic explanation

Pedro Nauck edited this page Mar 12, 2024 · 2 revisions

CleanShot 2024-02-27 at 18 04 11@2x

  • The access is always from inside to outside, never opposite.

Definitions

1. Value Objects

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.

2. Controller

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).

3. Repository

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.

4. Domain

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.)

5. Aggregate

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.

6. Use Case

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.

8. Entity

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.

Rules and Responsibilities

Entities

  • 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).

Value Objects

  • 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.

Aggregates

  • 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.

Repositories

  • 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.

Use Cases

  • 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.

Controllers (Presentation Layer)

  • 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.

Example Implementation

Business Layer

Entity

// User.ts - Represents a user in the system.
export class User {
  constructor(public id: string, public name: string, public email: string) {}
}

Value Object

// 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');
    }
  }
}

Application Layer

Use Case

// 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;
  }
}

Infrastructure Layer

Repository

// 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>;
}

Presentation Layer

Controller

// 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
    }
  }
}