Данная статья является продолжением поста “Программная архитектура”. Ознакомьтесь с ним перед тем, как приступить к изучению.
Переходным звеном между “Сценарием транзакции” и “Моделью домена” является “Модуль таблицы” (автор Мартин Фаулер). Это решение предлагает использовать по классу для каждой таблицы в реляционной базе данных, который включал бы все необходимые для работы методы. Такие классы можно было бы обернуть в легкие сценарии (на подобе сценариев транзакций), отвечающие за взаимодействие с ними, либо использовать напрямую в качестве контроллеров с инкапсулированной логикой доступа к данным.
Рассмотрим пример:
<?php
class BlogTable{
// Добавление статьи в блог
public function addArticle($author, $title, $content, array $tags){
...
}
// Удаление статьи из блога
public function removeArticle($arthicleId){
...
}
// Получение списка статей
public function getArticles($limit = 10, $offer = 0){
...
}
// Получение конкретной статьи
public function getArticle($arthicleId){
...
}
}
Чаще всего классы, представляющие таблицы базы данных, предоставляют сразу коллекции сущностей, хранящихся в этих таблицах, а не единичные экземпляры.
Поверх таких классов, может быть построен простой REST API, который перенесет основную логику генерации пользовательского интерфейса на уровень клиента. Более того, инкапсуляция логики работы с сущностями системы уменьшает объем повторяемого кода и делает архитектуру более гибкой и переносимой.
Пример слоя контроллеров, использующих “Модуль таблиц”:
<?php
class BlogController{
private $userTable;
private $blogTable;
public function __construct(UserTable $userTable, BlogTable $blogTable){
$this->userTable = $userTable;
$this->blogTable = $blogTable;
}
// Страницы
public function indexPage(){
return new TemplateEngine('blog/index.html', [
'articles' => $this->blogTable->getArticles(),
]);
}
// Действия
public function addAction(){
$currentUser = $this->userTable->getCurrentUser();
$title = trim($_POST['title']);
$content = trim($_POST['content']);
$this->blogTable->addArticle($currentUser, $title, $content, []);
return header('Location: /blog');
}
public function removeAction(){
$currentUser = $this->userTable->getCurrentUser();
$targetArticle = $this->blogTable->getArticle((int) $_POST['id']);
// Удалить статью может только ее автор.
// Данное ограничение может быть инкапсулированно в класс BlogTable, либо вынесено в контроллер, как в данном примере
if($targetArticle->author != $currentUser){
return header('Location: /error');
}
$this->blogTable->removeArticle($targetArticle->id);
return header('Location: /blog');
}
}
Данная архитектура все еще довольно проста в реализации, но все же не идеальна. Вот некоторые проблемы, которые не могут быть решены с ее помощью:
- Сильная зависимость от инфраструктуры, в виде реляционной базы данных
- Смешивание с инфраструктурой или полное отсутствие слоя модели с бизнес-ограничениями и бизнес-логикой, присущей ей
- Более сложная структура, по сравнению со “Сценарием транзакции”, и недостаточно гибкая и переносимая, нежели “Модель домена”