4

Say I'm creating a repository to store digital E-Books as shown in the interface below. This repository will store the actual text of the book, as well as the metadata that identifies the book (title, author, publisher, ISBN etc..).

public interface IBookRepository
{
    void AddBook(Book newBook);
    void DeleteBook(int bookId);
    void UpdateBook(Book updatedBook);
    Book GetBook(int bookID)
} 

public class Book
{
    public int BookId {get; set;}
    public string Title {get; set;}
    public string Author {get; set;}
    public IList<Page> Contents {get; set}
}

public class Page
{
    public int PageNumber {get; set;}
    public string PageContent {get; set;}
}

In most cases I would not want to retrieve the entire text for the book, as that would be rather expensive. In most cases all I care about is the metadata, for example I may simply want to create a list of books. So would it be acceptable in regards to DDD to also allow an IBookRepository to have methods that return BookSummary objects? Book summary objects would include the metadata but not the actual contents of the book.

What about having an UpdateBook(BookSummary book) method? Say I want to update the Book.Rating property, but don't need/want to read the entire contents of the book from the repository to do this.

public interface IBookRepository
{
    //Full Book Methods
    void AddBook(Book newBook);
    void DeleteBook(int bookId);
    void UpdateBook(Book updatedBook);
    Book GetBook(int bookID)

    //BookSummary Methods
    BookSummary GetBookSummary(int bookID)
    IEnumerable<BookSummary> GetBooksByAuthor(string authorName);
    IEnumerable<BookSummary> GetBooksByGenre(int genreId);
    void UpdateBook(BookSummary bookSummary);
} 


public class BookSummary
{
    public int BookId {get; set;}
    public string Title {get; set;}
    public string Author {get; set;}
    public int PageCount {get; set;}
}

Note: I know using an ORM with Lazy Loading would also be a solution to this, but I'd like to design my repositories with out the assuming that lazy loading will be used

Eric Anastas
  • 21,675
  • 38
  • 142
  • 236

3 Answers3

3

If there is a use case in your Domain that would support it why don't just create additional entity BookSummary with its own Repository that will do the job? It does not really matter where BookSummary is persisted - that it not relevant for the Domain.

It is important to derive Entities from the Domain using Ubiquitous language and not to look at the database structure.

public interface IBookRepository
{
    //Full Book Methods
    void Add(Book Book);
    void Delete(Book Book);
    Book findById(int bookID)
}

public interface IBookSummaryRepository
{
    //Full Book Summary Methods
    void Add(BookSummary BookSum);
    void Delete(BookSummary BookSum);
    Book findById(int bookSummaryID)
}

If you Repository has methods update() or store() it is more likely DAO than repository by DDD: http://codebetter.com/iancooper/2011/04/12/repository-saveupdate-is-a-smell/

Tomas Dermisek
  • 778
  • 1
  • 8
  • 14
  • Yes but my domain does expect to find a BookSummary if a Book is added to an IBookRespository. If they are separate repositories then there is no way to ensure that adding a Book to one will cause a BookSummary to show up in the other. Sure you could set up two repositories pointing at the same database table, but that would be a specific implementation. To me having one repository implies that all the data that comes from the repository comes from the same data store. See my latest SO question which is related to this: http://bit.ly/jjUhbi – Eric Anastas Jun 10 '11 at 05:42
  • You can call one repository from another. – Vijay Patel Jun 10 '11 at 08:09
  • I thought that repositories should not call other repositories: http://stackoverflow.com/questions/1364461/should-a-repository-call-another-repository-or-should-a-repository-call-a-servic, – Eric Anastas Jun 10 '11 at 20:53
2

In DDD repository should work only with aggregate root. In your case, I suppose, BookSummary is just an entity inside of Book aggregate (of course, more accurate analysis is needed here), so should get Book via BookRepository and then traverse to BookSummary from aggregate root using lazy loading. Otherwise, you are not applying Domain Driven Design here

xelibrion
  • 2,220
  • 1
  • 19
  • 24
  • I've added code for BookSummary. I don't think BookSummary is an entity inside the Book aggregate. A BookSummary has the same ID as the corresponding Book. It represents the same thing just in a different form. It's just a reduced/proxy version of a book. I understand that using an ORM with lazy loading would remove the need for a BookSummary type. Yet designing a repository with the assumption that lazy loading will be used doesn't seem any more correct then designing a repository with the assumption that SQL Server will be used. The interface should be independent of the implementation. – Eric Anastas Jun 10 '11 at 08:29
  • I'd suggest you do not have BookSummary. Here are few options to do it: 1. Load only data that is needed (using lazy properties or projections) 2. Introduce separated read-optimized storage (apply CQRS principles here) 3. Split Book into Book and BookText (here is no duplicated data as you would have in current model). Anyway, if you do not introduce read storage, you would have to load ENTIRE aggregate (using lazy load or not). If you don't do it, you will come down to data-driven approach – xelibrion Jun 10 '11 at 11:05
1

This is an old question, however there is no accepted answer so I will answer for the benefit of people landing here from Google. I run into the issue all the time as we have many summary screens that show partial information and /or information compiled from multiple entities. What I do is use separate read models as suggested by @xelibrion in the comments of his answer. I do not employ full CQRS, just simple read models with query methods only. So your BookRepository remains like so:

public interface IBookRepository
{
    void AddBook(Book newBook);
    void DeleteBook(int bookId);
    void UpdateBook(Book updatedBook);
    Book GetBook(int bookID)
}

And your BookSummary read model and its methods look like this:

public class BookSummary
{
    public int BookId {get; set;}
    public string Title {get; set;}
    public string Author {get; set;}
    public int PageCount {get; set;}
} 

public interface IBookSummaryQueries
{
    BookSummary GetBookSummary(int bookID)
    IEnumerable<BookSummary> GetBooksByAuthor(string authorName);
    IEnumerable<BookSummary> GetBooksByGenre(int genreId);
}

Note, there are no methods to update Book here, just querying

Louise Eggleton
  • 969
  • 2
  • 15
  • 27