Skip to content

Instantly share code, notes, and snippets.

@Rhincodon
Last active January 15, 2017 11:52
Show Gist options
  • Save Rhincodon/5f3b12158b1282122a71 to your computer and use it in GitHub Desktop.
Save Rhincodon/5f3b12158b1282122a71 to your computer and use it in GitHub Desktop.
Структура приложения и пространств имён при DDD

Источник

Вопрос

Как лучше организовать структуру моего приложения используя DDD и Laravel 4 или 5?

Ответ

Я обычно разделяю большие приложения на 4-5 пространств имён, которые привязаны к глобальному app пространству имён. Например, я хочу создать приложение ToDo List, так что базовое пространство имён будет ToDo.

Затем у меня есть 3 пространства имён внутри этого:

  • App — laravel-специфичная функциональность — классы валидаторы, сервис-провайдеры базовой модели и тд;
  • Domain — вся моя бизнес-логика, такая как сущности, интерфейсы репозиториев, сервисы домена;
  • Infrastructure — вся базовая логика. Это включает в себя реализации репозиториев, декораторы кеша и тд;

В дополнение к этому, у меня есть по крайней мере одно пространство имён для взаимодействия с внешним миром. Для типичного веб-приложения это Http, для REST API это Api, для команд artisan это Cli. Итоговая структура приложения может быть примерно такой:

app/
----ToDo/
--------App/
------------Providers/
----------------ToDoServiceProvider.php
----------------ConfigServiceProvider.php
------------Validators/
----------------LaravelValidator.php
------------ValueObject.php
------------BaseModel.php
--------Domain/
------------List/
----------------EloquentList.php
----------------ListRepository.php
----------------ListService.php
----------------ListValidator.php
----------------Priority.php
------------Task/
----------------EloquentTask.php
----------------TaskRepository.php
----------------TaskService.php
----------------TaskValidator.php
--------Http/
------------Lists/
----------------ListController.php
----------------ListPresenter.php
----------------ListViewComposer.php
------------Tasks/
----------------TaskController.php
--------Infrastructure/
------------Lists/
----------------ListRepositoryCacheDecorator.php
----------------EloquentListRepository.php
------------EloquentTaskRepository.php

Пространство имён App

Это то что я рассматриваю как основную точку взаимодействия между моей бизнес-логикой, логикой инфраструктуры, взаимодействием с внешним миром (web, REST API, CLI), и Laravel. Это то место где я настраиваю все мои зависимости и храню классы которые расширяются потом кодом в домене, и не содержат никакой логики домена в себе. Основное правило здесь — большинство классов будут абстрактными.

  • Связывание TaskRepository и EloquentTaskRepository через сервис-провайдер
  • Связывание конфигурации из одного из конфигов Laravel и класса, который требует её как часть конструктора[1]
  • Создание абстрактного класса LaravelValidator, который содержит в себе интерфейс без реальных правил валидации бизнес-логики
  • Создание класса BaseModel который предоставляет общую функциональность моим сущностям
  • Создание специфичных сервисов уровня приложения(не сервисов домена), которые зависимы от Laravel, например утилиты для перемещения файлов[2]
  • Создание абстрактных классов которые используются различными портами(web, REST API и тд), например ApiController который имеет вспомогательные методы для трансформации ответов сервера, возвращения API-специфичных кодов ошибок, внедрения meta информации и td.

[1] Например, у меня может быть Validator, который зависит от массива правильных Task Categories, которые определены в конфигурационном файле. Конструктор принимает массив категорий и я использую сервис-провайдер для для связи:

//ToDo/App/Providers/ConfigServiceProvider.php
public function register()
{
    $categories = $this->app['config']->get('todo.categories');
    $this->app->bind('ToDo\Domain\Tasks\TaskValidator.php', function($app) 
    {
        $categories = $this->app['config']->get('todo.categories');
        $factory = $this->app->make('Illuminate\Validation\Factory');
        return new TaskValidator($factory, $categories);
}

[2] Здесь возможно расхождение во мнениях, но я верю что моя логика домена не должна заботиться о том в какой директории файл, до тех пор пока она не получит доступ к этому файлу. Поэтому сервис для определения расположения файлов это слой инфраструктуры или приложения.

Пространство имён Domain

Тут просто, это место где живёт вся моя бизнес-лоигка. Сущности, Объекты-значения, Валидаторы, Спецификации, вся фукнциональность которая доставляет результат клиенту находится здесь. В идеале мои сущности должны быть POPO(Plain Old PHP Objects) и не зависеть от Eloquent, но пока это сложно заставить работать хорошо, я имею склонность использовать Eloquent для моих сущностей — это единственное место где я сильно нарушаю принцип разделения ответственности SRP. Но пространство имён ToDo\Domain\List будет содержать следующее:

  • EloquentList — сущность List, и я обычно использую мутаторы для объектов-значений. Если нам нужно загрузить в сущность List все задания, это будет сделано автоматически.
  • Priority — самовалидирующийся объект-значение, который содержит приоритет сущности List. Он может принимать цифровое значение от 1 до 10, или что-то простое типа «низкий», «средний», «высокий».
  • ListRepository — интерфейс репозитория с методами такими как например findById($id), store($list), setPerPage($perPage = 0), sortBy($field, $direction = 'DESC'), existsByName($name) и тд.
  • ListService — это главный класс с которым будет взаимодействовать мой порт. Если функциональность достаточно сложная, я разобью его на несколько классов.
  • ListValidator — это валидатор который ответственный за реализацию валидации и ему нужны внешние данные о том что мы можем делать с сущностями и объектами-значениями, а что нет. Например, мы должны удостовериться что название для List уникальное среди остальных сущностей List(что требует репозиторий как зависимость так как это нельзя сделать в рамках только одной сущности List).

Все эти вещи должны меняться только тогда когда мне скажет клиент. Любые технические изменения должны уходить в абстрактные классы из которых расширяются классы домена или должны идти в реализации интерфейсов в пространстве имён домена.

Пространство имён Http

Это место где находится вся логика которая связывает моё приложение с внешним миром(например браузером). Всё что отвечает за HTTP запросы должно жить здесь. Это может включать в себя следующее:

  • ListController — достаточно самообъясняюще. Он может расширяться из абстрактного контроллера в пространстве имён App если нужно, и может иметь ListService внедрённым в себя. Он может быть ответственным за ловлю исключений валидации которые приходят из класса ListService, их обработку путём редиректа и отправки ошибок во вьюху.
  • ListPresenter — если нам нужно добавить html-специфичное форматирование для списков, например форматирование дат или преобразование цифр приоритета в более читаемый label, эта функциональность должна быть здесь. Контроллер должен быть ответственным за создание экземпляра объекта, его преобразования и доставки его во вьюху.
  • ListViewComposer — если есть какая либо дополнительная информация которую мы должны прикрепить к одной или нескольким вьюхам списков, это будет жить здесь. Здесь может находиться всё и даже могут быть внедрены различные сервисы приложения или домена.

Это только один «порт» моего приложения. Если у меня есть Artisan Commands или REST API, то для них будет своё пространство имён с соответствующими классами.

Пространство имён Infrastructure

Всё что относится к базовому состоянию находится здесь. Самая очевидная вещь которая должна быть здесь это реализации репозиториев. Например:

  • EloquentListRepository — Eloquent-специфичная реализация интерфейса ListRepository в пространстве имён Domain.
  • ListRepositoryCacheDecorator — это реализация того же интерфейса что и у репозитория и она является декоратором который добавляет функциональность кеширования.

Другие вещи которые могут быть здесь: класс ListDatabaseMapper, если используется паттерн Data Mapper вместо Eloquent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment