Domain Logic: Morphing an entity?

In my test blog project, I have the notion of a Comment, but there are two states it can be in: moderated and published. Therefore, I subclassed it into ModeratedComment and PublishedComment. ModeratedComment has a method called Publish() but PublishedComment does not, logically.

Now here’s the issue, neither ModeratedComment nor PublishedComment need an ‘IsPublished’ property because their state is defined by their type. So how do I set this state in the body of Publish() so that on save and flush, the entities are in the right state, and list, on next request?

The only alternatives I can see are adding a readonly IsPublished property (which I’d rather not do), or the following:

public virtual void PublishComment(Guid moderatedCommentId)
{

moderatedComment = moderatedComments.Single(x => x.Id == moderatedCommentId);

moderatedComments.Remove(moderatedComment);

PublishedComment publishedComment = new PublishedComment(moderatedComment.Id)
{ // set other properties };

publishedComments.Add(publishedComment);

}

Of course, this does twice the work as it actually deletes the record and then recreates it.

Am I missing an option? What’s the best way to handle this?

The best way is to have a single Comment entity with the property IsPublished :smiley: . I’m all in favour for creating models specific for a task but sometimes it’s just over engineering.

After drawing a total blank on it myself, I have to agree with you on this one.

^^^ agreed. In fact I would not even call it over engineering. Over engineering implies that it is still correct modelling, which I do not think this is (although I recognize that modelling is not an exact discipline).

Why? Think in terms of identities versus states. Is it really a new comment just because it was published, or is it the same comment which has changed state? To me at least it is the same comment.

There is nothing wrong with having a Publish() method even though it doesn’t apply to all possible states. That’s what the InvalidOperationException is for. Also, by modelling it as change behavior you may even consider externalizing the publish function. This fits very well with e.g. a workflow engine: It is the workflow which drives the state from “submitted” to “published”. This way the workflow can be changed varying by organization policy, comment target, commentator, subject etc.

One funny example I often think of is this: Try modeling a cow and milk. Do you tell the cow to squirt out the milk? nah. Do you tell the milk to come out of the cow? nah. You need 3rd object: A farmer who can milk the cow. You bring him in to do the job and he doesn’t even change state doing it. He can even be a singleton :wink:

What I did was go with the single class, expose a protected set variable, and gave it a method Publish() which threw an exception if already published. This way, it can’t be set back unpublished.

Of course, Post entity has a method called PublishComment(id) which first finds the right comment before calling comment.Publish(), and throws an exception if it doesn’t exist.

I had to wrap it at the comment level to avoid it being set back. But kept primary control at the post level (it’s the root after all).