2

Let's say I'm making a library database with books and authors, and I want to model the domain with interfaces, should I also specify the relations in the interfaces?

For example, if I have the following interfaces:

public interface IAuthor
{
    public string Name { get; }
}
public interface IBook
{
    public string Title { get; }
    public string Isbn { get; }
    public IEnumerable<IAuthor> BookAuthors { get; }
}
public interface ILibrary
{
    public string Name { get; }
    public IEnumerable<IBook> LibraryBooks { get; }
}

Then my entities would look like this:

public class Author : IAuthor
{
    public int Id { get; set; }
    public string Name { get; set; }
}
public class Book : IBook
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Isbn { get; set; }

    public List<Author> Authors { get; set; }
    [NotMapped]
    public IEnumerable<IAuthor> BookAuthors // Clunky
    {
        get => Authors;
        set => Authors = value.Select(a => a as Author).ToList();
    }
}
public class Library : ILibrary
{
    public int Id { get; set; }
    public string Name { get; set; }

    public List<Book> Books { get; set; }
    [NotMapped]
    public IEnumerable<IBook> LibraryBooks // Clunky
    {
        get => Books;
        set => Books = value.Select(b => b as Book).ToList();
    }
}

But the whole casting process from IEnumerable<ISomething> to List<Something> (or ICollection or IEnumerable or HashSet or what have you) seems a bit clunky. Is there not a more elegant way?

4
  • 1
    Your use of casts suggests that your interfaces does not represent an appropriate abstraction. Alternatively, the problem could be argued to be that you are offering setters. If you have a get/set property, then the type must not have “variance”.
    – amon
    Jan 20, 2022 at 12:04
  • @amon the public/private setter topic aside (it's simply an example to show relationships, after all), how would you suggest re-writing the classes? Implement abstract classes? Use the interfaces directly (I'm not sure if that works with Entity Framework Core)? Jan 20, 2022 at 12:09
  • 1
    (1) My point is that the interfaces don't seem to provide any benefit and complicate your code. Entities are typically very concrete, and it doesn't make a lot of sense to abstract over them. Neither DDD nor EF Core expect you to define these interfaces. (2) If you wanted to provide accurate types (without needing casts), you would need interfaces like interface IBook<Author> where Author: IAuthor { ... }.
    – amon
    Jan 20, 2022 at 14:07
  • Interfaces are for hiding stuff and being able to provide multiple implementations of the same thing. If you don't need any of that interfaces will just give a lot of extra typing. Jan 20, 2022 at 18:21

3 Answers 3

1

Drop the interfaces, they are not useful for your purpose.

What you want is your own business objects, that may look different from your actual database objects. Because your database objects are an image of your database and that has to conform to technical realities of your database brand, while your business objects are modelled after the reality of your business processes.

Trying to solve that with interfaces is not going to work, because those two might not map correctly in the eyes of your compiler. And that is fine. They have different rules anyway. For your database, zip code might be a 20 character field, but your business logic might know that in country X it's 5 numbers while in country Y it's two letters followed by four numbers. Or in your database, parenthood might be a many-to-many relationship, but your business logic knows having 3 kids is likely, having three biological mothers is... unlikely to be correct.

So you need actually different classes. Because your business logic is not the same as your database logic.

3

The primary purpose on an interface is to guarantee functionality for an object like calling Dispose() on anything implementing IDisposable. The related, secondary benefit of an interface is to allow unit testing to isolate the object under test, because if an object exposes dependencies as interfaces that functionality can be faked. The interfaces you have here aren't actually doing that for you, because they don't actually have any functionality (you also don't have anything to test, because no functionality). If you were to delete the interfaces, you now have ~15 lines of code for a domain model, which is far more manageable.

1

Since IBook is an interface, I could have two implementations ABook and BBook. ABook might have a List of AAuthor, while BBook might have an Array of BAuthor.

And now you find that you can’t add an IAuthor to an IBook: Because you can only add AAuthors to ABooks, and only BAuthors to BBooks. Suddenly the interfaces become much less useful, because any author that might be set as the author of a book must be a concrete AAuthor or BAuthor.

Solution: Remcve the ABook method returning a concrete List of concrete list of AAuthors.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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