Sam
Sam

Reputation: 10113

N-tier Repository POCOs - Aggregates?

Assume the following simple POCOs, Country and State:

public partial class Country
{
    public Country()
    {
        States = new List<State>();
    }
    public virtual int CountryId { get; set; }
    public virtual string Name { get; set; }
    public virtual string CountryCode { get; set; }
    public virtual ICollection<State> States { get; set; }
}

public partial class State
{
    public virtual int StateId { get; set; }
    public virtual int CountryId { get; set; }
    public virtual Country Country { get; set; }
    public virtual string Name { get; set; }
    public virtual string Abbreviation { get; set; }
}

Now assume I have a simple respository that looks something like this:

public partial class CountryRepository : IDisposable
{
    protected internal IDatabase _db;

    public CountryRepository()
    {
        _db = new Database(System.Configuration.ConfigurationManager.AppSettings["DbConnName"]);
    }

    public IEnumerable<Country> GetAll()
    {
        return _db.Query<Country>("SELECT * FROM Countries ORDER BY Name", null);
    }

    public Country Get(object id)
    {
        return _db.SingleById(id);
    }

    public void Add(Country c)
    {
        _db.Insert(c);
    }

    /* ...And So On... */
}

Typically in my UI I do not display all of the children (states), but I do display an aggregate count. So my country list view model might look like this:

public partial class CountryListVM
{
    [Key]
    public int CountryId { get; set; }
    public string Name { get; set; }
    public string CountryCode { get; set; }
    public int StateCount { get; set; }
}

When I'm using the underlying data provider (Entity Framework, NHibernate, PetaPoco, etc) directly in my UI layer, I can easily do something like this:

IList<CountryListVM> list = db.Countries
    .OrderBy(c => c.Name)
    .Select(c => new CountryListVM() {
        CountryId = c.CountryId,
        Name = c.Name,
        CountryCode = c.CountryCode,
        StateCount = c.States.Count
    })
    .ToList();

But when I'm using a repository or service pattern, I abstract away direct access to the data layer. It seems as though my options are to:

  1. Return the Country with a populated States collection, then map over in the UI layer. The downside to this approach is that I'm returning a lot more data than is actually needed.

    -or-

  2. Put all my view models into my Common dll library (as opposed to having them in the Models directory in my MVC app) and expand my repository to return specific view models instead of just the domain pocos. The downside to this approach is that I'm leaking UI specific stuff (MVC data validation annotations) into my previously clean POCOs.

    -or-

  3. Are there other options?

How are you handling these types of things?

Upvotes: 3

Views: 506

Answers (4)

Vladik
Vladik

Reputation: 176

Honestly, your question has gave me a food for thought for a couple of days. More and more I tend to think that denormalization is the correct solution.

Look, the main point of domain driven design is to let the problem domain drive your modeling decisions. Consider the country entity in the real world. A country has a list of states. However, when you want to know how many states a certain country has, you are not going over the list of the states in the encyclopedia and count them. You are more likely to look at the country's statistics and check the number of states there.

IMHO, the same behavior should be reflected in your domain model. You can have this information in the country's property, or introduce a kind of CountryStatistics object. Whatever approach you choose, it must be a part of the country aggregate. Being in the consistency boundary of the aggregate will ensure that it holds a consistent data in case of adding or removing a state.

Upvotes: 1

Neil Barnwell
Neil Barnwell

Reputation: 42155

One option is to separate your queries from your existing infrastructure entirely. This would be an implementation of a CQRS design. In this case, you can issue a query directly to the database using a "Thin Read Layer", bypassing your domain objects. Your existing objects and ORM are actually getting in your way, and CQRS allows you to have a "command side" that is separate and possibly a totally different set of tech to your "query side", where each is designed to do it's own job without being compromised by the requirements of the other.

Yes, I'm quite literally suggesting leaving your existing architecture alone, and perhaps using something like Dapper to do this (beware of untested code sample) directly from your MVC controllers, for example:

int count = 
    connection.Query<int>(
        "select count(*) from state where countryid = @countryid", 
        new { countryid = 123 } );

Upvotes: 2

Simon Whitehead
Simon Whitehead

Reputation: 65077

It really depends on the projects architecture for what we do. Usually though.. we have services above the repositories that handle this logic for you. The service decides what repositories to use to load what data. The flow is UI -> Controller -> Service -> Repositories -> DB. The UI and/or Controllers have no knowledge of the repositories or their implementation.

Also, StateCount = c.States.Count would no doubt populate the States list anyway.. wouldn't it? I'm pretty sure it will in NHibernate (with LazyLoading causing an extra select to be sent to the DB).

Upvotes: 2

Vladik
Vladik

Reputation: 176

Some other approaches:

  • If the states collection is not expected to change a lot, you can allow a bit of denormalization - add "NumberOfStates" property to the Country object. It will optimise the query, but you'll have to make sure the extra field holds the correct information.

  • If you are using NHibernate, you can use ExtraLazyLoading - it will issue another select, but won't populate the whole collection when Count is called. More info here: nHibernate Collection Count

Upvotes: 1

Related Questions