Уведомления по электронной почте - В объекте домена или в сервисе?
-
06-09-2019 - |
Вопрос
Я ищу рекомендации о том, как подойти к следующей проблеме проектирования (используя фиктивный пример, основанный на 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);
}
}
Потенциальные реализации:
Изменение
AddUpvote()
чтобы принять участие вIEmailerService
параметр и выполнить логику в рамкахAddUpvote()
способ.public void AddUpvote(Vote upvote, IEmailerService emailer) { Upvotes.Add(upvote); if ( Upvotes.Count == 10 ) { emailer.Send(Owner.EmailAddr); } }
Обнаружьте это состояние внутри
AddUpvote()
и иметьAddUpvote()
разрешите IEmailService из контейнера IoC (вместо передачи IEmailerService в качестве параметра).Обнаружьте это состояние во внешнем сервисном объекте, который вызывает
question.AddUpvote()
.public void UpvoteClickHandler(Question question) { question.AddUpvote(new Upvote()); if ( question.Upvotes.Count == 10 ) { _emailer.Send(question.Owner.EmailAddr); } }
Ваше лучшее решение здесь!
Решение
Вы действительно не хотите смешивать эти два фактора вместе, поскольку у них разные проблемы.Пусть класс вопросов заботится о вопросах, а служба сообщений - о том, что делать, когда число голосов достигнет 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) выскакивают как неподходящие для отправки электронного письма.Экземпляр вопроса не должен знать эти две вещи:
- Он не должен знать о политике, то есть о том, когда отправлять электронное письмо.
- Он не должен знать о механизме уведомления для политики, то есть о службе электронной почты.
Я знаю, что это дело вкуса, но вы тесно увязываете этот Вопрос как с политикой, так и с механизмом отправки электронного письма.Было бы действительно сложно перенести этот класс вопросов в другой проект (например, 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