Уведомления по электронной почте - В объекте домена или в сервисе?

StackOverflow https://stackoverflow.com/questions/928533

Вопрос

Я ищу рекомендации о том, как подойти к следующей проблеме проектирования (используя фиктивный пример, основанный на stackoverflow).Я бы попытался избежать анемичной модели предметной области и обратиться за общим советом "наилучшей практики" для данного типа случаев.

Сценарий:

Предположим, для stackoverflow разрабатывается новая функция, которая отправляет уведомление по электронной почте владельцу вопроса всякий раз, когда его / ее вопрос получает 10 положительных отзывов.

Объектная модель домена выглядит примерно так:

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() разрешите IEmailService из контейнера IoC (вместо передачи IEmailerService в качестве параметра).

  3. Обнаружьте это состояние во внешнем сервисном объекте, который вызывает question.AddUpvote().

    public void UpvoteClickHandler(Question question)
    {
        question.AddUpvote(new Upvote());
        if ( question.Upvotes.Count == 10 )
        {
            _emailer.Send(question.Owner.EmailAddr);
        }
    }
    
  4. Ваше лучшее решение здесь!

Это было полезно?

Решение

Вы действительно не хотите смешивать эти два фактора вместе, поскольку у них разные проблемы.Пусть класс вопросов заботится о вопросах, а служба сообщений - о том, что делать, когда число голосов достигнет 10, или 20, или 100, или...

Следующий пример предназначен только для демонстрации, но вы поймете, в чем дело.Существует четкое разделение проблем, поэтому класс вопросов не должен меняться, если изменяются требования к отправке сообщений.Помните, что согласно принципам 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. Он не должен знать о механизме уведомления для политики, то есть о службе электронной почты.

Я знаю, что это дело вкуса, но вы тесно увязываете этот Вопрос как с политикой, так и с механизмом отправки электронного письма.Было бы действительно сложно перенести этот класс вопросов в другой проект (например, 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, что в question было добавлено повышение, и позволяет NotificationManager определять, следует ли отправлять электронное письмо и как именно.

Ответ зависит от вашего фундаментального подхода к разработке приложений и объектов.И (отредактируйте здесь) то, что вы считаете своей самой важной чертой системы.Похоже, у вас есть данные, вопросы и бизнес-правила, набравшие голоса.Вообще не подвергайте сомнению объекты.Поэтому вы должны относиться к своим данным как к данным и позволять инструментам обработки данных работать с ними, не подмешивая в них поведение.Традиционный дизайн объекта содержал бы все параметры поведения и данные объекта, поэтому отправка электронной почты была бы частью объекта.(варианты 1 и 2) Я предполагаю, что это черный ящик, или автономный объектный подход.Современная практика, как я узнал, использует объекты в качестве простых носителей данных.Которые предназначены для перемещения, сохранения, трансформации и того, чтобы с ними что-то делали.Возможно, живущий немногим больше, чем структуры на C.Поведение зависит от служб и преобразований, которые применяются к простым объектам.

ПРИВЕТ всем,

На мой взгляд, "отправляет уведомление по электронной почте владельцу вопроса всякий раз, когда его / ее вопрос получает 10 голосов" - это логика домена, и поэтому оно должно быть в объекте домена, чтобы избежать анемичного домена.

Это действие по отправке электронного письма (т. е.связь с smtp-сервером), который ДОЛЖЕН быть включен в уровень инфраструктуры.

Поэтому я думаю, что вариант 1 не совсем неправильный.Имейте в виду, что вы всегда можете протестировать свой объект, передав макет реализации IEmailerService.

С наилучшими пожеланиями,

Stefano

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top