0

I have a two aggregate roots in my domain, and therefore two repositories. We'll call them BookRepository, and AuthorRepository, for the sake of example.

I'm designing an MVC application, and one page has to display a table containing a list of authors, with each row showing the author's personal details. At the end of each row is a small button that can be clicked to expand the row and show a child table detailing the author's published books.

When the page loads, some ajax is executed to retrieve the Author details from an API controller and display the data in the table. Each property in an Author object maps almost directly to a column, with one exception, and this is where I'm having my problem. I want the button at the end of each row to be disabled, if and only if the author has no published books. This means that a boolean has to returned with each Author record, indicating if they have any published books.

My book repository has a couple of methods like this:

public IEnumerable<Book> GetBooksForAuthor(int authorId);

public bool AnyBooksForAuthor(int authorId);

and my Book class has a property called AuthorId, so I can retrieve a book's author by calling

authorRepository.GetById(book.AuthorId);

My problem is that in order to create a row for my aforementioned table, I need to create it like this:

IEnumerable<Author> authors = authorRepository.GetAll();
foreach (Author author in authors)
{
    yield return new AuthorTableRow
    {
        Name = author.Name,
        Age = author.Age,
        Location = author.PlaceOfResidence.Name,
        HasBooks = this.bookRepository.AnyBooksForAuthor(author.Id)
    };
}

The above code seems correct, but there's a fairly heft performance penalty in calling this.bookRepository.AnyBooksForAuthor(author.Id) for every single author, because it performs a database call each time.

Ideally, I suppose I would want an AuthorTableRowRepository which could perform something like the following:

public IEnumerable<AuthorTableRow> GetAll()
{
    return from a in this.dbContext.Authors
           select new AuthorTableRow
           {
               Name = a.Name,
               Age = a.Age,
               Location a.PlaceOfResidence.Name
               HasBooks = a.Books.Any()
           });
}

I'm hesitant to put this in place for these reasons :

  • AuthorTableRowRepository is a repository of AuthorTableRows, but AuthorTable row is not a domain object, nor an aggregate root, and therefore should not have its own repository.
  • As Author and Book are both aggregate roots, I removed the "Books" property from the Author entity, because I wanted the only way to retrieve books to be via the BookRepository. This makes HasBooks = a.Books.Any() impossible. I am unsure whether I am imposing my own misguided best practice here though. It seems wrong to obtain Books by obtaining an Author via the AuthorRepository and then going through its Books property, and vice versa in obtaining an Author via a property on a Book object. Crossing aggregate root boundaries would be the way I'd term it, I suppose?

How would other people solve this? Are my concerns unfounded? I am mostly concerned about the (what should be a) performance hit in the first method, but I want to adhere to best practice with the Repository pattern and DDD.

dark_perfect
  • 1,458
  • 1
  • 23
  • 41

2 Answers2

0

I would stick to the first approach, but try to optimize things in the bookrepository method. For instance, you can load this information all in one time, and use in-memory lookup to speed this up. Like this you would need 2 queries, and not 1 for each author.

  • How can the bookrepository method be optimised? It is a pretty straight forward .Any() query to the database, I'm not sure what more I can do to optimise it? – dark_perfect Sep 23 '14 at 10:43
  • Well, if the bookrepository would have an in-memory list for number of books by author, you would need only to initialize the list before the loop, like this; `this.bookRepository.LoadBooksByAuthor(); IEnumerable authors = authorRepository.GetAll(); foreach (Author author in authors) { yield return new AuthorTableRow { Name = author.Name, Age = author.Age, Location = author.PlaceOfResidence.Name, HasBooks = this.bookRepository.AnyBooksForAuthor(author.Id) // this call does not go to the database, but is just an in-memory lookup }; }` – Tom Van Acker Sep 24 '14 at 04:22
  • Is that really better if the bookRepository has millions of books to load though? The bookRepository can't load a subset of the table either, because it doesn't know which books you will want information on until the author query executes? – dark_perfect Sep 24 '14 at 10:29
  • Indeed, I would not load all books ... only the total amount of books by author. This memory list would hold on record per author... If that is not acceptable neither, you should consider being pragmatic and let the AuthorRepository return the number of books together with each author record. – Tom Van Acker Sep 25 '14 at 15:58
0

The way I solved this in the end was to create an Entity from a view in the database. I named the entity 'AuthorSummary', and made an AuthorSummaryRepository that didn't contain any Add() methods, just retrieval methods.

dark_perfect
  • 1,458
  • 1
  • 23
  • 41