0

New to DDD I have a simple case a I would like to model using DDD approach

2 entities Student and Course

Relevant property for Student are StudentId and Budget

Relevant property for Course are CourseId and Price

Student and Course are entities that can exists on its own and have their own life cycle

Business requirements:

1) Student can book one course (CourseId is fk for Student table)

2) Student can book the course only if the user's budget is higher or equal to the course price.

3) Changes of course price doesn’t affect the students have already booked the course.

4) When the student book the course the his budget remains unchanged (maybe changes later at the end of the course)

5) Student budget can be modified setting a different amount but new amount have to be higher or equal to the price of the course the user booked. Setting a lower amount should throw a runtime error.

What the way to model this simple case following domain driven design? Where to enforce the two busines rules (points 2 and 5)?

As a Course can exist without a Student I can’t define the aggregate where Student is the root entity and Course its child entity. Can I?

But at the same time the business rule defined at point 5 seems to me be an invariants. Is it?

So where and how to apply this rules?

I tried a service approach, can work for the first simple rule (point 2) but fail for the rule described at point 5

var student = studentRepository.Get(srtudentId);
var course = courseRepository.Get(courseId)

var studentService = new StudentService();

studentService.SubScribeStudentToCourse(student, course);

studentRepository.Update(student);


StudentService.ChangeStudentBudget(student, 100000);

studentRepository.Update(student);  

when I update the student with the new budget someone else can change the course price making the student budget inconsistent

public class StudentService
{
    SubScribeStudentToCourse(Studen student, Course course)
    {
        if (studentt.Budget >= course.Price)
        {
            student.CourseId = course.CourseId
        }
    }

    ChangeStudentBudget( Student student, decimal budgetAmount)
    {
        if (student.CourseId != null)
        {
            var studentCourse = courseRepository.Get(student.CourseId);
            if ( studentCourse.Price <= budgetAmount)
            {
                student.Budget = budgetAmount;
            }
            else
            {
                throw new Exception("Budget should be higher than studentCourse.Price");
            }
        }
    }
}
2

Both business rules are limitations on the Student entity. This means they can be enforced either by the Student entity itself or by a service class like you did.

The way that the rule about budget changes is written

5) Student budget can be modified setting a different amount but new amount have to be higher or equal to the price of the course the user booked. Setting a lower amount should throw a runtime error.

there is no indication which course price should be taken into account (if the price has changed between booking the course and changing the budget). This is something that you should ask clarification on.

If the new budget must be sufficient to cover the current course price, then your current implementation is nearly good (you missed the case where the budget is changed before a course has been booked).

If the new budget must cover the course price at the time of booking, you will have to remember that amount somehow. This can be either within the Student entity, or the Course entity keeps a record of the price changes over time and the Student entity records when a course has been booked.

1

Overuse of services is a sure sign of an anemic model. Your system can be modeled quite simply:

class Student
{
    private studentId;

    private budget;

    private registration;

    Book( Registration registration )
    {
        if( registration.Price > this.budget )
            throw;

        this.registration = registration;
    }

    ChangeBudget( amount )
    {
        if( this.registration && this.registration.Price > amount )
            throw;

        this.budget = amount;
    }
}


class Course
{
    private courseId;

    private price;

    // Factory Method
    OpenRegistration( offers )
    {
        price = ; // do something to calculate new price

        return new Registration( this.courseId, price );
    }
} 

// Value Object
class Registration
{
    public CourseId { get; }

    public Price { get; }

    Registration( courseId, price )
    {
        CourseId = courseId;
        Price = price;
    }
}

And then in your BookCourseCommandHandler:

student = students.Find( cmd.StudentId );

course = courses.Find( cmd.CourseId );

registration = course.OpenRegistration( cmd.SpecialOffers );

student.Book( registration );

students.Save( student );

The key here is that the above is declarative. There is no "asking" for data so it is very easy to see what's happening.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.