3

I am just starting in DDD and have a question regarding interfaces of objects and repositories. Suppose I have the following objects

public interface IPerson { ... }

public class Student 
{
    double gpa;
    ...
}

public class Teacher
{
    double salary; ...
}

then I also have two repositories such as

public class StudentRepository :IRepository { public void Save(Student) }

public class TeacherRepository :IRepository { public void Save(Teacher) }

My question is, suppose I have a list of IPerson objects called persons, is there a way where I can just do something like repository.Save(persons) ? Without having to use reflection to figure out what type the IPerson actually is.

I currently have another class

PersonRepository :IRepository 
{
     public void Save(IPerson person)
     {
          if(Person is Student)
          {
               new StudentRepository.Save(person as Student);
          }
          else if(Person is Teacher)
          { ....}
      }
}

Then I can call personRepository.Save(persons); However this doesnt feel like an optimal way to structure things. How can I improve this design?

Thanks

EDIT:

What I'm looking for is, say I receive an IPerson object called person. I do not necessarily know what implementation it is, I just want to call repository.Save(person) and have it call the correct repository. Is there a way to do this without using some sort of switch statement with reflection?

user1599120
  • 107
  • 2
  • 8

3 Answers3

4

Consider using generic repository

class Repository<T> :IRepository<T>
{
     public void Save(T entity)
     {
         ...
     }
}

Usage

IRepository<Student> repo1 = new Repository<Student>();
repo1.Save(new Student());

IRepository<Teacher> repo2 = new Repository<Teacher>();
repo2.Save(new Teacher());


Next you can use IoC container and DI just to pass repositories around instead of creating them

At the top level, say in the main method or global.asax

IRepository<Student> studentRepo = IoC.Current.Resolve<IRepository<Student>>();

Later in a class that needs to save data, pass IRepository<Student> studentRepo into constructor

class Foo
{
    private IRepository<Student> repo

    Foo(IRepository<Student> repo)
    {
        this.repo = repo;
    }

    public void Save(Student s)
    {
        repo.Save(s);
    }
}

EDIT

You can move a save operation to the IPerson<T>

class Person<T> : IPerson<T>
{
    private IRepository<T> repo;

    Person(IRepository<T> repo)
    {
        this.repo = repo;
    }

    public void Save()
    {
        repo.Save<T>();
    }
}

So when you derive Teacher and Student from Person<T> you pass correspondent T, like

class Student : Person<Student>
{
    private IRepository<Student> repo;

    Person(IRepository<Student> repo):base(repo)
    {
       ...
    }    
}

This shall give you the ability to work with List without Reflection or switch kung fu.

oleksii
  • 35,458
  • 16
  • 93
  • 163
  • Perhaps I'm missing something, but how would I go about saving something like a List that contains both students and teachers ? would I have to declare two seperate repositories and filter the list to input to the correct repository? – user1599120 Aug 14 '12 at 21:10
  • Basically, we have a Grid of courses. We want to be able to link students/teachers to a course. Each person to add to a course is added to a List. We also do some other stuff when a Teacher is linked to a course like increment salary. Then, we want to update the whole list of IPerson to the database. I hesitate to maintain two seperate lists of students / teachers because we may add other IPerson implementations in the future such as TAs etc. – user1599120 Aug 14 '12 at 21:31
  • @user1599120 aha got you. See if you move this logic to the Person class, then your derived classes can use correspondent repositories and save where needed. Code is written in notepad, but I hope you can use the idea. – oleksii Aug 14 '12 at 21:35
  • 1
    Domain objects should be as persistent-ignorant as possible. Having a 'Save' method on the entity violates persistent-ignorance and SRP. It is more of an ActiveRecord than domain model at this point. http://en.wikipedia.org/wiki/Active_record_pattern – Dmitry Aug 14 '12 at 23:05
  • @Dmitry it is actually persistent-ignorant. If you read this code `public void Save() { repo.Save(); }` from a `Person` class you wouldn't find any reference to a specific database code. – oleksii Aug 15 '12 at 07:52
  • Agree with Dmitry. I would never make my domain class dependent on a repository. – Wiktor Zychla Aug 15 '12 at 10:26
  • @oleksii: persitent-ignorant object does not know that it needs to be saved/persisted, so it should not have a method like 'Save'. – Dmitry Aug 15 '12 at 20:51
  • @Dmitry I understand your point, and it has its rationale. But I don't 100% agree with it. The object may be aware that it *can* be saved. It just doesn't care where to. In this particular case, OP needed to have 2 different domain objects, saved into 2 potentially different places by a single operation. Consider for example [this answer](http://stackoverflow.com/a/1975518/706456). I agree with that PI is a scale. Please suggest a better approach for this case. – oleksii Aug 15 '12 at 22:33
3

You can potentially have a method with C# generics

interface Repository<TEntity> where TEntity : class {

    void Save(TEntity entity);    
}

But I would discourage having generic (as in generalized, not C# generics) repositories. Repository interface should be domain driven and specific to your entity. Please consider this article by Greg Young.

It is also not clear why you have interfaces for you entities (IPerson). Interfaces are usually created at the seam of the application. Are you planning to have more than one implementation of IPerson?

Community
  • 1
  • 1
Dmitry
  • 17,078
  • 2
  • 44
  • 70
  • I've had success implementing generic repository base classes with implementors. No need to repeat plumbing. – neontapir Aug 14 '12 at 20:53
  • 1
    Repository interface is not a plumbing, it is a domain concept. Implementation is plumbing and you can avoid code duplication in the data access layer. Please take a look at the article that I mention. – Dmitry Aug 14 '12 at 20:55
  • I think we're trying to say the same thing. The data access doesn't need to be repeated. Excellent article, I now think composition would be preferable. – neontapir Aug 14 '12 at 20:55
  • I think `Student` and `Teacher` are supposed to be two implementors of `IPerson`. – neontapir Aug 14 '12 at 21:00
1

Two possible approaches.

First, interfaces specific for domain types

interface IStudentRepository
interface ITeacherRepository
class StudentRepository : IStudentRepository
class TeacherRepository : ITeacherRepository

Second, a generic interface

interface IRepository<T>
class StudentRepository : IRepository<Student>
class TeacherRepository : IRepository<Teacher>
Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106