5.7 KiB
5.7 KiB
DI-007: Contratos y DTOs
Proyecto: Sistema de Registro de Estudiantes Fecha: 2026-01-07
1. DTOs Backend (C#)
Request DTOs (Inputs)
// Students
public record CreateStudentInput(string Name, string Email);
public record UpdateStudentInput(string? Name, string? Email);
// Enrollments
public record EnrollInput(int StudentId, int SubjectId);
Response DTOs
// Student
public record StudentDto(
int Id,
string Name,
string Email,
int TotalCredits,
IEnumerable<EnrollmentDto> Enrollments);
// Subject
public record SubjectDto(
int Id,
string Name,
int Credits,
ProfessorDto Professor);
// Professor
public record ProfessorDto(
int Id,
string Name);
// Enrollment
public record EnrollmentDto(
int Id,
SubjectDto Subject,
DateTime EnrolledAt);
// Available Subject (con disponibilidad)
public record AvailableSubjectDto(
SubjectDto Subject,
bool IsAvailable,
string? UnavailableReason);
// Classmates
public record ClassmateDto(
string SubjectName,
IEnumerable<string> StudentNames);
Payloads (Respuestas con errores)
public record StudentPayload(StudentDto? Student, IEnumerable<string>? Errors);
public record EnrollmentPayload(EnrollmentDto? Enrollment, IEnumerable<string>? Errors);
public record DeletePayload(bool Success, IEnumerable<string>? Errors);
2. DTOs Frontend (TypeScript)
Tipos Generados (graphql-codegen)
// Student
export interface Student {
id: number;
name: string;
email: string;
totalCredits: number;
enrollments: Enrollment[];
}
// Subject
export interface Subject {
id: number;
name: string;
credits: number;
professor: Professor;
}
// Professor
export interface Professor {
id: number;
name: string;
subjects?: Subject[];
}
// Enrollment
export interface Enrollment {
id: number;
subject: Subject;
enrolledAt: string;
}
// Available Subject
export interface AvailableSubject {
subject: Subject;
isAvailable: boolean;
unavailableReason?: string;
}
// Classmate
export interface Classmate {
subjectName: string;
students: string[];
}
Inputs
export interface CreateStudentInput {
name: string;
email: string;
}
export interface UpdateStudentInput {
name?: string;
email?: string;
}
export interface EnrollInput {
studentId: number;
subjectId: number;
}
Payloads
export interface StudentPayload {
student?: Student;
errors?: string[];
}
export interface EnrollmentPayload {
enrollment?: Enrollment;
errors?: string[];
}
export interface DeletePayload {
success: boolean;
errors?: string[];
}
3. Mapeos (Mapster)
public class MappingConfig : IRegister
{
public void Register(TypeAdapterConfig config)
{
config.NewConfig<Student, StudentDto>()
.Map(dest => dest.TotalCredits, src => src.Enrollments.Count * 3);
config.NewConfig<Subject, SubjectDto>();
config.NewConfig<Professor, ProfessorDto>();
config.NewConfig<Enrollment, EnrollmentDto>();
}
}
4. Validadores (FluentValidation)
public class CreateStudentInputValidator : AbstractValidator<CreateStudentInput>
{
public CreateStudentInputValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Nombre requerido")
.MaximumLength(100).WithMessage("Máximo 100 caracteres");
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email requerido")
.EmailAddress().WithMessage("Formato de email inválido")
.MaximumLength(255).WithMessage("Máximo 255 caracteres");
}
}
public class EnrollInputValidator : AbstractValidator<EnrollInput>
{
public EnrollInputValidator()
{
RuleFor(x => x.StudentId)
.GreaterThan(0).WithMessage("StudentId inválido");
RuleFor(x => x.SubjectId)
.GreaterThan(0).WithMessage("SubjectId inválido");
}
}
5. Contratos de Repositorio (Ports)
public interface IStudentRepository
{
Task<Student?> GetByIdAsync(int id, CancellationToken ct = default);
Task<Student?> GetByIdWithEnrollmentsAsync(int id, CancellationToken ct = default);
Task<IEnumerable<Student>> GetAllAsync(CancellationToken ct = default);
Task<bool> EmailExistsAsync(string email, int? excludeId = null, CancellationToken ct = default);
Task AddAsync(Student student, CancellationToken ct = default);
void Update(Student student);
void Delete(Student student);
}
public interface ISubjectRepository
{
Task<Subject?> GetByIdAsync(int id, CancellationToken ct = default);
Task<IEnumerable<Subject>> GetAllAsync(CancellationToken ct = default);
Task<IEnumerable<Subject>> GetByProfessorIdAsync(int professorId, CancellationToken ct = default);
}
public interface IEnrollmentRepository
{
Task<IEnumerable<Enrollment>> GetByStudentIdAsync(int studentId, CancellationToken ct = default);
Task<IEnumerable<Student>> GetClassmatesAsync(int studentId, int subjectId, CancellationToken ct = default);
}
public interface IUnitOfWork
{
Task<int> SaveChangesAsync(CancellationToken ct = default);
}
6. Resumen de Contratos por Operación
| Operación | Input | Output | Errores Posibles |
|---|---|---|---|
| createStudent | CreateStudentInput | StudentPayload | DUPLICATE_EMAIL, VALIDATION |
| updateStudent | UpdateStudentInput | StudentPayload | NOT_FOUND, DUPLICATE_EMAIL |
| deleteStudent | id: Int | DeletePayload | NOT_FOUND |
| enrollStudent | EnrollInput | EnrollmentPayload | MAX_ENROLLMENTS, SAME_PROFESSOR |
| unenrollStudent | enrollmentId: Int | DeletePayload | NOT_FOUND |