Reputation: 73
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:
Students
cannot enroll in the same course twiceStudents
can only enroll in 5 coursesCourse
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
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
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