Если только я неправильно понимаю ваше намерение, а вместо этого я хочу сосредоточиться на семантике, я собираюсь разобрать это утверждение "В качестве примера: каждая сущность домена, которой требуется возможность отправлять электронную почту, должна зависеть от интерфейса IEmailService . "
Я бы сказал, что это само по себе является экстремальным убожеством DDD. Почему доменная сущность должна зависеть от службы электронной почты? ИМО это не должно. Этому нет оправдания.
Однако существуют бизнес-операции в сочетании с сущностью домена, которая потребует отправки электронных писем. Здесь должна быть ваша IEmailService
зависимость, а не доменная сущность. Этот класс, скорее всего, попадет под одно из нескольких почти синонимичных имен: модель, сервис или контроллер, в зависимости от того, в какой архитектуре / слое вы находитесь.
В этот момент ваш StructureMapControllerFactory
будет правильно автоматически подключать все, что будет использовать IEmailService
.
Хотя я могу немного переоценить обобщение, довольно стандартная практика, когда доменные объекты должны быть POCO или почти POCO (чтобы избежать нарушения SRP), однако часто SRP нарушается в доменных объектах ради сериализации и проверки. Выбор нарушения SRP для таких сквозных задач - это скорее личное убеждение, а не «правильное» или «неправильное» решение.
В качестве заключительного ответа, если ваш вопрос касается части кода, которая действительно работает в автономном сервисе, основанном на веб или ОС, и о том, как связать зависимости от этого, обычное решение будет взять на себя службу на базовом уровне и примените к нему IOC таким же образом, как StructureMapControllerFactory
в MVC. Как этого добиться, будет полностью зависеть от инфраструктуры, с которой вы работаете.
Ответ:
Допустим, у вас есть IOrderConfirmService
, у которого есть метод EmailOrderConfirmation(Order order)
. В итоге вы получите что-то вроде этого:
public class MyOrderConfirmService : IOrderConfirmService
{
private readonly IEmailService _mailer;
public MyOrderConfirmService(IEmailService mailer)
{
_mailer = mailer;
}
public void EmailOrderConfirmation(Order order)
{
var msg = ConvertOrderToMessage(order); //good extension method candidite
_mailer.Send(msg);
}
}
Тогда у вас будет OrderController
класс, который будет выглядеть примерно так:
public class OrderController : Controller
{
private readonly IOrderConfirmService _service;
public OrderController(IOrderConfirmService service)
{
_service= service;
}
public ActionResult Confirm()
{
_service.EmailOrderConfirmation(some order);
return View();
}
}
StrucutreMap по сути создаст всю цепочку архитектуры, если вы правильно используете внедрение конструктора. В этом принципиальная разница между жесткой связью и инверсией управления. Поэтому, когда StructureMapFactory собирается создать свой контроллер, первое, что он увидит, это то, что ему нужен IOrderConfirmService. На этом этапе он проверит, может ли он подключить IOrderConfirmService напрямую, чего он не может, потому что ему нужен IEmailService. Так что он проверит, может ли он подключить IEmailService, а для аргументов скажем, что может. Таким образом, в этот момент он создаст EmailService, который затем создаст MyOrderConfirmService и подключит EmailService, а затем, наконец, соберет OrderController и подключит MyOrderConfirmService. Отсюда и термин инверсия контроля. StructureMap сначала создаст EmailService во всей цепочке зависимостей и, наконец, завершит его контроллером. В тесно связанной установке это будет противоположно тому, где сначала будет построен Контроллер, и он должен будет создать бизнес-службу, а затем построить службу электронной почты. Плотно связанная конструкция очень хрупкая по сравнению с МОК.