这是实现ITaggable功能的适当方式吗?

时间:2011-10-27 08:54:41

标签: c# asp.net-mvc-3 domain-driven-design tagging

我正在考虑为我所承担的项目添加可标记功能。该项目是3层(mvc3 - 域 - 存储库)。

我需要添加标记此系统中各种对象的功能。因为标签可以附加到许多不同的聚合根,我最好将标签作为自己的根(在域中的rep / ITagManager)。

我的想法是让ITaggable界面类似于:

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);
}

我有一个想法,有一个ITagManager,它有方法来获取ITaggable对象并保存/加载附加到它们的标签(可能使用类似String.Concat(typeof(this),this.ID)来生成一个唯一的ID实现ITaggable接口的对象)。 现在有两种可能的路由,首先将任何实现ITaggable接口的对象传递到ITagManager本身,或者将Itaggable接口修改为如下所示:

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);
}

第一种解决方案可能是贫血模型的气味,但第二种解决方案似乎很混乱。我知道可以注入依赖关系,但我认为将它作为一个函数参数可以明确地说明发生了什么。或者将它注入对象会更好吗?

这些解决方案中的任何一种都适用吗?

任何帮助/建议都将不胜感激。

2 个答案:

答案 0 :(得分:5)

我不认为你的'ITagable'界面需要非常臃肿。此外,我不会将标签建模为聚合根,因为标签本身没有任何意义。

以下是我们使用的界面:

public interface ITagable
{
    IEnumerable<Tag> Tags { get; }
    void Tag(Tag tag);
    void Untag(Tag tag);
}

如果需要,您在界面上的许多方法都可以轻松实现为扩展方法。

有时您无法处理域对象中的所有逻辑。这是域服务很有用的地方,也是我们用来处理'ITagable'实体上标签“处理”的地方:

public interface ITagService
{
    void ProcessTags<TEntity>(TEntity entity, Func<IEnumerable<Tag>> featureTags, 
        string tagString) where TEntity : ITagable;
}

请注意,我们会传入'featureTags'列表。在博客示例中,这将是整个博客的标签列表,因为我们不想创建重复的标签。因此,'TagService'没有执行任何类型的持久性,它只是在需要时创建新标记(在博客上),如果需要则在实体上标记或取消标记:

public class TagService : ITagService
{      
    public void ProcessTags<TEntity>(TEntity entity, 
        Func<IEnumerable<Tag>> featureTagsFactory, string tagString) where TEntity : ITagable
    {
        var result = new List<Tag>();
        // remove any leading/trailing spaces
        tagString = tagString.Trim();

        if (tagString.IsNotNullOrEmpty())
        {
            result.AddRange(from str in tagString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(t => t.Length > 1).Distinct()
                            join tag in featureTagsFactory() on (Slug)str equals tag.Slug into tags
                            from tag in tags.DefaultIfEmpty()
                            select tag ?? new Tag(str.Trim()));
        }

        // merge tags
        foreach (var tag in entity.Tags.Except(result)) // remove unmatched tags
        {
            entity.Untag(tag);
        }
        foreach (var tag in result) // entity should check if already added
        {
            entity.Tag(tag);
        }
    }
}

当我们更新可标记实体(通常从我们的UI层传递逗号分隔的标记列表)时,我们执行以下操作:

// tags
if (command.TagString.IsNotNullOrEmpty())
{
    tagService.ProcessTags(post, () => blog.Tags, command.TagString);
}

在我的例子中,我们将所有标签映射到我们的父博客对象。您可能无法复制+粘贴此代码,但它应该让您了解如何处理实体上的标记处理。

答案 1 :(得分:1)

为什么不标记自己的有界上下文?我怀疑标签需要与相应聚合上的任何其他行为保持一致。