I’m looking at adding taggable functionality to a project I’ve taken on. The project is 3 tier (mvc3 – Domain – Repositories).
I need to add the ability to tag various objects within this system. Because Tags can be attached to many different aggregate roots, I though it best to have tags as their own root (rep / ITagManager in domain).
My Idea was to have an ITaggable interface something similar to:
public interface ITaggable
{
bool SaveTags(IList<ITag> _tags);
bool SaveTag(ITag _tag);
IList<ITag> GetTags();
bool HasTag(IList<ITag> _tags);
bool HasTag(ITag _tag);
bool HasTag(string _tagName);
}
I had the idea to have an ITagManager which has methods to take ITaggable objects and save/load tags attached to them (perhaps using something like String.Concat(typeof(this), this.ID) to generate a unique ID for the object which implements the ITaggable interface).
Now there are two possible routes, first pass in any object that implements the ITaggable interface into the ITagManager itself, or modify the Itaggable interface to something like this:
public interface ITaggable
{
bool SaveTags(IList<ITag> _tags, ITagManager _tagManager);
bool SaveTag(ITag _tag, ITagManager _tagManager);
IList<ITag> GetTags(ITagManager _tagManager);
bool HasTag(IList<ITag> _tags, ITagManager _tagManager);
bool HasTag(ITag _tag, ITagManager _tagManager);
bool HasTag(string _tagName, ITagManager _tagManager);
}
The first solution perhaps smells of anemic model, but the second solution seems messy. I know that dependency could be injected, but I figured having it as a function parameter would make it obvious as to what was going on. Or would it be better to inject it into the object?
Are any of these solutions suitable?
Any help/advice would be appreciated.
I don’t think your ‘ITagable’ interface needs to be quite as bloated. Also, I wouldn’t model a Tag as an aggregate root as a tag doesn’t make any sense on its own.
Here is the interface we use:
Many of the methods you have on your interface could easily be implemented as extension methods if you required.
Sometimes you can’t handle all the logic within your domain objects. This is where domain services are useful and is what we use to handle the “processing” of tags on an ‘ITagable’ entity:
Notice we pass in the list of ‘featureTags’. In a blog example, this would be the list of tags for the entire blog, since we don’t want to create duplicate tags. The ‘TagService’ therefore, is not doing any kind of persistence, it is just creating new tags (on the blog) if it needs to, and tagging or untagging on the entity if it needs to:
When we update a tagable entity (normally passing a comma separated list of tags from our UI layer) we do the following:
In my case, we are mapping all tags to our parent blog object. You’re probably not going to be able to just copy+paste this code but it should give you an idea of how you can handle the processing of tags on your entities.