电子邮件通知 - 在域对象或服务中?

时间:2009-05-29 23:22:23

标签: domain-driven-design business-logic

我正在寻找有关如何解决以下设计问题的建议(使用基于stackoverflow的虚构示例)。我试图避免一个贫血的领域模型,并寻求针对此类案例的一般“最佳实践”建议。

情境:

假设正在为stackoverflow开发一个新功能,只要他/她的问题收到10个upvotes就会向问题所有者发送电子邮件通知。

域对象模型是这样的:

public class Question
{
    string Question { get; set; }
    IList<Votes> Upvotes { get; set; }
    User Owner { get; set; }

    public void AddUpvote(Vote upvote)
    {
        Upvotes.Add(upvote);
    }
}

潜在的实施:

  1. 更改AddUpvote()以获取IEmailerService参数并在AddUpvote()方法中执行逻辑。

    public void AddUpvote(Vote upvote, IEmailerService emailer)
    {
        Upvotes.Add(upvote);
        if ( Upvotes.Count == 10 )
        {
            emailer.Send(Owner.EmailAddr);
        }
    }
    
  2. AddUpvote()内检测此状态,并AddUpvote()从IoC容器中解析IEmailService(而不是将IEmailerService作为参数传递)。

  3. 在调用question.AddUpvote()的外部服务对象中检测此状态。

    public void UpvoteClickHandler(Question question)
    {
        question.AddUpvote(new Upvote());
        if ( question.Upvotes.Count == 10 )
        {
            _emailer.Send(question.Owner.EmailAddr);
        }
    }
    
  4. 这里有更好的解决方案!

4 个答案:

答案 0 :(得分:5)

你真的不想将这两者混合在一起,因为他们有不同的顾虑。让问题类关心问题,并且消息服务关心当投票达到10,或者20或100或......时应该做什么。

以下示例仅用于演示目的,但您将明白这一点。关注点明显分离,因此如果发送消息的要求发生变化,则不必更改Question类。请记住,根据SOLID原则,一个类只应该有一个改变的理由。

public class Question
{
    public string Description { get; set; }
    public Int32 Votes { get; set; }
    public User Owner { get; set; }

    public event EventHandler<QuestionEventArgs> OnUpvote;

    private void RaiseUpvoteEvent(QuestionEventArgs e)
    {
        var handler = OnUpvote;
        if (handler != null) handler(this, e);
    }

    public void Upvote()
    {
        Votes += 1;

        RaiseUpvoteEvent(new QuestionEventArgs(this));
    }
}

public class MessageService
{
    private Question _question;

    public MessageService(Question q)
    {
        _question = q;

        q.OnUpvote += (OnUpvote);
    }

    private void OnUpvote(object sender, QuestionEventArgs e)
    {
        if(e.Question.Votes > 10)
            SendMessage(e.Question.Owner);
    }
}

public class QuestionEventArgs: EventArgs
{
    public Question Question { get; set; }

    public QuestionEventArgs(Question q)
    {
        Question = q;
    }
}

所以你有它。还有很多其他方法可以实现这一目标,但事件模型是一个很好的方法,它可以在您的实现中实现您想要的关注点分离,以便更早地进行维护。

答案 1 :(得分:2)

选项1)和2)都跳出来作为发送电子邮件的错误位置。问题实例不应该知道这两件事:

  1. 它不应该知道该政策,即何时发送电子邮件。
  2. 它不应该知道政策通知的机制,即电子邮件服务。
  3. 我知道这是一个品味问题,但是你在政策以及发送电子邮件的机制方面都在密切关注问题。将这个Question类移动到另一个项目(比如ServerFault,例如StackOverflow的姊妹站点)真的很难。

    我对这个问题很感兴趣,因为我正在为我正在构建的帮助台创建一个通知系统。这就是我在我的系统中所做的:

    创建NotificationManager(基本上,完全将对通知的关注转移到单独的类)。

    public Class NotificationManager
    {
        public void NotificationManager(NotificationPolicy policy, IEmailService emailer)
        {
        }
    }
    

    然后我做了一些事情(UpvoteClickHandler依赖于NotificationManager实例):

    public void UpvoteClickHandler(Question question)
    {
        question.AddUpvote(new Upvote());
        _notificationManager.Notify(Trigger.UpvoteAdded, question);
    }
    

    所有UpvoteClickHandler都会告诉NotificationManager一个upvote被添加到问题中,让NotificationManager确定是否以及如何发送电子邮件。

答案 2 :(得分:1)

答案取决于您对应用程序和对象设计的基本方法。并且(在此处编辑)您认为最重要的系统特征。看起来你有数据,问题和业务规则,投票。根本不是问题对象。因此,您应该将数据视为数据,并允许数据工具处理它们,而不是将行为混合到它们中。传统对象设计将具有对象中的所有行为和数据,因此发送eMail将属于对象。 (选项1和2)我猜这是黑盒子,或者是自包含对象的方法。正如我所学习的那样,现代实践将对象作为简单的数据持有者。这意味着要转移,坚持,转变并完成任务。也许只是生活在C语言中的结构。行为来自应用于简单对象的服务和转换。

答案 3 :(得分:1)

全部,

在我看来,“每当他/她的问题收到10个upvotes时,向问题所有者发送电子邮件通知”是域逻辑,因此它应该进入域对象,以避免贫血域。

发送电子邮件(即与smtp服务器通信)必须进入基础设施层。

所以我认为选项1并非完全错误。请记住,您始终可以通过传递IEmailerService的模拟实现来测试您的对象。

最诚挚的问候,

斯特凡诺