Как назвать метод?

Вурдалак

Продвинутый новичок
но получил ответ - тут нет связи много ко многим... в твоей вселеной НИКОГДА не может быть связи много ко многим в принципе в домене?
Не нужно путать many-to-many и циклическую зависимость (bidirectional mapping). Может быть many-to-many и unidirectional.
 

Redjik

Джедай-мастер
Я называю в данном контексте «магией» любую модификацию объекта модели внутри репозитория.
любая реляционная база автоматом синхронизирует One to Many, эта магия по твоему? нет - просто так реляционные базы устроены, прописал fk в одной таблице, все - можешь вытаскивать данные из обеих таблиц хоть в каком направлении, хоть для первой сущности, хоть для второй.

а вот если объекты в памяти или в key-value хранилище, то да, надо синхронизировать

Потому что я пока не знаю как сделать лучше, я делюсь сомнениями. Вывод книг автора, например, решается элементарно через $bookRepository->findByAuthor().
откуда он у тебя там возмется, ты же за то, чтобы у них не было связи... инжектить в конструктор $bookRepository AuthorBookRepository? и из него вытаскивать? не проще напрямую из AuthorBookRepository?
 

Redjik

Джедай-мастер
Не нужно путать many-to-many и циклическую зависимость (bidirectional mapping). Может быть many-to-many и unidirectional.
не может - ты иначе сущности не синхронизируешь, я уже в 100ый раз об этом говорю... можно, если оставить на откуп репозиторию, но ты же говоришь, что это магия
 

Redjik

Джедай-мастер
ну или давай подумаем - я за конструктив =)))
давай попробуем другой пример
товар - корзина, many-many же?
 

Redjik

Джедай-мастер
кстати, я тоже умею находит статьи непонятных товарищей
http://stackoverflow.com/questions/7014255/ddd-repository-and-many-to-many-relationship
Correctness of this logic would depend on how the repository is written and it's contract. If the repository will not persist any relationships, then it could be a sign of a poorly designed repository. I would expect a repository to implement the necessary logic to store entity associations as well, and not just the simple properties of the entity.

A repository based on an ORM solution would have made this easier by through the cascading of persistence events
или вот
http://www.udidahan.com/2009/01/24/ddd-many-to-many-object-relational-mapping/
где как бе чувак задачу решил, правда изначально он вообще другую задачу ставил =)
при этом в финале он грит - ой да ладно если что можно через query вытащить...
идеальный пример того, что люди начинают гребсти под гребенку DDD все подряд, извращают изначальную задачу, меняют ее, лишь бы по канонам DDD (в их понимании) все сошлось и в итоге еще и костыляют

ладно пофиг... попробую пока по моему, если начну упиратся в косяки - пересмотрю еще раз концепцию
 

whirlwind

TDD infected, paranoid
Опять же, мне не понятно как EditableUser extends User тут может помоч, Ведь мне нужно будет User пришедший извне подменить на EditableUser для дальнейших манипуляций.
А вот для этого, я говорил, нужны фабричные методы и делегаты. Снаружи не должно быть никаких "извне" потому что сущность это часть репозитория (репозиторий это агрегат). Это примерно как передавать автомобилю колеса, для того, что бы повернуть налево. Где можно оперировать колесами отдельно от авто? Только на заводе или в мастерской (factory/builder). Именно репозиторий должен знать все тонкости реализации объектов, которыми он владеет. Не нужно говорить, что new Order где-то в коде это нормально. Это не нормально, потому что это гарантирует проблемы для потребителей при изменении модели заявки. Это допустимо для Money или DateTime, которые имеют ультраустойчивый интерфейс, но не для entities.
 

Sufir

Я не волшебник, я только учусь
Да, я понял. "Извне" никаких сеттеров, никаких геттеров, никаких new. При этом примера я так и не увидел. Я не спорю ни в коем случае, я только разобраться пытаюсь.
PHP:
// Колёсами отдельно от авто нигде, а вот авто отдельно от гаража или стоянки везде. Я себе это представляю вот как:
$auto = $garage->getAuto($identity);
$auto->drive();
$auto->turnLeft();
PHP:
// Из твоего описания у меня выходит как-то так (причем всё равно остаётся непонятным как в сервисе колёса будут менять у авто, или сервис тоже будет его частью?):
$auto->loadSelf($identity);
$auto->drive();
$auto->turnLeft();
Я об этом же и спрашиваю. Как мне адекватно реализовать возможность "оперировать колёсами отдельно от авто" исключительно на заводе или в мастерской, при этом не давать этого делать где-либо ещё.
PHP:
$rep->buildProduct($data);
$rep->saveCurrentProduct()

$rep->buildOrder($data);
$rep->addProductInOrderById($orderId, $productId1);
$rep->addProductInOrderById($orderId, $productId2);

$rep->orderApprove($orderId);

$rep->updateProduct($productId, $data);
Это будет выглядеть как-то так? Выглядит малость стрёмно. Покажи на конкретном примере, на Products и Orders. Всё просто продукты нужно создавать, редактировать и добавлять/удалять в заказы, у продуктов есть целый ряд свойств в том числе некоторые features[] в качестве подсущностей. Причем в первую очередь меня тут интересует как-раз управление словарем продуктов с features[] (которые к тому же могут быть вложенными и иметь свои дочерние features)
 
Последнее редактирование:

Redjik

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

По поводу товаров и продуктов, https://vaughnvernon.co/?p=838
Там ссылки на pdf ки...

На основе этих докладов вот что получается.
1) 2 aggreagate root - Product и Order, мы не можем сохранять все скопом, ибо нельзя сохранять целиком заказ в транзакции, один из товаров в заказе может измениться... А фишка aggregate root именно в транзакциях и сохранении скопом всего подряд. Не только заказа, но и товара.
2) из первого вытекает, что связи надо строить через id, то есть
PHP:
class Order
{
    public function addProduct(Product $product)
    {
        $this->productIds[]=$product->getIdentity()->getId();
    }
    //нет метода getProducts в принципе

    public function getProductIds()
    {
        return $this->productIds;
    }
}
3) Синхронизация опять же на уровне репо
 

Sufir

Я не волшебник, я только учусь
По поводу заказа, это верно, тут лучше работать с ID и нет никаких причин через заказ изменять/сохранять продукты. Впрочем тут меня больше интересует вопрос как раз управления словарем продуктов. В частности features не существуют отдельно от продукта и не должны иметь собственного репа. Плюс, опять же, может быть целая куча других редактируемых свойств.
 

Redjik

Джедай-мастер
Sufir, по поводу колес и машин, у меня получилось =)))))
но кривовато =)))))))))))
но мне нравится =))))))))))))))))))))))

Короче вот, что получилось
PHP:
class CarIdentity
{
    private $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

    /**
    * @return mixed
    */
    public function getId()
    {
        return $this->id;
    }
}

class Car
{
    const NUM_OF_WHEELS = 4;
    /**
    * @var CarIdentity
    */
    private $identity;

    /**
    * @return CarIdentity
    */
    public function getId()
    {
        return $this->identity->getId();
    }
}

class Wheel
{
    private $carId;

    public function attachToCar(CarIdentity $identity)
    {
        $this->carId = $identity->getId();
    }

    public function detachFromCar()
    {
        $this->carId = null;
    }
}

class WheelService
{
    public function changeAllWheels(Car $car)
    {
        /** @var Wheel[] $wheels */
        $wheels = $this->wheelRepo->findWheelsOfCar($car);
        //тут можно напихать проверко на общее колво колес, и если вдруг колес меньше чем в NUM_OF_WHEELS, то эксепшен
        // или ветвление какое-нибудь

        foreach ($wheels as $wheel)
        {
            $wheel->detachFromCar();
            $this->wheelRepo->save($wheel);
            //сняли все колеса и сохранили
        }

        /** @var Wheel[] $newWheels */
        $newWheels = $this->wheelRepo->findNewWheels(Car::NUM_OF_WHEELS);
        foreach ($newWheels as $wheel)
        {
            $wheel->attachToCar(new CarIdentity($car->getId()));
            $this->wheelRepo->save($wheel);
            //накинули нове колеса и сохранили
        }
       
        //конечно это очень черновой вариант и мы не учитываем сейчас различные фейлы, типа сняли все колеса а новых только 2... итп
        //но можно реально добиться более менее хорошего результата по UserExperience именно в этом методе
    }
}
 

Sufir

Я не волшебник, я только учусь
Да, с колёсами/машинами остроумно выглядит.
Ну, я про простейшие действия имею в виду. Должна быть возможность добавлять новые и редактировать имеющиеся продукты, в том числе изменять их различные свойства, добавлять и удалять features[]. Features - подсущности, которые как раз нужно сохранять скопом вместе с продуктом, а у самих features могут быть дочерние features.

Вот что-то такое:
PHP:
$product = $rep->getById(123);

$product->setDescription($desc); // Но у нас не должно быть сеттеров?

$feature = new Feature(); // Но мы не должны пользоваться new (ладно уберём кудато в Factory::create() это разумно)
$product->addFeature($feature);

$feature->addSubFeature(FeatureFactory::createSomeFeature());

$product->removeFeature($feature);

$product->getFeature(123)->removeSubFeature($feature);
Как это правильнее выразить в DDD? Впрочем ладно, есть у меня некоторое представление, но довольно смутное. Нужно попробовать это в реальном коде реализовать хоть минимально, тогда думаю многое на места станет и яснее будет, что к чему и проблемы виднее...
 

Redjik

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

whirlwind

TDD infected, paranoid
Да, я понял. "Извне" никаких сеттеров, никаких геттеров, никаких new. При этом примера я так и не увидел. Я не спорю ни в коем случае, я только разобраться пытаюсь.
PHP:
// Колёсами отдельно от авто нигде, а вот авто отдельно от гаража или стоянки везде. Я себе это представляю вот как:
$auto = $garage->getAuto($identity);
$auto->drive();
$auto->turnLeft();
Этот первый пример правильный. Только нужно осознать, что гараж это не репозиторий, а сервис. Гараж и автомобиль могут существовать отдельно друг от друга? Могут. Значит гараж не владеет автомобилем, он его обслуживает. И это основание для сводного корня. Новый сервис = новый корень.

PS. Немного добавлю. Вот хороший пример с автомобилем в гараже. Нужна ли функция turnLeft для автомобиля в гараже? Для проверки работоспособности рулевой колонки вполне. Но нужна-ли такая низкоуровневая функция для бухгалтера? Абсолютно нет. Это собственно ответ. У этих автомобилей могут быть разные интерфейсы при получении из разных сервисов, даже если по существу это один и тот же автомобиль. Грубо говоря, сервис-сводный корень это декоратор на базовую сущность.

Предположим, у тебя организация автопарк. Ну и как разруливать все автомобили? Через гараж? А те, которые на трассе? А амортизация, а лизинги, аренда, водители, базы и прочая бухгалтерия? Если ты будешь усложнять систему путем внесения изменений в модель автомобиля, это будет создавать ком, который в итоге загубит систему, потому что это будет настолько сложно понять, что быстрее, дешевле и удобнее заново или отдельно написать. Нужна декомпозиция. Репозиторий в данном случае будет некий реестр автомобилей, оторванный от всех деталей, кроме идентификационных или характеризующих. А все остальное доступно через сводные корни-сервисы. Например, покупка авто будет выполняться через сервис, который в итоге создаст все нужные связи для нового автомобиля. И если подразумевается, что автомобиль после покупки пригоняется в гараж, так сервис покупки маякнет кому нужно и авто уже будет в гараже после покупки. Человеку, который оформляет покупку нет необходимости знать все эти детали про гаражи и балансы всякие. Все что ему нужно в контексте задачи - зафиксировать покупку и сохранить первичку. Какое отношение стоимость покупки имеет к технику в гараже или водителю? Ну строго говоря никакого. Значит это один автомобиль, но разные связи с ним в контексте разных задач. Можно разрабатывать постепенно, не аффектя то что уже написано. Принцип DDD - программные сущности должны отражать сущности предметной области бизнес процесса, а не абстрактные паттерны. Этими сущностями вы должны общаться со всеми. Базовые понятия должны быть понятны абсолютно всем участникам системы. Разделите понятия по участникам системы и станет понятно как лучше организовать программные компоненты. То есть domain-driven.
 
Последнее редактирование:

Redjik

Джедай-мастер
Вурдалак, эй пацан, псс, есть чо по виджетам?

для устранения неясностей, обзовем виджетом некий кусок, рендерещийся в шаблоне — топ комментариев, облако тегов, последние 3 статьи и т.п.
есть два варианта у меня, архитектура общая
иду не по CQRS, хотя можно и в разрезе этого подхода обсудить

FooController::__construct(RequestInterface $request, FooResponder $responder)

Другими словами, FooController в конструкторе получает Request и Responder, экшены возвращают Response
Что такое Responder — это Сервис-фабрика для генереации Response
В зону ответственности Responder входят: заголовки, шаблонизатор
Что мы получаем от такого подхода:
- убираем наконец заголовки из контроллеров
- инкапсулируем View (вместо $this->render('foo',['articel'=>$article]) вызываем $this->responder->renderArticle($article)), то есть общение с View только по публичному интерфейсу с тайпхинтингом

И вот тут вопрос как рендерить виджеты, непосредственно способы:
1) Виджеты хранятся в слое Presentation, при этом Responder в конструкторе получает контейнер, который lazy load'ит виджеты, ну или как то по-другому он их получает... это не важно... главное зона ответственности, Responder знает о виджетах и может их вызывать. Каждый виджет — сервис, прописанный в DIC.
Ну например последние статьи.
LatestArticlesWIdget::__construct(ArticlesRepository $repo)
WidgetInterface::render(TemplatingInterface $templating)

2) В случае с Симфони прокидываем HttpKernel в Responder и оттуда (или из templating) делаем sub-request
мне этот вариант не нравится, ибо я принципиально против подзапросов и различных forward костылей

что думаешь? есть еще варианты?
 

Redjik

Джедай-мастер
:( не нравится? или слишком поток сознания и надо кода накидать?
 

fixxxer

К.О.
Партнер клуба
Redjik, я тоже ничего не понял. Наверное, надо начать с того, в чем состоит проблема, и чем не устраивают ее стандартные решения.
 

Redjik

Джедай-мастер
блин, а в голове так все стройно выглядит, okay, сейчас код набросаю
 

Вурдалак

Продвинутый новичок
Не, ну я примерно понял, только как это с этой темой связано? :) Здесь же именование метода обсуждали.

LatestArticlesWIdget::__construct(ArticlesRepository $repo)
Виджет же ничем не отличается от обычного контроллера или console command в плане ответственности с точки зрения 4-х слойной архитектуры. Поэтому наличие там доменных сущностей выглядит странно, он должен взаимодействовать с service layer, т.е. каким-то ArticleQueryService, например, который возвращает массив ArticleDTO, которые можно сериализовать как хочешь, вывести и т.д.
 
Сверху