8

First off, I'll admit that I'm a newbie to DDD and need to read the "blue book".

I'm building a system that has an AggregateRoot of type "Match". Each Match can have a collection of "Votes" and also has a readonly "VoteCount" property which gets incremented when a user up-votes or down-votes a Match.

Since many users could be voting on a Match at the same time, Votes have to be added/removed from the Match and the VoteCount has to be incremented/decremented as one atomic operation involving write locks (with locks handled by the DB). (I need VoteCount as a static value in the database to be queried on efficiently by other processes/components.)

It seems to me that if I were adhering to strict DDD, I would be coding this operation as such:

  1. An application service would receive a vote request object
  2. The service would then retrieve the Match object from a Match Repository
  3. The service would then call some sort of method on the Match object to add the Vote to the collection and update VoteCount.
  4. The Repository would then persist that Match instance back to the DB

However, this approach is not feasible for my application for 2 main reasons, as I see:

  1. I'm using MongoDB on the backend and cannot wrap this read-write operation into a transaction to prevent dirty reads of the Match data and its associated Votes and VoteCount.

  2. It's highly inefficient. I'm pulling back the entire object graph just to add a Vote and increment VoteCount. Although this is more efficient in a document db than in a relational one, I'm still doing an unnecessary read operation.

Issues 1 & 2 are not a problem when sending a single Vote object to the repository and performing one atomic update statement against Mongo.

Could Vote, in this case be considered an "aggregate" and be deserving of its own repository and aggregate status?

drogon
  • 334
  • 3
  • 8
  • Please do not cross-post questions between Stack Exchange sites. I see that you posted this on Programmers first - this is the right site for questions about software architecture and design and development methodologies. – Thomas Owens Feb 01 '13 at 11:04
  • That makes no sense, seeing as how I've gotten good answers from both. – drogon Feb 01 '13 at 11:17
  • 2
    This [discussion on Meta Stack Overflow](http://meta.stackexchange.com/questions/95615/cross-posting-etiquette) explains why cross posting is not a good thing. Your Stack Overflow question has been migrated here and merged with this one since this site is where this type of question belongs. – Thomas Owens Feb 01 '13 at 12:41

3 Answers3

8

Could Vote, in this case be considered an "aggregate" and be deserving of its own repository and aggregate status?

I think this might be the right answer. An aggregate should be a transactional consistency boundary. Is there a consistency requirement between votes on a match? The presents of a Vote collection on a Match aggregate would suggest that there is. However, it seems like one vote has nothing to do with the next.

Instead, I would store each vote individually. This way you can use the aggregate functionality of MongoDB to get the count, though I'm not sure whether it is still slow. If it is, then you can aggregate using the Map/Reduce functionality.

More generally, this may not be a best fit for DDD. If the domain doesn't consist of complex behavior there is hardly a reason to try to adapt the DDD tactical patterns (entity, agreggate) to this domain.

eulerfx
  • 1,222
  • 7
  • 8
  • Thanks! However, if I "normalize" the data, storing Votes and Matches as separate collections, I won't be able to "join" them into a single query, correct? I'd have to loop the reduced votes results and get the associated Matches? – drogon Feb 01 '13 at 01:47
  • @drogon - Your concern about joins is related to a data access strategy. That is a different matter that the actual domain model. And besides, I'm pretty sure that mongodb can handle the join. But I haven't used mongodb so I cannot say for sure. – Pete Feb 01 '13 at 19:13
  • @eulerfx - Your comment about map/reduce got me thinking about the data access side, that's why I voiced the concern in the comment. But I think everyone is getting at same idea here -- Vote in this case is an aggregate unto itself in the domain layer. I wish I could mark everyone's answer as the correct one :) – drogon Feb 01 '13 at 19:21
6

First of all, DDD is really about creating a model where reading the code is like how the actual domain experts would be talking about the domain.

Second, they way DDD describes an aggregate root, the child objects on the aggregate root are merely properties on the aggregate root itself, and you shouldn't be able to access the child elements directly, only through the aggregate root.

So seen from a DDD perspective, if votes are children to the aggregate root "match", then the act of adding a vote to a match is actually an act of modifying the match.

Though you have not given enough information for me to fully understand the domain, it doesn't seem to me that this would be the natural way to see things, but I imagine that it has some similarities to StackExchange?

On any StackExchange site, if I open my user page, I can see a list of votes I have cast, including if it is an up- or a downvote, and the time when I cast the vote. This is a direct query on votes I have cast. Thus, if you were to make a DDD model of StackExchange, then questions and answers could NOT be considered aggregate roots containing votes.

In this case, votes would be entities themselves.

Note that here I'm very focused on the way that "the blue book" describes aggregates, mainly because you mentioned it, and you focus on DDD in the question description. That doesn't mean that this is the only/correct way of dealing with aggregates.

p.s. If you havn't read the book yet, this presentation touches a lot of the subject matter: http://www.infoq.com/presentations/model-to-work-evans (part1) http://www.infoq.com/presentations/strategic-design-evans (part2)

Pete
  • 8,916
  • 3
  • 41
  • 53
2

Yes, you can model Vote as an aggregate unto itself. In the real world, often people vote for several things at once. The aggregate would then be called a Ballot instead of vote. You just have a special case where there is only one item to vote for at a time.

I can't speak for MongoDB (perhaps someone else can) - but you would have a very easy time doing this in RavenDB with something like:

public class Match
{
    public string Id { get; set; }
    public string Name { get; set; } // or whatever
}

public class Vote
{
    public string Id { get; set; }
    public string MatchId { get; set; }
    public string UserId { get; set; }
    public bool YeaOrNay { get; set; } // or whatever
}

Then you would just build a map/reduce index over your votes to tally the results.

  • Thanks, Matt. I'm still new to Mongo and still have some unanswered questions about how I'd do a query combining Match and Votes aggregate into one result set, similar to a join in SQL, without running into n+1 issue. – drogon Feb 01 '13 at 00:55
  • Not sure about Mongo, but in Raven, it's dead simple and there is no n+1 issue. No join is required. See a similar example [here](http://ravendb.net/docs/2.0/client-api/querying/static-indexes/defining-static-index#putting-indexes-into-practice) – Matt Johnson-Pint Feb 01 '13 at 14:30
  • Thanks, Raven looks very .net friendly :) In that example it looks like they are map reducing data from the same collection -> "Posts" and transforming by "Tags", which are children of "Posts", rather than joining 2 collections. – drogon Feb 01 '13 at 18:27
  • Right. You would be map/reducing the Votes, grouping by their MatchId. If you wanted to pull in the match name, you could use [LoadDocument](http://ravendb.net/docs/client-api/querying/static-indexes/indexing-related-documents) or a simple [include](http://ravendb.net/docs/client-api/querying/handling-document-relationships#includes) – Matt Johnson-Pint Feb 01 '13 at 19:07
  • Interesting, will definitely give Raven a look. – drogon Feb 01 '13 at 19:15