И снова здравствуйте!

Что ж очередной «новый» курс, который стартовал в конце декабря, подходит к концу — «Backend разработчик на PHP». Учли разные мелкие шероховатости и запускаем новый. Осталось только посмотреть на выпуск и всё, поставим очередную галочку.

А счас пока давайте посмотрим на одну интересную статью.

Поехали.

В этой статье вы узнаете, как использовать PHP для управления следующим DDD-проектом вашей компании и эффективно моделировать реальные ситуации, чтобы помочь определить вашу бизнес-логику.

Предметно-ориентированное проектирование (Domain-Driven Design, в дальнейшем — DDD) — это методология разработки программного обеспечения для проектирования сложных программных проектов с целью доставки конечного продукта, который отвечает задачам организации. Фактически, DDD способствует фокусированию проекта на развивающейся базовой модели.
DDD научит вас эффективно моделировать реальный мир в вашем приложении и использовать ООП для инкапсуляции бизнес-логики организации.



Что такое модель предметной области?


На мой взгляд, модель предметной области (Domain Model) — это ваше восприятие контекста, к которому она относится. Попробую объяснить подробнее. «Область» сама по себе означает мир бизнеса, с которым вы работаете, и задачи, которые он предназначен решить. Например, если вы хотите разработать приложение для онлайн-доставки еды, в вашей предметной области все (задачи, бизнес-правила и т. д.) будет об онлайн-доставке еды, что необходимо реализовать в вашем проекте.

Модель предметной области — это ваше структурированное решение задачи. Она должна представлять собой словарь и ключевые понятия задач из предметной области.

Единый язык


«Единый язык» («Ubiquitous Language») — это язык, используемый бизнес-специалистами для описания модели предметной области. Это означает, что команда разработчиков последовательно использует этот язык во всех взаимодействиях и в коде. Язык должен основываться на модели области. Позвольте мне привести пример:

$product = new Entity\Product();
$product->setTitle(new Title('Mobile Phone'));
$product->setPrice(new Price('1000'));
$this->em->persist($product);
$this->em->flush();

В приведенном выше коде я создаю новый продукт, но в приложении продукт должен быть добавлен, а не создан:

//add is a static method in product class
$product = Product::add(
    new Title('Mobile Phone'),
    new Price('1000')
);

Если в команде разработчиков кто-то создает продукт, а кто-то добавляет, это нарушает единый язык. В данном случае, если в методе добавления продукта у нас есть дополнительные действия, такие как отправка электронных писем, все они будут пропущены, а также будет изменено определение добавления продукта в команду. У нас было бы два разных определения для одного термина.

Многослойная архитектура


В этой статье я не собираюсь говорить об объектно-ориентированном проектировании. Но DDD предполагает основы хорошего проектирования. Эрик Эванс (Eric Evans — автор книги “Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем”) считает, что разработка хорошей модели предметной области — это искусство.

Чтобы разработать хорошую модель предметной области, вам нужно знать о разработке, управляемой моделями (Model-Driven Design). Model-Driven Design — объединение модели и реализации. Многослойная архитектура является одним из ее блоков.

Многослойная архитектура (Layered Architecture) — это идея изоляции каждой части, основанная на многолетнем опыте и сотрудничестве разработчиков. Слои перечислены ниже:

  • Пользовательский интерфейс
  • Уровень приложения
  • Уровень домена
  • Уровень инфраструктуры

Пользовательский интерфейс отвечает за отображение информации пользователю и интерпретацию его команд. В Laravel отображение представляет собой слой пользовательского интерфейса (презентации). Уровень приложения — это способ общения с внешним миром (вне домена). Этот слой ведет себя как открытый API для нашего приложения. Он не содержит бизнес-правил или знаний. В Laravel контроллеры находятся именно здесь.

Уровень домена — это сердце бизнесс-ПО. Этот уровень отвечает за представление концепций бизнеса, информации о бизнес-ситуации и бизнес-правилах. Уровень инфраструктуры предоставляет общие технические возможности, которые поддерживают более высокие уровни, а также поддерживает структуру взаимодействий между четырьмя слоями (вот почему хранилища находятся в этом слое).

Связь между слоями является обязательной, но без потери преимуществ разделения. Связь происходит в одном направлении. Как видно из схемы выше, верхние слои могут взаимодействовать с более низкими уровнями. Если нижние слои должны соединяться с верхним слоем, они должны использовать такие шаблоны, как Callback или Observer.

Объекты-значения и сущности


Я большой поклонник объектов-значений (Value Objects). Я думаю, что они — суть ООП. Хотя объекты-значения DDD кажутся простыми, они являются серьезным источником замешательства для многих, включая меня. Я читал и слышал очень много разных способов описания объектов-значений с разных точек зрения. К счастью, каждое из разных объяснений, скорее помогло мне углубить понимание объектов-значений, нежели противоречило друг с другом.

Объекты-значения доступны по их значению, а не по идентификатору. Это неизменяемые объекты. Их значения не изменяются (или изменяются, но редко) и не имеют жизненного цикла (это означает, что они не как строки таблиц баз данных, которые можно удалить), например, валюты, даты, страны и т. д.

Вы можете создавать объекты-значения, которые вы не распознаете как объекты-значений. Например, адрес электронной почты может быть строкой, или это может быть объект-значение с собственным набором поведений.

Код, расположенный ниже, демонстрирует пример класса объекта-значения:

final class ImagesTypeValueObject 
{
    private $imageType;
    private $validImageType = ['JPEG', 'GIF', 'BMP', 'TIFF', 'PNG'];

    public function __construct($imageType) 
    {
        Assertion::inArray($this->validImageType, $imageType, 'Sorry The entry is wrong please enter valid image type');
        $this->imageType = $imageType;
    }

    public function __toString() 
    {
        return $this->imageType;
    }
}

Сущности — это объекты, доступные по идентификаторам в нашем приложении. Фактически, сущность представляет собой набор свойств, которые имеют уникальный идентификатор. Хорошим примером может служить ряд таблиц базы данных. Сущность изменчива, потому что она может изменять свои атрибуты (обычно с помощью сеттеров и геттеров), а также имеет жизненный цикл, то есть ее можно удалить.

Объект представляет из себя что-то с непрерывностью и идентичностью — что-то, что отслеживается в разных состояниях или даже в разных реализациях? Или это атрибут, описывающий состояние чего-то еще? Это основное различие между сущностью и объектом-значением.

Агрегаты


Модель может содержать большое количество объектов предметной области. Независимо от того, сколько всего мы предусмотрим при моделировании области, часто бывает, что многие объекты зависят друг от друга, создавая набор отношений, и вы не можете быть уверены в результате на 100%. Другими словами, вы должны знать о бизнес-правиле, которое всегда должно соблюдаться в вашей модели предметной области; только с этими знаниями вы можете с уверенностью рассуждать о своем коде.

Агрегаты помогают уменьшить количество двунаправленных ассоциаций между объектами в системе, потому что вам разрешено хранить ссылки только на корень. Это значительно упрощает проектирование и уменьшает количество слепых изменений в графе объектов. С другой стороны, агрегаты помогают с развязкой больших структур, устанавливая правила отношений между сущностями. (Примечание: агрегаты также могут иметь свойства, методы и инварианты, которые не вписываются в один класс)

Эрик Эванс в своей книге установил некоторые правила для реализации агрегатов, и я перечисляю их ниже:

  • Корневой объект имеет глобальную идентичность и в конечном счете отвечает за проверку инвариантов.
  • Корневые объекты имеют глобальную идентичность. Внутренние сущности имеют локальное идентичность, уникальную только внутри агрегата.
  • Ничто вне границы агрегата не может содержать ссылку на что-либо внутри, кроме корневого объекта. Корневой объект может связывать ссылки с внутренними объектами на другие объекты, но эти объекты могут использовать их только временно и не могут быть привязаны к ссылке.
  • В качестве следствия вышеприведенного правила, только базовые корни могут быть получены непосредственно с запросами базы данных. Все остальные объекты должны быть найдены путем обхода ассоциаций.
  • Объекты внутри агрегата могут содержать ссылки на другие совокупные корни.
  • Операция удаления должна удалять сразу все в пределах общей границы (со сборкой мусора это легко. Поскольку внешних ссылок на что-либо, кроме корня, нет, удалите корень и все остальное будет собрано).
  • Когда совершено изменение какого-либо объекта на границе агрегата, все инварианты агрегата должны быть удовлетворены.

Фабрики


В мире ООП Фабрика является объектом, который отвечает только за создание других объектов. В DDD фабрики используются для инкапсуляции знаний, необходимых для создания объектов, и они особенно полезны для создания агрегатов.

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

Бывают случаи, когда фабрика не нужна, и простого конструктора достаточно. Используйте конструктор, когда:

  • Конструкция не сложная.
  • Создание объекта не связано с созданием других, и все необходимые атрибуты передаются через конструктор.
  • Разработчик заинтересован в реализации и, возможно, хочет выбрать стратегию для использования.
  • Класс — тип. Нет иерархии, поэтому нет необходимости выбирать между списком конкретных реализаций.

Хранилища


Хранилище — это слой, который находится между доменом вашего проекта и базой данных. Мартин Фаулер (Martin Fowler) в своей книге «Шаблоны корпоративных приложений» пишет, что хранилище является промежуточным взаимодействие между доменом и слоем сопоставления данных с использованием интерфейса, подобного коллекции, для доступа к объектам домена.
Это означает, что вы должны думать о доступе к данным в своей базе данных так же, как и к стандартным объектам коллекции.

Позвольте мне объяснить немного подробнее. Представьте себе, что в DDD вам может понадобиться создать объект либо с помощью конструктора, либо с помощью фабрики; вы должны запросить его из корня агрегата. Проблема в том, что разработчик должен иметь ссылку на корень. Для больших приложений это становится проблемой, потому что необходимо убедиться, что разработчики всегда имеют ссылку на необходимый объект. Это приведет к созданию разработчиками ряда ассоциаций, которые на самом деле не нужны.

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

Если у нас нет хранилища, фокус домена будет потерян, а дизайн будет скомпрометирован. Поэтому, если разработчики используют запросы для доступа к данным из БД или вытягивают несколько конкретных объектов, логика домена перемещается в запросы и код разработчика, поэтому агрегаты будут бесполезны.

Наконец, хранилище выступает в качестве места хранения для объектов, доступных по всему миру. Хранилище также может включать в себя стратегию. Он может получить доступ к одному постоянному хранилищу или другому, основанному на указанной стратегии.

Реализация в Laravel


Как вы уже могли знать, лучшим выбором для внедрения DDD в PHP является Doctrine ORM. Чтобы реализовать агрегаты и хранилища, нам нужно внести некоторые изменения в наши сущности и создать некоторые файлы на нашем доменном уровне.

Я решил реализовать небольшую часть приложения, в котором пользователь может создать страницу или изменить ее. Каждая страница может содержать много комментариев, и каждый комментарий может содержать некоторые под-комментарии. Администраторы могут утверждать/отклонять комментарии после их добавления.

В приведенном выше сценарии первым шагом является создание базового хранилища в доменном уровне, базовом хранилище, полученном из Doctrine EntityRepository, который позволит нам иметь все встроенные функции хранилища Doctrine. Здесь мы также можем использовать нашу общую функциональность, и все наши хранилища должны наследоваться от нее. Реализация выглядит следующим образом:

namespace App\Domain\Repositories\Database\DoctrineORM;

use App\Domain\Events\Doctrine\DoctrineEventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManager;
use GeneratedHydrator\Configuration;
use Doctrine\Common\Collections\ArrayCollection;

abstract class DoctrineBaseRepository extends EntityRepository 
{
    public $primaryKeyName;
    public $entityName = null;

    public function __construct(EntityManager $em) 
    {
        parent::__construct($em, new ClassMetadata($this->entityClass));
        $this->primaryKeyName = $em->getClassMetadata($this->entityClass)->getSingleIdentifierFieldName();
    }
}

У нас есть два хранилища. Первый — это хранилище страниц, а второй — хранилище комментариев. Все хранилища должны иметь свойство entityClass для определения класса сущности. В этом случае мы можем инкапсулировать (private свойство) объект в наши хранилище:

namespace App\Domain\Repositories\Database\DoctrineORM\Page;
use App\Domain\User\Core\Model\Entities\Pages;
use App\Domain\Repositories\Database\DoctrineORM\DoctrineBaseRepository;

class DoctrinePageRepository extends DoctrineBaseRepository 
{
    private $entityClass = Pages::class;

    public function AddComments($pages) 
    {
        $this->_em->merge($pages);
        $this->_em->flush();
    }
}

Я использую командную строку Doctrine для генерации сущностей:

namespace App\Domain\Interactions\Core\Model\Entities;

use App\Domain\User\Comments\Model\Entities\Comments;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Pages
 *
 * @ORM\Table(name="pages")
 * @ORM\Entity
 */
class Pages
{
    /**
     * @var string
     *
     * @ORM\Column(name="page_title", type="string", length=150, nullable=false)
     */
    private $pageTitle;
    /**
     * @ORM\OneToMany(targetEntity="App\Domain\User\Comments\Model\Entities\Comments", mappedBy="pageId", indexBy="pageId", cascade={"persist", "remove"})
     */
    private $pageComment;
    /**
     * @var integer
     *
     * @ORM\Column(name="page_id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $pageId;

    public function __construct()
    {
        $this->pageComment = new ArrayCollection();
    }

    /**
     * @param Comment
     * @return void
     */
    public function addComments(Comments $comment)
    {
        $this->pageComment[] = $comment;
    }
//... other setters and getters.
}

namespace App\Domain\User\Comments\Model\Entities;

use Doctrine\ORM\Mapping as ORM;

/**
 * Comments
 *
 * @ORM\Table(name="comments")
 * @ORM\Entity
 */
class Comments
{
    /**
     * @ORM\ManyToOne(targetEntity="App\Domain\User\Core\Model\Entities\Users")
     * @ORM\JoinColumn(name="users_user_id", referencedColumnName="id")
     */
    private $usersUserId;
    /**
     * @ORM\ManyToOne(targetEntity="comments", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="comment_id")
     */
    private $parentId;
    /**
     * @ORM\ManyToOne(targetEntity="App\Domain\Interactions\Core\Model\Entities\pages", inversedBy="pageComment" )
     * @ORM\JoinColumn(name="page_id", referencedColumnName="page_id")
     */
    private $pageId;
    /**
     * @ORM\OneToMany(targetEntity="comments", mappedBy="parent")
     */
    private $children;

    /**
     * @param Page
     * @return void
     */
    public function __construct()
    {
        $this->children = new\Doctrine\Common\Collections\ArrayCollection();
    }
}

Как вы можете видеть, в приведенном выше коде я определяю отношения в аннотациях объектов. Реализация отношений в Doctrine ORM может показаться очень сложной, но на самом деле это не так сложно, когда вы познакомитесь с тем, как все работает. Единственный способ добавить комментарии — это вызвать addComments объекта page, и этот метод принимает только объект сущности comment в качестве входных данных. Это сделает нас уверенными в функциональности нашего кода.

Мой агрегат выглядит так:

namespace App\Domain\Comment;

use App\Domain\User\Comments\Model\Entities\Comments;
use App\Domain\Repositories\Database\DoctrineORM\User\DoctrineCommentRepository;
use App\Domain\Repositories\Database\DoctrineORM\Interactions\DoctrinePagesRepository;
use Assert\Assertion;

class PageAggregate
{
    public $Pages;
    public $pageResult;
    public $parentId = null;
    public $comments;
    public $DoctrineRepository = DoctrinePagesRepository::class;

    public function __construct($id, $comments = null, $administrative = null)
    {
        $this->DoctrineRepository = \App::make($this->DoctrineRepository);
        Assertion::notNull($this->pageResult = $this->DoctrineRepository->findOneBy(['pageId' => $id]), 'sorry the valid page id is required here');
        $commentFacory = new Commentfactory($this->pageResult, $comments);
        return $commentFacory->choisir($administrative);
    }
}

Нам нужен агрегат, который отвечает за ограничение доступа к комментариям, если PageId валидный; Я имею в виду, что без PageId доступ к comments невозможен. Скажем, comments без действительного page id не имеют смысла и недоступны. Кроме того, существует метод фабрики comment, который помогает нам инкапсулировать бизнес-правила.

Метод фабрики:

namespace App\Domain\Comment;
interface CommentTypeFactoryInterface
{
    public function confectionner();
}

namespace App\Domain\Comment;
interface CommentFactoryInterface
{
    public function choisir();
}

Я определил две фабрики. Первая — тип комментариев, а вторая — интерфейсы комментариев, которые делают ее обязательной для каждого комментария при реализации метода choisir.

namespace App\Domain\Comment;

use App\Application\Factory\Request\RequestFactory;

class Commentfactory implements CommentFactoryInterface
{
    private $page;
    private $comment;
    private $parentId;

    public function __construct($page, $comment = null)
    {
        $this->page = $page;
        $this->comment = $comment;
    }

    public function choisir($administrative = null)
    {
        // TODO: Implement choisir() method.
        if (is_null($administrative)) {
            $comment = new Comment($this->page, $this->comment);
            return $comment->confectionner();
        }
        $comment = new AdministrativeComments($this->page, $this->comment, $this->parentId);
        return $comment->confectionner();
    }
}

Метод фабрики Comment предоставляет внутренние части агрегата.

namespace App\Domain\Comment;

use App\Domain\User\Comments\Model\Entities\Comments;
use App\Domain\Repositories\Database\DoctrineORM\User\DoctrineCommentRepository;
use App\Domain\Repositories\Database\DoctrineORM\Interactions\DoctrinePagesRepository;
use App\Domain\Interactions\Core\Model\Entities\Pages;
use App\Application\Factory\Request\RequestFactory;
use Assert\Assertion;

class Comment implements CommentTypeFactoryInterface
{
    private $page;
    private $comments;
    public $DoctrineCommentRepository = DoctrineCommentRepository::class;
    public $DoctrineRepository = DoctrinePagesRepository::class;

    public function __construct(Pages $page, $comment)
    {
        $this->page = $page;
        $this->comments = $comment;
        $this->DoctrineCommentRepository = \App::make($this->DoctrineCommentRepository);
        $this->DoctrineRepository = \App::make($this->DoctrineRepository);
    }

    public function confectionner()
    {
        if (is_array($this->comments)) {
            \Request::replace($this->comments['data']);
            \App::make(RequestFactory::class);
            $this->addComments();
        } elseif (is_null($this->comments)) {
            return $this->retrieveComments();
        } elseif (is_int($this->comments)) {
            $this->deleteComment();
        }
        return true;
    }

    private function addComments()
    {
        if (isset($this->comments['id']) && !is_null($this->comments['object'] = $this->DoctrineCommentRepository->findOneBy(['commentId' => $this->comments['id']]))) {
            return $this->editComment();
        }
        $this->comments = $this->CommentObjectMapper(new Comments(), $this->comments['data']);
        $this->page->addComments($this->comments);
        $this->DoctrineRepository->AddComments($this->page);
    }

    private function editComment()
    {
        $comment = $this->CommentObjectMapper($this->comments['object'], $this->comments['data']);
        $this->page->addComments($comment);
        $this->DoctrineRepository->AddComments($this->page);
    }

    private function deleteComment()
    {
        $this->DoctrineCommentRepository->delComments($this->comments);
    }

    private function retrieveComments()
    {
        return $this->page->getPageComment();
    }
    //...
}

namespace App\Domain\Comment;

use App\Domain\Interactions\Core\Model\Entities\Pages;
use App\Domain\Repositories\Database\DoctrineORM\User\DoctrineCommentRepository;
use App\Domain\Repositories\Database\DoctrineORM\Interactions\DoctrinePagesRepository;
use App\Domain\User\Comments;
use Assert\Assertion;

class AdministrativeComments implements CommentTypeFactoryInterface
{
    private $page;
    private $comments;
    private $parentId;
    private $privilege;
    public $DoctrineCommentRepository = DoctrineCommentRepository::class;
    public $DoctrineRepository = DoctrinePagesRepository::class;

    public function __construct(Pages $page, $comment, $parentId)
    {
        $this->page = $page;
        $this->comments = $comment;
        $this->parentId = $parentId;
        $this->privilege = new Athurization(\Auth::gaurd('admin')->user());
    }

    public function confectionner()
    {
        $action = $this->comments['action'];
        Assertion::notNull($this->comments = $this->DoctrineCommentRepository->findOneBy(['commentId' => $this->comments['id']]), 'no Valid comment Id');
        $this->$action;
        return true;
    }

    public function approve()
    {
        $this->privilege->isAuthorize(__METHOD__);
        $this->DoctrineCommentRepository->approve($this->comments, \Auth::gaurd('admin')->user());
    }

    public function reject()
    {
        $this->privilege->isAuthorize(__METHOD__);
        $this->DoctrineCommentRepository->reject($this->comments, \Auth::gaurd('admin')->user());
    }

    public function delete()
    {
        $this->privilege->isAuthorize(__METHOD__);
        $this->DoctrineCommentRepository->delete($this->comments, \Auth::gaurd('admin')->user());
    }
}

Как вы видите в приведенном выше коде, у нас есть два класса: Comment и AdministrativeComments. Commentfactory будет решать, какой класс нужно использовать. Некоторые ненужные классы или методы, такие как класс Authorization и метод reject, здесь опущены. Как вы можете видеть в приведенном выше коде, я использую RequestFactory для валидации. Это другой метод фабрики в нашем приложении, который отвечает за проверку входных данных. Этот вид проверки имеет определение в DDD, а также добавлен в laravel 5+.

Заключение


Для покрытия всех этих определений потребуется много статей, но я сделал все возможное, чтобы обобщить их. Это был всего лишь простой пример корня агрегата, но вы можете создать свой собственный сложный агрегат, и я надеюсь, что этот пример поможет вам.

THE END

Как всегда ждём комментарии, вопросы тут или у нас на Дне открытых дверей.

Комментарии (31)


  1. sayber
    13.04.2018 21:53
    +2

    У вас код немного поехал. Имеется пробел с двух сторон у указателя ->, тоже и в неймспейсах.
    Из-за этого, читабельность кода в некоторых местах сходит на нет.


    1. MaxRokatansky Автор
      13.04.2018 21:56

      Хм. Счас поправлю. Спасибо, почему-то на превью я этого не заметил


      1. samizdam
        14.04.2018 10:50

        «Счас» — у вас и в тексте так...


        1. MaxRokatansky Автор
          14.04.2018 11:01

          Сломался вчера на половине :( Доделаю через несколько часов…


          1. zelenin
            14.04.2018 15:22

            на половине чего? ни один блок кода не исправлен.


            1. MaxRokatansky Автор
              15.04.2018 14:31

              На половине того, что было тогда в черновике у меня.


  1. tsukasa_mixer
    13.04.2018 21:56
    +1

    Уж простите придерусь.
    Как ужастно оформлен код


    1. MaxRokatansky Автор
      13.04.2018 21:57

      Уехало почему-то. Поправлю.

      P.S. простите тоже придерусь, но «ужаСНо» :)


      1. tsukasa_mixer
        13.04.2018 22:08

        Вечер, пятница, опять работа.
        Бессмысленный и стремный код.
        Сидишь читаешь тут газету,
        А стремный код опять и тут…

        =)


        1. SerafimArts
          13.04.2018 22:35
          +1

          О, там есть шедевры, вроде «Атхуизация гаурда»:

          $this - > privilege = new Athurization(\Auth::gaurd('admin') - > user());

          Это просто Epic Win, по-моему.


          1. tsukasa_mixer
            14.04.2018 00:04

            Да ладн спишем все на пятницу =)
            Но парсер в мозгу изрядно так сломался при прочтении.


            1. SerafimArts
              14.04.2018 00:11

              Я бы списал на автора, а не пятницу. Это уже не первый раз, когда выплёскивается подобное, кхм… Автор обещает поправить и кладёт большой болт.


              1. BoShurik
                14.04.2018 00:14

                Судя по источнику перевода, это у автора стиль такой, а не у переводчика


              1. MaxRokatansky Автор
                14.04.2018 00:30

                По одному случаю как-то странно судить. В тексте перед так же указали на ошибки — всё исправлено.
                С кодом мне сложнее работать почему-то, глаза быстрее устают — вот и торможу.


  1. zelenin
    13.04.2018 22:03

    а это что за кодстайл? в каких языках так принято?


    1. MaxRokatansky Автор
      13.04.2018 22:28

      В криворуких пятничных :(


  1. oxidmod
    13.04.2018 22:04
    +1

    Както у вас инфраструктура в домен протекла


  1. SerafimArts
    13.04.2018 22:26

    удалено


    1. MaxRokatansky Автор
      13.04.2018 22:30

      Сам офигел и проглядел. Счас сижу правлю, так что особо не напрягайтесь, скоро будет сделано нормально.


      1. SerafimArts
        13.04.2018 22:34

        Ой, а я удалил коммент, т.к. увидел, что уже куча подобного рода набежало. А вы ответили. Некрасиво с моей стороны получилось.


        1. MaxRokatansky Автор
          13.04.2018 22:35

          Ничего страшного. Претензия-то по делу


          1. SerafimArts
            14.04.2018 00:09

            Хорошо. А когда поправите вот это? Полтора месяца прошло: habrahabr.ru/company/otus/blog/350368


            1. MaxRokatansky Автор
              14.04.2018 00:29

              Фигассе… Вообще был уверен, что исправил, видимо задолбался и не сохранил. Завтра сделаю уже — у меня на это тексте глаза поплыли.

              Прошу прощения


  1. ButscH
    13.04.2018 23:36

    Пара вопросов:

    — Почему у вас какие то параметры именуются $comment, а какие то $DoctrineRepository? Почему разный стиль?
    — Почему бы не использовать, хотя бы при создании репозиториев принцип инверсии зависимостей? Куда приятнее работать с интерфейсами, а не с реализацией, благо в Laravel это достаточно легко реализуется.

    Вот кстати у меня вопрос, я как человек мало понимающий в DDD и в вашем коде,

    class Comment implements CommentTypeFactoryInterface {
        private function addComments()
        private function editComments()
        private function deleteComment()
        private function retrieveComments()
    }
    

    Кто и откуда запускает эти методы?

    В методе editComments() вызывает метод $this - > page - > addComments($comment);, но ведь DDD подразумевает, что мы общаемся на языке бизнеса, а получается, что редактирование комментария у нас приравнивается к добавлению?

    Хотелось бы более детального разбора кода.


  1. r-moiseev
    14.04.2018 18:31
    +1

    Было бы круто если бы вы форматировали код по PSR, читать сложно


  1. ivorobioff
    15.04.2018 00:18

    Почему публичные свойства и почему \App::make в конструкторе вместо нормальной инъекции зависимостей?

    Вот этот момент

    public $DoctrineCommentRepository = DoctrineCommentRepository::class;
    ...
    $this - > DoctrineCommentRepository = \App::make($this - > DoctrineCommentRepository);


    вообще убойный

    Очень стремная организация кода… что за паттерн такой? :)


  1. L0NGMAN
    15.04.2018 04:01

    1. Стоит использовать Assertion в продакшене? В мануале говорится что нет php.net/manual/en/function.assert.php
    2. Должен знать ValueObject о сушестыовании конфига? Если нет то как передать на пример допустимые расширения для картинки?
    3. Можете привести реальный пример использования аггрегатов? На примере того же ПО доставки еды. Я как понимаю это сервисы где происходит валидация, вызивается моделы и репозитории


    1. SerafimArts
      15.04.2018 12:27

      Стоит использовать Assertion в продакшене? В мануале говорится что нет


      1. Это не те ассершены, что являются частью php.
      2. Ассершены отключаются в продакшене с помощью конфигов в ini, а не с помощью вырезания их из кода.


    1. VolCh
      15.04.2018 13:18

      2. В целом не должны. Обычно это тупые объекты. Можно передавать значения из конфигов в конструкторы/фабрики. Можно передавать во внутренний валидатором типа Image.hasValidExt($validExts), можно во внешний. В целом можно сделать и конфиг, особенно в PHP, где конфиг может быть обычным PHP-кодом, но это должен быть конфиг конкретного VO, а не часть общего конфига приложения.

      3. Агрегаты не сервисы, это сущности (корни агрегата), инкапсулирующие в себя другие сущности.


  1. VolCh
    15.04.2018 13:03

    Контроллеры Laravel и подобных MVC-like фреймворков — это не слой приложения, это слой UI. Слой приложения — это модель в MVC триаде, фасад (в широком смысле слова) к домену и инфраструктуре.

    Общее правило, отклонения от которого должны быть очень обоснованы — один репозиторий на один агрегат. Репозитории страниц и комментариев, связанных друг с другом не скалярными ссылками, а обьектными — очень сильно попахивают нарушением границ агрегатов. Или комментарии — это отдельный агрегат, хранящий символьную ссылку на страницу (string/int pageId, а не Page page), или комментарий нельзя получить без получения страницы по id, id комментария должен использоваться только внутри Page/PageAgregate.


  1. VolCh
    15.04.2018 13:40

    В целом при использовании фреймворков общего назначения типа Laravel или Symfony, всему коду, связанному с ним место в слое UI/инфраструктуры, ни слой приложения, ни, тем более, слой домена не должны о фреймворке ничего знать. То же и с ORM — только это исключительно слой инфраструктуры. Репозитории Doctrine не должны быть репозиториями домена или их предками. В крайнем случае репозитории Доктрины должны реализовывать интерфейс, объявленный в домене, использующий только термины домена и в слоях UI, приложения и, тем более, домена, не должно использоваться знание того, что реализация этого интерфейса является классом, унаследованным от репозитория Доктрины. Обычно проще сделать репозиторий Доктрины свойством в реализации репозитория домена, используя паттерн адаптер, приводящий вызовы методов репозитория домена к вызовам репозитория Доктрины.