Saltar al contenido principal

Diseño de casos de uso y entidades

Una entidad representa un objeto del dominio que tiene identidad propia y que permanece consistente a lo largo del tiempo, incluso si sus atributos cambian. Ejemplo: Un estudiante con ID único, nombre y correo. Aunque cambie el correo, sigue siendo el mismo estudiante.

Un caso de uso representa una acción o comportamiento del sistema que produce valor para un actor (usuario o sistema externo). Define lo que el sistema hace en términos de negocio, sin preocuparse por cómo se implementa. Ejemplo: Registrar un estudiante, asignarlo a un curso, generar certificado, etc.

Relación entre ambos

  • Un caso de uso opera sobre una o más entidades.
  • Las entidades contienen la lógica inmutable del dominio, mientras que los casos de uso representan la interacción dinámica con ese dominio.
  • En Clean Architecture, los casos de uso viven en la capa application, y las entidades en la capa domain.

Buenas prácticas

AspectoRecomendación
EntidadesEvita poner lógica de infraestructura o frameworks
Casos de usoUn solo propósito por clase (SRP)
NombresUsa verbos para casos de uso: Create, List, Update
PruebasPrueba casos de uso con mocks de repositorios
ContratosDefine interfaces (ports) para comunicarse entre capas

Ejemplo técnico en NestJS

Cuando un estudiante se inscribe en un curso, debemos:

  • Verificar que el estudiantes existe
  • Verificar que el curso existe
  • Agregar el ID del curso a la lista de cursos del estudiante
  • Guardar la inscripción

El scaffold del proyecto sería:

src/
├── domain/
│ ├── entities/
│ │ ├── student.entity.ts
│ │ └── course.entity.ts
│ ├── repositories/
│ │ ├── student.repository.ts
│ │ └── course.repository.ts
├── application/
│ └── use-cases/
│ └── enroll-student-in-course.usecase.ts
├── infrastructure/
│ └── db/
│ ├── student.repository.impl.ts
│ └── course.repository.impl.ts
├── modules/
│ └── enrollment/
│ ├── enrollment.module.ts
│ ├── enrollment.controller.ts
│ └── dto/
│ └── enroll.dto.ts
src/domain/entities/student.entity.ts
export class Student {
constructor(
public readonly id: string,
public name: string,
public email: string,
private enrolledCourseIds: string[] = []
) {}

enroll(courseId: string) {
if (!this.enrolledCourseIds.includes(courseId)) {
this.enrolledCourseIds.push(courseId);
}
}

getCourses() {
return this.enrolledCourseIds;
}
}

La clase Student es una entidad del dominio. Representa un estudiante del cual debe mantener sus propios datos (id, name, email, enrolledCourses), además de tener métodos que modifican su estado internamente. Es usada por el caso de uso y persistida mediante StudentRepository

src/domain/entities/course.entity.ts
export class Course {
constructor(
public readonly id: string,
public name: string,
public capacity: number
) {}
}

Al igual que Student, la entidad Course se encarga de representar un curso con sus atributos y modelarlo para inscripción. Es usado por el caso de uso para validar si un curso existe antes de inscribir un estudiante en el mismo.

src/domain/repositories/student.repository.ts
import { Student } from '../entities/student.entity';

export abstract class StudentRepository {
abstract findById(id: string): Promise<Student | null>;
abstract save(student: Student): Promise<void>;
}

StudentRepository es un contracto abstracto que define cómo acceder o modificar estudiantes, sin implementar la lógica concreta. Se encarga de definir métodos como interfaz para infraestructura. Es implementada por InMemoryStudentRepository y consumida por el caso de uso.

src/domain/repositories/course.repository.ts
import { Course } from '../entities/course.entity';

export abstract class CourseRepository {
abstract findById(id: string): Promise<Course | null>;
}

CourseRepository es un contracto abstracto que define cómo acceder a cursos.

Referencias