Reputation: 6081
I am looking into Domain Driven Design (yes, I am quite late for that party) and so far I have come to the realization that the Domain Model should be the center of the universe. The data persistance is merely a practical detail.
However, if this is true, then I have a hard time figuring out where the communication with the persititance part (e.g. a repository) should be.
For instance, if I want to be able to add a new grade to a student, should I call the repository from inside the student domain model, like this?
interface IGradeRepository
{
void SaveGrade(int courseId, string grade);
// ...other methods
}
class Student
{
IGradeRepository _gradeRepository;
List<Grade> _grades = new List<Grade>();
public Student(IGradeRepository gradeRepository)
{
_gradeRepository = gradeRepository;
}
int StudentId { get; set; }
void AddGrade(int courseId, string grade)
{
var grade = new Grade(this.StudentId, courseId, grade);
_grades.Add(grade);
// Do I put the call to the data persistance here?
_gradeRepository.SaveGrade(grade);
}
}
As you can see, my best bet is to access the repository from inside the domain model. However, I am not sure if this is the way to go about it. It seems to be one of the things that is left out in many tutorials.
So to summarize: From where do I access the data layer, when doing Domain Driven Design?
Update
As some have commented, it is not clear from my example, how I would access the student class. I would probably do that from a view model or through some sort of use case service.
Example with view model:
class StudentGradesViewModel
{
// (...) all sort of VM-stuff
private Student _student;
private Course _selectedCourse;
public void AddGrade(string grade)
{
_student.AddGrade(_course.CourseId, grade);
}
}
Upvotes: 1
Views: 145
Reputation: 86
First of all: The repository should only refer to aggregate. In your case, you have student which can have multiple grades. So, you repository should always be the persistance for the aggregate, not an entity inside an aggregate.
Here is the solution with the way I would choose:
Aggregate in Domain Layer:
class Student
{
List<Grade> _grades = new List<Grade>();
int StudentId { get; set; }
public Student()
{
}
void AddGrade(int courseId, string grade)
{
var grade = new Grade(this.StudentId, courseId, grade);
_grades.Add(grade);
}
}
Repository Interface in Domain Layer:
interface IStudentRepository
{
void SaveStudent(int studentId);
// ...other methods
}
Of course, as it was mentioned in posts above, the implementation of Repository should take place infrastructure layer.
You should create Application Service or CommandHandler (if you're using CQRS) in order to integrate domain logic and infrstructure:
class AddGradeService
{
private readonly IStudentRepository _studentRepository;
public AddGradeService(IStudentRepository studentRepository)
{
_studentRepository = studentRepository;
}
void Handle(int studentId, int courseId, string grade)
{
Student student = _studentRepository.Get(studentId);
student.AddGrade(courseId, grade);
_studentRepository.Save(student);
}
}
This is how it should be done :) You can also refer to my example od DDD if you want: https://github.com/czarek-szok/DDD.Ecommerce/
Upvotes: 2
Reputation: 57204
should I call the repository from inside the student domain model, like this?
That's not the usual pattern.
Instead, it is more common that the interaction with the repositories happens at the application layer, rather than within the domain layer.
The repositories give the application access to the aggregate root, and the application interacts with the aggregate root only. So the simple pattern looks something like
using transaction():
Root root = _repository.get(rootId)
root.doSomeCoolDomainThing(...)
The assumption here being that root has access to all of the persisted information required to maintaining its domain invariant while doing the cool thing (which normally means that the entire graph of information is available in memory).
In some cases, you'll see the save of the root into the repository be made more explicit:
using transaction():
Root root = _repository.get(rootId)
root.doSomeCoolDomainThing(...)
_repository.save(root)
What is the root when you get all the way to the bottom?
Root is a domain model entity; it's the one that is "in charge", so to speak, of all of the other entities in that particular aggregate.
Here, I'm just using it as a stand in. In a real project, the spelling would reflect the language of your domain -- Student, GradeBook, ReportCard, etc.
Upvotes: 0
Reputation: 156
The best time to join the party is right now so no worries. You should not access the data persistence directly from your domain model, instead this should be done in the infrastructure layer. If you are familiar with Onion Architecture, it explains this nicely. You can look it up here or here. The same principles described in the links can be applied to your case.
Accessing the persistence layer (usually named infrastructure) is via executing your use-cases which are usually placed in the application layer and implemented via commands (manipulating the state of entities) or queries (reading the state of entities in persistance)
Upvotes: 0