Использовать плагины контроллера ZF2 для служб приложений в DDD?
В настоящее время я использую DDD (Domain Driven Design) для нового проекта Zend Framework 2. Все работает нормально, но у меня есть вопрос относительно сервисов приложений.
Я понял, что сервисы приложений расположены на уровне приложений и являются своего рода точкой входа в доменную логику. Они могут получить доступ к доменным службам или хранилищу, например.
Теперь мне интересно, имеет ли смысл реализовывать сервисы приложений как плагины контроллера. В классическом приложении MVC этот плагин контроллера может обрабатывать результаты от названных доменных служб или репозиториев. В зависимости от этих результатов они могут генерировать ответ перенаправления или передавать данные / форму в ViewModel. Если эта логика заключена в плагин, мой контроллер должен только вызвать плагин и вернуть результат плагина.
Я совершенно не прав? Или вы предпочли бы сохранить логику, как реагировать на результаты доменной службы или хранилища в контроллере?
С уважением,
Ralf
2 ответа
Конечно, это отчасти субъективно, и у людей сильные мнения о таких вещах... так вот мое:
- Плагины контроллера содержат код, который универсален для любого действия MVC/REST, а бизнес-логика не универсальна. Плагины должны облегчать "контроль" запросов / ответов, а не бизнес-логику, которая находится на уровне модели. Связывание этого сделает его менее пригодным для повторного использования, например, для консольных действий. Кроме того, было бы менее вероятно использовать бизнес-логику с какой-либо другой структурой.
- Это неудобно для проверки. Внедрение плагинов контроллера в качестве параметров конструктора класса контроллера было бы излишним, поскольку они уже доступны в диспетчере плагинов, введенном в
AbstractActionController
или жеAbstractRestfulController
, Отсутствие зависимостей, введенных очевидным / видимым способом (например, методом через корыто), затрудняет выяснение того, от чего фактически зависит класс контроллера. Также, так как все плагины (AbstractPlugin
related) зависит от экземпляра контроллера, переключение контекста с http на консоль (например, для теста phpunit) может стать проблематичным. Также тестовая логика, написанная / сделанная доступной как плагин контроллера, рано или поздно переросла бы в включение объектов запросов / ответов в тесты, и это лишняя сложность. - Это не интуитивно понятно. Когда я слышу плагин, я думаю о чем-то маленьком. Не полный код бизнес-логики похоронен под таким неприметным именем. Поэтому, когда у меня мало времени для отладки чьего-то кода, последнее, что мне нужно, чтобы такие вещи были неуместны.
Снова я хотел бы повторить, это только мое мнение. Я узнал, что многие шаблоны могут разрушиться при достаточно странном сценарии использования, но вышеприведенные пункты имели смысл для меня и моей команды до сих пор.
В качестве примера моего решения вы можете увидеть действие контроллера:
public function showAction()
{
$service = $this->readProductEntityCommand;
$service->setId($this->params()->fromRoute('id'));
try {
$result = $service->execute();
} catch (ProductException $e) {
$this->flashMessenger()->addMessage($e->getMessage());
return $this->redirect()->toRoute('part3/product');
}
return new ViewModel(
array(
'productEntity' => $result->getData(),
)
);
}
А вот пример сборки службы приложения как объекта команды
class ReadProductEntityCommand implements CommandInterface
{
protected $productRepository;
protected $id;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function setId($id)
{
$this->id = $id;
}
public function execute()
{
if (is_null($this->id)) {
throw new ProductIdInvalidException(
'Produkt ID wurde nicht angegeben.'
);
}
try {
$product = $this->productRepository->getProduct(
new ProductIdCriterion(
new ProductId($this->id)
)
);
} catch (\Exception $e) {
throw new ProductNotFoundException(
'Es konnten kein Produkt gelesen werden.'
);
}
if ($product === false) {
throw new ProductNotFoundException('Produkt wurde nicht gefunden.');
}
$result = new Result();
$result->setValid(true);
$result->setData($product);
$result->setMessage('Produkt wurde gelesen.');
return $result;
}
}
Может быть, это поможет кому-то в будущем.