5.3 KiB
5.3 KiB
DI-002: Modelo de Dominio
Proyecto: Sistema de Registro de Estudiantes Fecha: 2026-01-07
1. Diagrama de Entidades
┌─────────────────┐ ┌─────────────────┐
│ PROFESSOR │ │ STUDENT │
├─────────────────┤ ├─────────────────┤
│ Id: int (PK) │ │ Id: int (PK) │
│ Name: string │ │ Name: string │
└────────┬────────┘ │ Email: Email │
│ │ RowVersion │
│ 1:2 └────────┬────────┘
▼ │
┌─────────────────┐ │ 0..3
│ SUBJECT │ ▼
├─────────────────┤ ┌─────────────────┐
│ Id: int (PK) │◄──────│ ENROLLMENT │
│ Name: string │ 1:N ├─────────────────┤
│ Credits: 3 │ │ Id: int (PK) │
│ ProfessorId: FK │ │ StudentId: FK │
└─────────────────┘ │ SubjectId: FK │
│ EnrolledAt │
└─────────────────┘
2. Entidades
Student (Aggregate Root)
public class Student
{
public int Id { get; private set; }
public string Name { get; private set; }
public Email Email { get; private set; }
public byte[] RowVersion { get; private set; }
private readonly List<Enrollment> _enrollments = new();
public IReadOnlyCollection<Enrollment> Enrollments => _enrollments;
public int TotalCredits => _enrollments.Count * 3;
public void Enroll(Subject subject, IEnrollmentPolicy policy)
{
policy.Validate(this, subject);
_enrollments.Add(new Enrollment(this, subject));
}
public void Unenroll(int subjectId)
{
var enrollment = _enrollments.FirstOrDefault(e => e.SubjectId == subjectId);
if (enrollment != null) _enrollments.Remove(enrollment);
}
}
Subject
public class Subject
{
public int Id { get; private set; }
public string Name { get; private set; }
public int Credits { get; } = 3; // Constante: RN-002
public int ProfessorId { get; private set; }
public Professor Professor { get; private set; }
}
Professor
public class Professor
{
public int Id { get; private set; }
public string Name { get; private set; }
private readonly List<Subject> _subjects = new();
public IReadOnlyCollection<Subject> Subjects => _subjects;
}
Enrollment
public class Enrollment
{
public int Id { get; private set; }
public int StudentId { get; private set; }
public int SubjectId { get; private set; }
public DateTime EnrolledAt { get; private set; }
public Student Student { get; private set; }
public Subject Subject { get; private set; }
}
3. Value Objects
public record Email
{
public string Value { get; }
private Email(string value) => Value = value;
public static Result<Email> Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
return Result.Failure<Email>("Email requerido");
if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
return Result.Failure<Email>("Formato de email inválido");
return Result.Success(new Email(email.ToLowerInvariant()));
}
}
4. Domain Service
EnrollmentDomainService (Implementa RN-003 y RN-005)
public class EnrollmentDomainService : IEnrollmentPolicy
{
public void Validate(Student student, Subject newSubject)
{
// RN-003: Máximo 3 materias
if (student.Enrollments.Count >= 3)
throw new MaxEnrollmentsExceededException(student.Id);
// RN-005: No repetir profesor
var professorIds = student.Enrollments
.Select(e => e.Subject.ProfessorId)
.ToHashSet();
if (professorIds.Contains(newSubject.ProfessorId))
throw new SameProfessorConstraintException(
student.Id,
newSubject.ProfessorId);
}
}
5. Excepciones de Dominio
public class MaxEnrollmentsExceededException : DomainException
{
public MaxEnrollmentsExceededException(int studentId)
: base($"Estudiante {studentId} ya tiene 3 materias inscritas") { }
}
public class SameProfessorConstraintException : DomainException
{
public SameProfessorConstraintException(int studentId, int professorId)
: base($"Estudiante {studentId} ya tiene materia con profesor {professorId}") { }
}
6. Invariantes del Dominio
| Invariante | Entidad | Validación |
|---|---|---|
| Email único | Student | DB Constraint + Validator |
| Email válido | Student | Value Object |
| Max 3 inscripciones | Student | Domain Service |
| No repetir profesor | Student | Domain Service |
| 3 créditos/materia | Subject | Constante |
| 2 materias/profesor | Professor | Seed Data |