Tuesday, April 7, 2009

CMS Project Update – DDD Analysis Paralysis

I’m in the middle of my re-write of the CMS project I blogged about earlier.  Here’s a quick update:

  • I’ve switched from building my own DAL to using nHibernate, a move I’m still coming to terms with, but generally happy with.
  • I’m using the Repository Pattern for data persistence
  • I’m using ASP.NET MVC for the web application layer
  • I’m writing a LOT of unit tests to flesh out the object model design and basic functionality before I even start on the web UI.
  • I’m attempting to combine my DTO layer with my rich object model, which is the focus of this post.

Architecture Overview

I’ve created an abstract EntityBase class that simply has an ID.  So far, 2 classes of objects derive from this – Content classes (e.g. Blog posts, articles, etc.) and non-content objects that still have identities and still need to be persisted (e.g. tags).  In the database, I have a common “Content” table to store the properties common to all content types (e.g. State, , and then I have one-off tables for storing properties specific to derived content objects (e.g. BlogPosts table has columns for Title and Body).

For content classes, I’ve created a ContentBase that derives from EntityBase.  ContentBase adds properties for things like SubmittedBy, SubmittedDate, PublishDate (for delayed publishing), State (e.g. Draft, Published, PendingModeration, etc.), and ContentType (an enum: BlogPost, Article, Picture, etc.).

All of the properties on entities have public getters and setters so they work easily with nHibernate without requiring me to create a custom access method.  Maybe I’m being lazy here, but this really feels like the direction nHibernate pushes you.

my business logic quandary

I’m working through a design issue right now specifically related to these content objects.  I want to be able to enforce rules/workflow around publishing content.  For example, any user could submit an article, but only editors are allowed to actually publish the article.

My question is where does this business logic fit best?  All properties on content objects have public getters and setters, and so by using the IContentRepository<T> interface, any user could set the state of a content item to “Published” and save it to the database.  I need some way to enforce the business rules about who can publish content.

Option 1 – Logic in Repositories

One thought is to encode this logic into the content repositories.  I don’t like that, however, because it shouldn’t be the repositories job to manage permissions.  They should only be concerned with loading and modifying data. 

Option 2 – Logic in Entities

This leads me to the thought that consumers of my “object model” shouldn’t have any access to the repository layer.  Then, they couldn’t directly manipulate the data and circumvent the business rules.  Actually, I think this is a good idea no matter where I store the business logic, unless I can guarantee somehow that no business rules would be violated via direct access to the repositories.

So, if not in the repositories, then maybe I should add this logic into the content entities.  Each entity implements an IsValid() method that gets called before the repositories save data.  I could place my rules checks in this method, but it’s still somewhat messy.  For example, an Article entity knows who authored it, but it shouldn’t care who actually edits/moderates/publishes it.  In order for IsValid() to validate the publishing workflow, I’d have to add a property to content entities to track who published them.  Surely there would be some other such property down the line, and I’d have to add more and more to the entities that they really shouldn’t be concerned with.

Option 3 – Introduce Services

Another option is to introduce an IPublisherService interface that handles publishing workflow.  The usage would look something like this:

   IUser author = UserRepository.GetByName("Jason");
DateTime dateToPublish = DateTime.Parse("4/5/2009 1:34:00");
BlogRepository.Create("post title", "post body", author, dateToPublish);

I have a few problems with this approach too.  Notice that the UserRepository is still public here.  That violates the “rule” I established above while discussing Option 2.  So, do I establish an IUserService that essentially duplicates the IUserRepository interface along with some extra business logic? 

Also, what about Tagging content?  If I go with the “directly access entity properties” approach of Option 1, then I can simply add tags to the Tags collection member of my objects and when the object is persisted, then the Tag objects are automatically generated by nHibernate and saved/mapped in the appropriate tables.

Option 4 – Hybrid with Credential Validation in Repository Calls / Entity Logic

Part of the problem with my current repository interface is that it has no concept of who is actually calling it.  I could take a dependency on the ASP.NET membership stuff and just assume that I can find out from inside the repository, but I don’t like being tightly coupled to dependencies like that.  So, I will probably stick with my IUser interface which abstracts user identities out a bit.

So, if I pass in user credentials via IUser with each repository call, and repositories know how to ask a central permissions service whether or not the given user has privileges to do the thing they’re asking about, then I can still embed the permissions part of the business logic into the repositories.  This feels natural somehow, and I’m inclined to go with it.  The remaining business logic around entities can go into the IsValid() method on the entities.

So, each repository call would look something like this:

   IUser author = UserRepository.GetByName("Jason");
DateTime dateToPublish = DateTime.Parse("4/5/2009 1:34:00");
var newPost = BlogRepository.Create("post title", "post body", author, dateToPublish); // author is checked for permissions to create blog posts
Publisher.SubmitForPublishing(newPost, author);

Then, some editor user comes along and tries:

   IUser moderator = UserRepository.GetByName(currentUserName);
publisher.Publish(newPost, moderator);


   publisher.Reject(newPost, moderator);

... but if the post author tried it:

   publisher.Reject(newPost, author);

... then they'd get a security exception (which would also be logged in the audit log).

I’m currently leaning towards this approach. It combines the simplicity of embedding business logic into the entities without the requiring me to hide the repositories or (heaven forbid) split repositories into reader/writer interfaces.

I’m still not quite sure what to do about public setters for things like the Tags collection on entities… but I think that’s a topic for my next post.

Thoughts?  Am I barking up the wrong tree entirely?