JackG
JackG

Reputation: 73

Modeling many to many invariants in DDD

I'm currently learning DDD and am having trouble modeling invariants in a many to many relationship

Lets suppose I am attempting to model a school scheduling program, and I have the following invariants:

  1. Students cannot enroll in the same course twice
  2. Students can only enroll in 5 courses
  3. A Course cannot have more students than the maximum capacity of the Course

This represents a many to many relationship from Course to Student, something that I am having a bit of trouble modeling whilst upholding these invariants. Logically I feel as though Course and Student should be their own aggregate roots. Additionally the concept of a Enrollment seems important, and as such I have made it an Entity under the Course aggregate.

class Student {
  public id: number
  public age: number
  public email: string
}


class Course {
  public id: number
  public studentLimit: number
  public studentIds: List<number>

  public enrollStudent(studentId: number) {
    if (studentIds.length >= student || studentIds.find(student => student.id == studentId) {
        throw new BadRequestException()
    }
    student.push(studentId)
  }
}


This is able to satisfy invariants 1 and 3, although in order to satisfy invariant 2 I feel as though we need some way to keep track on the students end how many courses they are enrolled in. If we keep track of this within the Student entity, we will have to ensure that we update both. Therefore, we might want to extract this logic into a "higher" Aggregate, something along the lines of a EnrollmentBoard which has a broader view

class EnrollmentBoard {

    const courseDetails: List<CourseDetails>
    const studentEnrollments: List<StudentEnrollments>

    public enrollStudentInCourse(studentId: number, courseId: number) {
        const course = courseDetails.find(c => c.courseId == courseId)
        const studentEnrollment = studentEnrollments.find(s => s.studentId === studentId)
        if (course.canAddStudent() && student.canEnrollInAnotherCourse()) {
          course.addStudet()
          student.enrollInCourse(courseId)
        }
    }
}

class courseDetails {
  const courseId: number
  const studentLimit: number (moved out of the Course entity into here)
  const studentCount: number (moved out of the Course entity)
  
  public canAddStudent() {
    return studentLimit - studentCount > 0 
  }

  public addStudent() {
    studentCount+=1
  }
}

class studentEnrollments {
  const studentId: number
  const courseIds: List<number>

  public canEnrollInAnotherCourse() {
    return courseIds.length < 5
  }

  public enrollInCourse(courseId: number) {
    courseIds.push(courseId)
  }
}


Now this is able to solve the invariant problem, through the enrollment board, we can manage students and courses together. My only issue is, this aggregate is seemingly going to get quite huge. We are going to potentially keep adding courses to enroll to (which is different than just adding a course). And for an entire university, we will have tens of thousands of students enrolling in courses. My question is very simple

  1. Is there any way to enforce these invariants without loading so much data into memory. Am I missing anything? I was considering the ability to have different enrollment boards for different majors/classifications of students

  2. Is it OK to have essentially two separate models of a Student, one where Student is an aggregate root, and one where StudentEnrollments represents a student in the EnrollmentBoard. These exist because lets say we want to update a student's name, we would simply access the aggregate root version. But if we wanted to update classes, we have to go through the EnrollmentBoard to ensure invariants.

Upvotes: 0

Views: 38

Answers (0)

Related Questions