Giter VIP home page Giter VIP logo

php-ddd's People

Contributors

webdevilopers avatar yvoyer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

php-ddd's Issues

Naming Interfaces - To (pre- / suf)fix or not to fix

For instance @symfony uses the Interface Suffix e.g. FooRepositoryInterface:

I've seen C# and @dotnet examples prefixing Interfaces with an I e.g. IFooRepository:

Why the prefix in C# but not in Java?

The difference between Java and C# is that Java allows you to easily distinguish whether you implement an interface or extend a class since it has the corresponding keywords implements and extends.

As C# only has the : to express either an implementation or extension, I recommend following the standard and put an I before an interface's name.

Or:

Because of that, I don't think conventions are nearly as important in java for interfaces, since there is an explicit difference between inheritance and interface implementation.

Further links for discussion:

Since PHP offers the implements keyword I think it is best practice to NOT USE any prefix or suffix.
At least inside the Domain Model namespace:

  • Domain\Model\Foo\Foo.php
  • Domain\Model\Foo\FooId.php
  • Domain\Model\Foo\FooRepository.php <-- Interface

While working with Symfony you should adapt their naming conventions.

Personally and since I started to put all my Interfaces into my Application or Infrastructure namespace I don't use any prefix or suffix at all anymore.

Related:

Creating event-sourced aggregate roots through CQRS read models

Came from:

/cc @JulianMay

An EmploymentContract is an event-sourced Aggregate Root (A+ES). It holds a reference to a A+ES Person.

<?php

namespace AcmersonnelManagement\Domain\Model\EmploymentContract;

final class EmploymentContract extends AggregateRoot
{
    /** @var EmploymentContractId $employmentContractId */
    private $id;

    /** @var PersonId $personId */
    private $personId;

    /** @var EmploymentPeriod */
    private $employmentPeriod;

    public static function sign(
        EmploymentContractId $anId, PersonId $aPersonId, PersonalData $aPersonalData, ContractType $aContractType,
        DateTimeImmutable $aStartDate, ?DateTimeImmutable $anEndDate, ?DateTimeImmutable $aProbationEndDate,
        WorkerCategory $aWorkerCategory, WageType $aWageType, bool $aWorkingTimeAccount,
        WorkweekDays $aWorkWeekDays, WeeklyWorkingHours $aWeeklyWorkingHours,
        HolidayEntitlement $aHolidayEntitlement, AdditionalLeave $additionalLeave,
        JobFunctionId $aJobFunctionId, string $aJobFunctionName,
        EmployerId $anEmployerId, string $anEmployerName, WorkplaceId $aWorkplaceId, string $aWorkplaceName
    ): EmploymentContract
    {
        $employmentPeriod = EmploymentPeriod::withType($aContractType, $aStartDate, $anEndDate);

        $self = new self();
        $self->recordThat(EmploymentContractSigned::withData(
            $anId, $aPersonId, $aPersonalData, $aContractType, $employmentPeriod, $aPbationaryPeriod,
            $aWorkerCategory, $aWageType, $aWorkingTimeAccount,
            $aWorkWeekDays, $aWeeklyWorkingHours,
            $aHolidayEntitlement, $additionalLeave,
            $aJobFunctionId, $aJobFunctionName,
            $anEmployerId, $anEmployerName, $aWorkplaceId, $aWorkplaceName,
            new DateTimeImmutable()
        ));

        return $self;
    }

    protected function apply(AggregateChanged $event): void
    {
        switch (get_class($event)) {
            /** @var EmploymentContractSigned $event */
            case EmploymentContractSigned::class:
                $this->id = $event->contractId();
                $this->personId = $event->personId();
                $this->employmentPeriod = $event->employmentPeriod();
                break;
        }
    }

    public function aggregateId(): string
    {
        return $this->id->toString();
    }
}

Employment periods of contracts for a person must NOT overlap. This is ensured by a OverlappingEmploymentContractPolicy that currently is called inside the command handler. The handler has to get the existing contracts of a Person from a read model repository.

<?php

namespace Acme\PersonnelManagement\Application\Service\EmploymentContract;

final class SignEmploymentContractHandler
{
    /** @var EmploymentContractEventStoreRepository */
    private $contractCollection;

    /** @var PersonDetailsRepository */
    private $personDetailsRepository;

    /** @var ContractDetailsRepository */
    private $contractsDetailsRepository;

    public function __construct(
        EmploymentContractEventStoreRepository $contractCollection,
        PersonDetailsRepository $personDetailsRepository,
        ContractDetailsRepository $contractsDetailsRepository
    )
    {
        $this->contractCollection = $contractCollection;
        $this->personDetailsRepository = $personDetailsRepository;
        $this->contractsDetailsRepository = $contractsDetailsRepository;
    }

    public function __invoke(SignEmploymentContract $command): void
    {
        $person = $this->personDetailsRepository->ofPersonId($command->personId()->toString());

        $enteredContracts = $this->contractsDetailsRepository->ofPersonId($person->personId());

        if (!OverlappingEmploymentContractPolicy::isSatisfiedBy(
            $command->contractId(), $command->contractType(),
            $command->employmentPeriod(), $command->employerId(), $enteredContracts
        )) {
            throw new EmploymentPeriodOverlapsException();
        }

        $contract = EmploymentContract::sign(
            $command->contractId(), $command->personId(),
            new PersonalData(...),
            $command->contractType(),
            $command->startDate(), $command->endDate(), $command->probationEndDate(),
            $command->workerCategory(), $command->wageType(),
            $command->workingTimeAccount(), $command->workweekDays(),
            $command->weeklyWorkingHours(), $command->holidayEntitlement(), $command->additionalLeave(),
            $command->jobFunctionId(), $jobFunction->name(),
            $command->employerId(), $employer->name(),
            $command->workplaceId(), $workplace->name()
        );

        $this->contractCollection->save($contract);
    }
}

The idea is to move the creation of the contract to the Read Model for the Person as demonstrated in PersonReadModel.

Based on this article by @udidahan:

Or the example from "Implementing Domain-Driven Design" by @VaughnVernon:

public class Forum extends Entity  {

    ...

    public Discussion startDiscussion(
      DiscussionId aDiscussionId, Author anAuthor, String aSubject) {

        if (this.isClosed()) {
            throw new IllegalStateException("Forum is closed.");
        }

        Discussion discussion = new Discussion(
          this.tenant(), this.forumId(), aDiscussionId, anAuthor, aSubject);

        DomainEventPublisher.instance().publish(new DiscussionStarted(...));

        return discussion;    
    }

as mentioned by @sofiaguyang:

The new handler would then look like this:

    public function __invoke(SignEmploymentContract $command): void
    {
        $person = $this->personDetailsRepository->ofPersonId($command->personId()->toString());

        $contract = $person->signEmploymentContract(...);
        
        $this->contractCollection->save($contract);
    }
<?php

namespace Acme\PersonnelManagement\Domain\Model\Person;

final class PersonReadModel
{
    private $personId;
    
    private $personalData;
    
    private $employmentContracts;
    
    public function signEmploymentContract(
        EmploymentContractId $contractId, ContractType $contractType,
        DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?DateTimeImmutable $aProbationEndDate,
        ...
    ): EmploymentContract {
        $employmentPeriod = EmploymentPeriod::withType($contractType, $startDate, $endDate);
        
        if (!OverlappingEmploymentContractPolicy::isSatisfiedBy(
            $contractId, $contractType, $employmentPeriod, ..., $this->employmentContracts
        )) {
            throw new EmploymentPeriodOverlapsException();
        }
        
        return EmploymentContract::sign(
            $contractId, $this->personId, $this->personalData,
            $contractType, $startDate(), $endDate(), $probationEndDate(),
            ...
        );
    }
}

This scenario is made for an application that lives in a single microservice. But even if Person and Contracts were dedicated services the Contracts service could consume PersonHired events and create a "local copy" of persons and use them as a read model. Or you would move the logic back to the command handler.

But THEN I would indeed recommend to make everything event-driven and create separate events that may result in a ContractCancelledDueToOverlapping event.

Validating deserialized messages (e.g. Domain Events with payload) when re-constructing Domain Models

When deserializing messages (e.g. Domain Events inside the same context) that hold value objects, do you still validate the given data or do you simply trust the incoming data?

Currently my objects offer a toArray() and fromArray() (see fromArray_v2) method. But the latter could theoretically be used by a developer to create "invalid state".

Especially when value objects do some custom logic e.g. price calculation it gets tricky. Maybe the calculation has changed when replaying the Domain Event.
Alternatively I could go through the constructor again. But then the calculation is again executed though it may has changed.

Which version do you prefer? Or are there any additional checks or other factory method suggestions?

final class CustomPrice
{
    /** @var int $quantity */
    private $quantity;

    /** @var float $subtotal */
    private $subtotal;

    /** @var float $total */
    private $total;

    /**
     * CustomPrice constructor.
     * @param int $quantity
     * @param float $subtotal
     */
    public function __construct(int $quantity, float $subtotal)
    {
        $this->quantity = $quantity;
        $this->subtotal = $subtotal;
        $this->total = $quantity*$subtotal;
    }

    /**
     * @param array $array
     * @return CustomPrice
     */
    public static function fromArray(array $array): CustomPrice
    {
        Assert::keyExists($array, 'quantity');
        Assert::integer($array['quantity']);
        Assert::keyExists($array, 'subtotal');
        Assert::float($array['quantity']);

        return new self($array['quantity'], $array['subtotal']);
    }

    public static function fromArray_v2(array $array): CustomPrice
    {
        Assert::keyExists($array, 'quantity');
        Assert::integer($array['quantity']);
        Assert::keyExists($array, 'subtotal');
        Assert::float($array['quantity']);
        Assert::keyExists($array, 'total');
        Assert::float($array['total']);

        $self = new self();
        $self->quantity = $array['quantity'];
        $self->subtotal = $array['subtotal'];
        $self->total = $array['total'];

        return $self;
    }

    /**
     * @return array
     */
    public function toArray(): array
    {
        return [
            'quantity' => $this->quantity,
            'subtotal' => $this->subtotal,
            'total' => $this->total
        ];
    }

Original discussion started here:

P.S.:
The value object shown here is stored inside the payload of a Domain Event. The payload uses primitives only. In order to use on a event handler method on the aggregate Root it has to be re-constructed from those primitives.

Primitive types only in command

When I started decoupling from Symfony Form and the EntityType I switched to primitive types only that would then be used on the data_class which is the Command / DTO.

In conlusion my command only uses integers (for IDs) and strings (also UUID) or arrays of these types.

I'm sometimes asked if objects e.g. value objects should be used. As I mentioned I don't use objects in a DTO at all. As @jeremyb states in his slides:

Tips #3: Command & Value Object

Il est recommandé d'utiliser des types primitifs si la Command doit être traitée en asynchrone (à cause de la serialization)
Passer des Values Objects à une Command permet de réutiliser un cadre et les validations définies

Most examples I see follow these "guidelines":
https://github.com/SimpleBus/MessageBus/blob/master/doc/command_bus.md#using-the-command-bus-an-example @matthiasnoback

Sometimes a DTO property transfers a string coming from a constant:

class ContractState
{
    const STATE_ACCEPTED        = 'contract_accepted';
    const STATE_APPROVED        = 'contract_approved';
    const STATE_QUOTE_SUBMITTED = 'quote_submitted';
    const STATE_IN_PROGRESS     = 'in_progress';
    const STATE_PENDING         = 'pending';
    const STATE_CLOSED          = 'closed';
}

People ask how to ensure (validate) that the string $contractState of the DTO is a correct string from the constants.

If you are using Symfony Form you already set the choices and a user cannot select a different choice.

When the input comes directly from the Request I guess you could add a custom Constraint to the Command that could check if the strings exists:

Since I'm using Symfony Form I prefer validating the input inside the Value Object. The handler will try to create an object from String:

ContractState::fromString($command->contractState());

The fromString method then will throw an Exception if the string was invalid.

What are your thoughts on this?

Related issues:

Finding aggregate (root) and naming (factory) methods

What really interests me is defining the actual aggregate root from the Order POV.

In my current company I have to processes:

  • Customer talks to Employee. Employee logs in, creates an offer, sends it to Customer
  • Customer receives Offer, agrees, informs Employee
  • Employee logs in, enters the final order. Generates an Order Confirmation and sends it to the Customer
  • Customer confirms order
  • Employee logs in - an actual "Contract" is created. Work can begin.

Regarding the Offer and Order it is the Employee with the active part. In the red book there is this example "Table 1.5 Analyzing the best model for the business.":
http://www.informit.com/articles/article.aspx?p=1944876&seqNum=3

Now I started thinking that it should be something like:

  • Employee->sendOfferToCustomer
  • Employee->sendOrderToCustomer
  • Customer->confirmOffer

or at least:

  • Customer->receiveOffer
  • Customer->receiveOrder
  • Customer->confirmOffer

instead of:

  • Customer->requestOffer
  • Customer->placeOrder
  • Customer->confirmOffer

This really confused me! ;) My feeling as a developer tells me to use the last approach.
What do you think @yvoyer ?

Using a Command as DTO to populate Symfony Form

@beberlei shows a good example on how to use this approach here:
http://whitewashing.de/2012/08/22/building_an_object_model__no_setters_allowed.html

With the following code you can add the EditPostCommand to the data_class of the EditPostType form:

<?php
class EditPostCommand
{
    public $id;
    public $headline;
    public $text;
    public $tags = array();
}
<?php
class PostController
{
    public function editAction(Request $request)
    {
        $post = $this->findPostViewModel($request->get('id'));

        // This could need some more automation/generic code
        $editPostCommand           = new EditPostCommand();
        $editPostCommand->id       = $request->get('id');
        $editPostCommand->headline = $post->headline;
        $editPostCommand->text     = $post->text;
        $editPostCommand->tags     = $post->tags;

        // here be the form framework handling...
        $form = $this->createForm(new EditPostType(), $editPostCommand);
        $form->bind($request);

        if (!$form->isValid()) {
            // invalid, show errors
        }

        // here we invoke the model, finally, through the service layer
        $this->postService->edit($editPostCommand);
    }
}

But it seems like Commands should be regarded immutable too which means the properties should not be public. Of course Symfony Form needs at least these to populate the Command.

@matthiasnoback has an interesting solution for this Symfony Form Use Case:
https://github.com/matthiasnoback/convenient-immutability

A Groovy example by @bodiam:
http://www.jworks.nl/2011/08/26/friday-repost-groovys-immutable-pitfalls/

General discussion:
https://groups.google.com/forum/#!msg/dddinphp/a5ielXU3EZg/qEoi5ppWAgAJ

Is there any approach how we could use the Command with Symfony Form if the properties were private?

Maybe using the empty_data callback on the Command / DTO and a static method as suggested by @webmozart:
http://whitewashing.de/2012/08/22/building_an_object_model__no_setters_allowed.html#comment-1091314235

Blending ORM and ODM but keeping the Domain Model clean

When trying to use Doctrine ORM Entities with ODM Documents (e.g. MongoDB) most solutions adding extra fields:

For me this feels like "polluting" the Domain Model with extra properties to fit the infrastructure.
So I am currently looking for a way to keep the Domain Models clean:

A possible workaround could be a custom mapper:

A different solution could be treating referenced entities as Value Objects which at the same time could serve as a historical "query database".

For instance:
Instead of linking an ODM Order Document (still an "Entity" in DDD though) with an ORM Customer you introduce a Customer Value Object that keeps Id and full name. Maybe the delivery and / or invoice address too.

Remember kids:

Implementation issues (such as persistence) are not dealt with in the model, but they must be in the design.” - @ericevans0
https://twitter.com/fromddd/status/725604978564308992

Doctrine (Array)Collection and other equivalents to Java (Array)List

When reading my first DDD Java examples I saw a lot of Lists:

class Order
{
    private Integer ID;
    private Customer owner;
    private List<Product> ordered;

A List is an interface like Set and has ArrayList as stated by @iamzero

Unfortunately PHP does not have a true equivalent as @giorgiosironi knows:

But we have @doctrine Collections:

And alternatives like Ardent by @morrisonlevi:

The docs say:

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

When using @doctrine ArrayCollections you can use the indexBy mapping to make your Collection indexed (ordered):

Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in the first version of Doctrine keys retrieved from the database were always numerical unless INDEX BY was used.
You have to manage both the key and field if you want to change the index by field value.

But you have to be careful when using accessors as @MacDada warns:

Still it seems to be a matter of taste if you use ArrayCollection at all in your Domain Models:

Don't be alarmed by the use of ArrayCollections. Notice that is in the Doctrine/Common namespace. It's just a little utility array wrapper with no particular ties to the Doctrine persistence layer. You could easily replace it with another array class.

@tPl0ch warns:

The problem with Doctrine collections and the ORM is that you cannot use custom Collection implementations since you will always receive a PersistenCollection in your proxies.

What are your thoughts? Any use cases? Any comparison to the Java DDD approaches using List?

XML mapping for Doctrine MongoDB ODM References Extension

Coming from:

I never got it working on the Document side - don't know if the XML mapping is complete anyway:

http://atlantic18.github.io/DoctrineExtensions/schemas/orm/doctrine-extensions-3.0.xsd

namespace Acme\AppBundle\Entity;

class Customer
{
    /**
     * @Gedmo\ReferenceMany(type="document", class="Acme\DomainModel\Order", mappedBy="customer")
     */
    private $orders;
}
        <field fieldName="customerId" type="integer" />
        <gedmo:reference type="entity"
             class="Acme\AppBundle\Entity\Customer"
             inversed-by="orders" identifier="customerId" />
namespace Acme\DomainModel;

class Order
{
    private $customer;
    private $customerId;
}

Doctrine Repositories best practice

Applying domain events to aggregate roots when deserializing payload of old events break current value object constraint changes

Came from:

Example:

/cc @AntonStoeckl

Our FirstName value object has changed over time. It started with no constraints (FirstName_V1). That's whay event payload like the following was possible:

{"firstName":"Max 12345"}

Later constraints e.g. no numbers allowed were added (FirstName_V2). The names were fixed. Now the event history that has to applied to the Person aggregate root includes this:

{"firstName":"John 12345"},
{"firstName":"John Doe"},

When creating the event and serializing the data this will not cause any problem.

        $event = self::occur(
            $personId->toString(),
            [
                'personId' => $personId->toString(),
                'oldFirstName' => $oldFirstName->toString(),
                'newFirstName' => $newFirstName->toString(),
            ]
        );

But when applying it...

    protected function apply(AggregateChanged $event): void
    {
        switch (get_class($event)) {
            case NameChanged::class:
                /** @var NameChanged $event */
                $this->firstName = $event->newFirstName();

...it will break and throw the NameContainsIllegalCharacters exception.

    private function __construct(string $aName)
    {
        $name = NameNormalizer::withString($aName);

        if (!NamePolicy::isSatisfiedBy($name)) {
            throw new NameContainsIllegalCharacters();
        }

        $this->name = $name;
    }

    public static function fromString(string $name): FirstName
    {
        return new self($name);
    }

There are two solutions I can imagine so far:

(1) Somehow catch the date of the BC for the payload and convert the old breaking payload to the new constraints. Similar to "event upcasting".

(2) Add a named constructor e.g. fromPayload that skips the validation. Similar to value objects based on multiple properties that have an fromArray and toArray e.g.:

abstract class DomainMessage implements Message
{
    public static function fromArray(array $messageData): DomainMessage
    {
        MessageDataAssertion::assert($messageData);

        $messageRef = new \ReflectionClass(\get_called_class());

        /** @var $message DomainMessage */
        $message = $messageRef->newInstanceWithoutConstructor();

        $message->uuid = Uuid::fromString($messageData['uuid']);
        $message->messageName = $messageData['message_name'];
        $message->metadata = $messageData['metadata'];
        $message->createdAt = $messageData['created_at'];
        $message->setPayload($messageData['payload']);

        return $message;
    }
}

Source: https://github.com/prooph/common/blob/master/src/Messaging/DomainMessage.php#L49-L65

This example even uses reflection in order to skip the constructor.

Concrete solution:

/**
 * Character e.g. numbers were forbidden.
 */
final class FirstName_V2
{
    /** @var string $name */
    private $name;

    private function __construct()
    {
    }

    public static function fromString(string $aName): FirstName
    {
        $name = NameNormalizer::withString($aName);

        if (!NamePolicy::isSatisfiedBy($name)) {
            throw new NameContainsIllegalCharacters();
        }

        $self = new self();
        $self->name = $name;

        return $self;
    }

    public static function fromPayload(string $name): FirstName
    {
        $self = new self();
        $self->name = $name;

        return $self;
    }

    public function toString(): string
    {
        return $this->name;
    }
}

Disadvantage: the validation is no longer inside the constructor. This is fine as long there is only a single named constructor that requires it. If there are multiple methods I guess you would have to put them into an extra validate method.

Please note:
Normally a first name is not a critical property that needs to be stored inside an aggregate root since it is not relevant when protecting the invariants when changing state. But let's assume it is in example. That's why I added the old first name to the event.

Catching exceptions and converting them to Symfony form errors

If anywhere in your application e.g. in your Handler or DomainModel an Exception / DomainException is thrown you can catch and convert the message to use it in your Symfony form:

        $fooCommand = FooCommand::fromBar($bar);

        $form = $this->createForm(FooForm::class, $fooCommand);
        $form->handleRequest($this->getRequest());

        if ($form->isValid()) {
            $fooHandler = $this->get('acme.handler.foo');
            try {
                $fooHandler->handle($fooCommand);

                return $this->redirectToRoute('acme_foo_list');
            } catch (\Exception $e) {
                $form->addError(new FormError($e->getMessage()));
            }
        }

And then inside your TWIG template:

{{ form_errors(form) }}

How to deal with updating Entity (CRUD) and Domain Events using DDD?

I know that DDD is good with a Task-Based UI, but I'm refactoring a legacy app, where I have an Anemic Domain Model (many setters without the business logic).

One of the first steps was to make it reach model and add Domain Events. While adding events for creating (TaskCreated in constructor) and removing (TaskRemoved) the model is an easy process, I'm struggling with updating the model.

We have a RESTful API with PUT /tasks/{id} endpoint. Under the hood the framework maps the body of the response to Task object and then calls setters one by one:

$task->setText('new text');
$task->setStartDate($newStartDate);
// and so on

I want to listen some event when the task is updated and update it in e.g. Google Calendar.
As you can imaging, if I record events in each setter (TextChanged, StartDateChanged) and listen all of them, I will end up with many API calls to the Google API, which is not what I want.

Question is: how should I work with Update operation in the right way? Should I replace all those setters calls with one ->update($newData) call and dispatch only one domain event there? How to make only one API call to google calendar after the task is updated?

PS: I've read tons of posts but didn't find useful information about Updating domain model.
The real example I found in the dddinphp repository in UpdatePostHandler class, where they call updaters (setters) one by one: https://github.com/dddinphp/blog-cqrs/blob/master/src/CQRSBlog/BlogEngine/Command/UpdatePostHandler.php#L27

// UpdatePostHandler.php
$aPost->changeTitle($anUpdatePostCommand->getTitle());
$aPost->changeContent($anUpdatePostCommand->getContent());

// Post.php
public function changeTitle($aNewTitle)
{
    $this->applyAndRecordThat(
        new PostTitleWasChanged($this->postId, $aNewTitle)
    );
}

Enriching / Fat Events and Event Handler / Projector - TELL, DON'T ASK

I have an Inspection Domain Model with a lot of properties, some of them nested.

A new Inspection is handled by a Command Handler. The Inspection AR then raises an ResultRecorded Event.
Currently this event only holds the AR InspectionId. Not all of the properties I mentioned.

Then an Event Handler catches the event. Currently the Handler lives inside the same Bounded Context "Inspection".
The Handler fetches the (WRITE) Domain Model from its repository by the InspectionId. It then transforms ("projects"?) the WRITE Model to the READ Model by passing the data to a "fromInspection" constructor. This constructor then has to do some heavy projection because of the nested Aggregates.
Finally the Event Handler stores the READ Model.

If I get the term correctly the Event Handler can be regarded as the "Projector"?
https://abdullin.com/post/event-sourcing-projections/

Then I asked myself what the responsibilities of a Projector is. I think it should do what every Application (Handler) should do:
Receive a message, transform it into Domain language if required (e.g. convert primitive types to Value Objects) and then pass it to the repository and return nothing.

So far this seems to be covered by my example. Though I didn't feel sure if the "transformation" inside the constructor should be moved to the Event Handler.

Then I read about "enriching" Events by @lavinski:
https://www.lavinski.me/generating-read-models-with-event-sourcing/

Also mentioned as "Fat Events" by @mathiasverraes:
https://speakerdeck.com/mathiasverraes/practical-event-sourcing?slide=41

This made me rethink the process of the Event Handler / Projector. What if the Projector was living in a different Bounded Context?

  • Should it really take the InspectionId to fetch the WRITE model from the other context?
    What if I had multiple READ models?
  • Should my READ model(s) really have a constructor taking the "rich" data from the WRITE model?

Conclusion: NO! NO! And NO!

The Projector - different bounded context or not - should not receive the Identifier of the original WRITE model to fetch it.
Instead it should receive all data it needs - even it is a lot of data.
Optionally the event could be extended or the data could be devided into multiple Events the Subscriber can listen to(o):

I would move the transformation inside the READ model constructor to place where the Event is raised - the (WRITE) Domain Model.

This felt strange at first! Why?
It felt like setting up a data structure the the Domain Model should not know about BUT the Subscriber e.g. the Projector or the READ Model in the end.
But I guess this type of thinking is simply wrong! We are NOT ASKING what somebody needs - WE ARE TELLING!

What do you think? Code examples will follow!

Primitives instead of value objects in Domain Events

Came from:

Recently I started a symfony project w/ @prooph components. Looking at the bluebrints I recognized the creation of events has changed.

 class User extends AggregateRoot
    {
        /**
         * @var Uuid
         */
        private $uuid;
        /**
         * @var string
         */
        private $name;
        /**
         * ARs should be created via static factory methods
         */
        public static function nameNew(string $username): User
        {
            //Perform assertions before raising a event
            Assertion::notEmpty($username);
            $uuid = Uuid::uuid4();
            //AggregateRoot::__construct is defined as protected so it can be called in a static factory of
            //an extending class
            $instance = new self();
            //Use AggregateRoot::recordThat method to apply a new Event
            $instance->recordThat(UserWasCreated::occur($uuid->toString(), ['name' => $username]));
            return $instance;
        }

        /**
         * Every AR needs a hidden method that returns the identifier of the AR as a string
         */
        protected function aggregateId(): string
        {
            return $this->uuid->toString();
        }
        protected function apply(AggregateChanged $event): void
        {
            switch (\get_class($event)) {
                case UserWasCreated::class:
                    //Simply assign the event payload to the appropriate properties
                    $this->uuid = Uuid::fromString($event->aggregateId());
                    $this->name = $event->username();
                    break;
                case UserWasRenamed::class:
                    $this->name = $event->newName();
                    break;
            }
        }
    }
    /**
     * ProophEventSourcing domain events are of the type AggregateChanged
     */
    class UserWasCreated extends AggregateChanged
    {
        public function username(): string
        {
            return $this->payload['name'];
        }
    }

Before there was a lot of boilerplate going on e.g. from an earlier project:

final class DormerElementCreated extends AggregateChanged
{
    /** @var DormerElementId */
    private $dormerElementId;

    /** @var VariableName */
    private $variableName;

    /** @var string */
    private $variableDescription;

    /** @var DormerPart */
    private $dormerPart;
    
    /** @var MeasurementUnit */
    private $measurementUnit;

    /** @var float */
    private $workingHours;

    /** @var int */
    private $surcharge;

    public static function with(
        DormerElementId $dormerElementId, VariableName $variableName, string $variableDescription,
        DormerPart $part, MeasurementUnit $unit, float $workingHours, int $surcharge
    ): DormerElementCreated
    {
        $event = self::occur(
            $dormerElementId->toString(),
            [
                'id' => $dormerElementId->toString(),
                'variableName' => $variableName->toString(),
                'variableDescription' => $variableDescription,
                'dormerPart' => $part->toString(),
                'measurementUnit' => $unit->toString(),
                'workingHours' => $workingHours,
                'surcharge' => $surcharge,
            ]
        );
        $event->dormerElementId = $dormerElementId;
        $event->variableName = $variableName;
        $event->variableDescription = $variableDescription;
        $event->dormerPart = $part;
        $event->measurementUnit = $unit;
        $event->workingHours = $workingHours;
        $event->surcharge = $surcharge;

        return $event;
    }

    public function dormerElementId(): DormerElementId
    {
        if (null === $this->dormerElementId) {
            $this->dormerElementId = DormerElementId::fromString($this->aggregateId());
        }

        return $this->dormerElementId;
    }

    public function variableName(): VariableName
    {
        if (null === $this->variableName) {
            $this->variableName = VariableName::fromString($this->payload['variableName']);
        }

        return $this->variableName;
    }
}

Most of the getters were creating value objects from the payload. This was really annoying when dealing with arrays. Most of the time you had to implement toArray and fromArray methods and loop them through array_map methods.

This change made me think. Then I read this post by @heynickc:

And followed some other discussions:

@prooph for instance keeps the symfony structure for convenience too:

I'm really beginning to like this approach. What do you guys think?

See also:

How to recover from domain events written to the stream but not published due to failure?

            function (ActionEvent $event) use ($eventStore): void {
                $recordedEvents = $event->getParam('streamEvents', new \ArrayIterator());

                if (! $this->inTransaction($eventStore)) {
                    if ($event->getParam('streamNotFound', false)
                        || $event->getParam('concurrencyException', false)
                    ) {
                        return;
                    }

                    foreach ($recordedEvents as $recordedEvent) {
                        $this->eventBus->dispatch($recordedEvent);
                    }
                } else {
                    $this->cachedEventStreams[] = $recordedEvents;
                }
            }

Regardless of what event-bus to use - at this point of the code the events have already been written to the stream, correct? What if dispatching fails here for some reason? How can an app recover from the domain event not being published?
For instance a console command that re-dispatches / publishes the events? Or am I thinking "too carefully"?!

/cc @bas @prolic

Discussion on Twitter:

Creating and grouping value objects

In my Entity EmploymentContract I have the value objects ContractType - "fixed_term" or "permanent" and EmploymentPeriod with "startDate" and "endDate".
When the ContractType is "permanent" then the "endDate" should be "null".

My current implementation:

final class EmploymentContract extends AggregateRoot
{
    public static function sign(
        EmploymentContractId $anId, ContractType $aContractType,
        DateTimeImmutable $aStartDate, ?DateTimeImmutable $anEndDate
    ) {
        if ($aContractType->isFixedTerm() && null === $anEndDate) {
            throw new FixedTermContractMustHaveAnEndDateException();
        }

        $employmentPeriod = new EmploymentPeriod($aStartDate, $anEndDate);
    }
}

In this solution the entity (event-sourced aggregate root A+ES) groups the two value objects and protects the invariants / business rules.

In issue #39 we talked about creating Entities through Aggregate roots. No I wonder if this works for value objects too.

Here a two different approaches:

final class EmploymentContract extends AggregateRoot
{
    public static function sign(
        EmploymentContractId $anId, ContractType $aContractType, EmploymentPeriod $aPeriod
    ) {
        // no further validation, the passed value objects are expected too be valid
    }
}

final class EmploymentPeriod
{
    public function __construct(DateTimeImmutable $startDate, ?DateTimeImmutable $endDate)
    {
        if (null !== $endDate) {
            if ($endDate < $startDate) {
                throw new EndDateMustBeGreaterThanStartDateException();
            }
        }

        $this->startDate = $startDate;
        $this->endDate = $endDate;
    }

    public static function withType(
        ContractType $type, DateTimeImmutable $startDate, ?DateTimeImmutable $endDate
    ): EmploymentPeriod
    {
        if ($type->isFixed()) {
            if (null === $endDate) {
                throw new FixedTermContractMustHaveAnEndDateException();
            }
        }

        if ($type->isPermanent()) {
            if (null !== $endDate) {
                throw new PermanentContractMustNotHaveAnEndDateException();
            }
        }

        return new self($startDate, $endDate);
    }
}

Or:

final class EmploymentContract extends AggregateRoot
{
    public static function sign(
        EmploymentContractId $anId, ContractType $aContractType, EmploymentPeriod $aPeriod
    ) {
        // no further validation, the passed value objects are expected too be valid
    }
}

final class ContractType
{
    public function specifyEmploymentPeriod($startDate, $endDate); EmploymentPeriod
    {
        if ($this->isFixed()) {
            if (null === $endDate) {
                throw new FixedTermContractMustHaveAnEndDateException();
            }
        }

        if ($this->isPermanent()) {
            if (null !== $endDate) {
                throw new PermanentContractMustNotHaveAnEndDateException();
            }
        }

        return new EmploymentPeriod($startDate, $endDate);
    }
}

Or should ContractType and EmploymentPeriod event be grouped into a single value object?

Thoughts?

Repositories inside or outside Domain Services

Came from:

I have a service that extracts the MIN and MAX date of a period. This is the original INSIDE approach #1

Domain Service

<?php

namespace Acme\PersonnelManagement\Domain\Service\EmploymentContract;

use Acme\PersonnelManagement\Domain\Model\EmploymentContract\EmploymentContractId;
use Acme\PersonnelManagement\Domain\Model\EmploymentContract\EmploymentPeriod;
use Acme\PersonnelManagement\Presentation\Model\EmploymentContract\TermRepository;
use Webmozart\Assert\Assert;

final class EmploymentPeriodExtractor
{
    /** @var TermRepository */
    private $termRepository;

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

    /**
     * @param EmploymentContractId[] $contractIds
     * @return EmploymentPeriod
     */
    public function fromContractIds(array $contractIds): EmploymentPeriod
    {
        Assert::allIsInstanceOf($contractIds, EmploymentContractId::class);
        Assert::minCount($contractIds, 1);

        $terms = $this->termRepository->ofContractIds(array_map(function (EmploymentContractId $contractId) {
            return $contractId->toString();
        }, $contractIds));

        $employmentPeriods = [];

        foreach ($terms as $term) {
            $employmentPeriods[] = new EmploymentPeriod(
                $term->startDate(), $term->endDate()
            );
        }

        return EmploymentPeriodMerger::merge($employmentPeriods);
    }
}

Application Service (Command Handler)

<?php

namespace Acme\PersonnelManagement\Application\Service\Person;

use Acme\PersonnelManagement\Domain\Service\EmploymentContract\EmploymentPeriodExtractor;

final class ExtractEmploymentPeriodHandler
{
    /** @var EmploymentPeriodExtractor */
    private $extractor;

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

    public function __invoke(ExtractEmploymentPeriod $command): void
    {
        $newPeriod = $this->extractor->fromContractIds($command->contractIds());
        // Save aggregate...
    }
}

The domain layer always holds an interface for the TermRepository:

<?php

namespace Acme\PersonnelManagement\Presentation\Model\EmploymentContract;

use Acme\PersonnelManagement\Domain\Model\EmploymentContract\EmploymentContractId;

interface TermRepository
{
    public function ofPersonId(string $personId): array;

    /**
     * @param string[] $contractIds
     * @return Term[]
     */
    public function ofContractIds(array $contractIds): array;
}

The implementation lives inside the infrastructure layer.
Since the Extractor Service only gets the interface type hinted I think it is valid to see it as a Domain Service.

Unfort. this is quite hard to unit test. It would require mocking or a InMemoryTermRepository with same fake data.
It also looks like it is violating the single-responsibility principle (SRP).

This is my OUTSIDE approach #2:

Domain Service

<?php

namespace Acme\PersonnelManagement\Domain\Service\EmploymentContract;

use Acme\PersonnelManagement\Domain\Model\EmploymentContract\EmploymentContractId;
use Acme\PersonnelManagement\Domain\Model\EmploymentContract\EmploymentPeriod;
use Acme\PersonnelManagement\Presentation\Model\EmploymentContract\TermRepository;
use Webmozart\Assert\Assert;

final class EmploymentPeriodExtractor
{
    /**
     * @param Term[] $terms
     * @return EmploymentPeriod
     */
    public function fromTerms(array $terms): EmploymentPeriod
    {
        Assert::allIsInstanceOf($terms, Term::class);
        Assert::minCount($terms, 1);

        $employmentPeriods = [];

        foreach ($terms as $term) {
            $employmentPeriods[] = new EmploymentPeriod(
                $term->startDate(), $term->endDate()
            );
        }

        return EmploymentPeriodMerger::merge($employmentPeriods);
    }
}

Application Service (Command Handler)

<?php

namespace Acme\PersonnelManagement\Application\Service\Person;

use Acme\PersonnelManagement\Domain\Service\EmploymentContract\EmploymentPeriodExtractor;

final class ExtractEmploymentPeriodHandler
{
    /** @var TermRepository */
    private $termRepository;

    /** @var EmploymentPeriodExtractor */
    private $extractor;

    public function __construct(TermRepository $termRepository, EmploymentPeriodExtractor $extractor)
    {
        $this->termRepository = $termRepository;
        $this->extractor = $extractor;
    }

    public function __invoke(ExtractEmploymentPeriod $command): void
    {
        $terms = $this->termRepository->ofContractIds(array_map(function (EmploymentContractId $contractId) {
            return $contractId->toString();
        }, $command->contractIds()));

        $newPeriod = $this->extractor->fromTerms(terms);
        // Save aggregate...
    }
}

This is much easier to test. Though a developer could easily use this code to manipulate the Extractor result by freely passing any Terms as argument. But I guess the developer should not be "the enemy".

Which approach do you prefer? Any exceptions to this or improvements?

Thank you for your feedback.

Which Message / Command / Event Bus to use

The example by @yvoyer features a custom implementation of the Event Bus e.g..
But it will also show how to integrate with Symfony components.

Another custom example by @juliendufresne:
https://github.com/php-ddd/php-ddd-bundle/tree/master/src

Of course there is also the @SimpleBus MessageBus by @matthiasnoback::
https://github.com/SimpleBus/MessageBus
I currently use it in my Symfony projects:
https://github.com/SimpleBus/SymfonyBridge

And not to forget @thephpleague Tactician by @rosstuck:
https://github.com/thephpleague/tactician

Another one is @PHPMessageBus by @nilportugues:
https://github.com/PHPMessageBus/messagebus

Opinions?

Event Enriching and external changes to read-model data

We enrich our Events when we reference another aggregate by ID e.g. employerId by adding a name employerName.
This allows us to directly update the read model.

A+ES:

final class EmploymentContractSigned extends AggregateChanged
{
    /** @var EmploymentContractId $contractId */
    private $contractId;

    /** @var EmployerId */
    private $employerId;
}

Projector:

final class EmploymentContractDetailsProjection implements ReadModelProjection
{
    public function project(ReadModelProjector $projector): ReadModelProjector
    {
        $projector->fromStream('employment_contract_stream')
            ->when([
                EmploymentContractSigned::class => function ($state, EmploymentContractSigned $event) {
                    /** @var EmploymentContractDetailsReadModel $readModel */
                    $readModel = $this->readModel();
                    $readModel->stack('sign', [
                        'contract_id' => $event->contractId()->toString(),
                        'employer_id' => $event->employerId()->toString(),
                        'employer_name' => $event->employerName(),
                        'created_at' => $event->createdAt()->format('Y-m-d H:i:s'),
                        'created_by_user_id' => $event->metadata()['created_by_user_id'],
                        'created_by_username' => $event->metadata()['created_by_username'],
                        'created_by_full_name' => $event->metadata()['created_by_full_name']
                    ]);
                },
            ]);

        return $projector;
    }
}

Sometimes an Employer name can change. Normally this is not relevant to historical data. But for active Contracts the read model should always have the current Employer name.

Normally you could simply update a table:

UPDATE `employment_contract_details` SET employer_name = 'New Acme'  where employer_id = '...';

But of course this data will get lost as soon as we reply the event stream. The original Employer name will be set and the UPDATE is lost.

One possibility to to keep the change is to fire an event on the aggregate:

final class EmploymentContractSigned extends AggregateChanged
{
    public function recognizeEmployerChange($newEmployerName): void
    {
        $this->recordThat(EmployerChangeRecognized::with($this->contractId, $this->employerId, $newEmployerName));
    }

"If an external change from another context is relevant to the state of the current context then the state change should be recorded."

But in this case we are not changing the actual state. That would require the Employer (ID) itself to change e.g.:

final class EmploymentContractSigned extends AggregateChanged
{
    public function transferToNewEmployer($newEmployerId, $newEmployerName): void
    {
        $this->recordThat(TransferredToNewEmployer::with($this->contractId, $newEmployerId, $newEmployerName));
    }

But in our case we are simply changing a property that was already enriched for read-model purposes.

It is convenient to have a Projector fill a single table for the read model. But updating the read model data by going through the aggregate feels strange.

How do you guys handle these external changes that do no seem relevant to the state of the aggregate?

Return of the Join

This is our current READ Model repository implementation:

final class EmploymentContractDetailsRepository extends DbalReadModelRepository implements DetailsRepository
{
    public function ofId(string $id): Details
    {
        $stmt = $this->getConnection()->prepare(sprintf(
            "SELECT * FROM `%s` WHERE contract_id = :id",
            Table::EMPLOYMENT_CONTRACT_DETAILS
        ));
        $stmt->bindValue('id', $id);
        $stmt->execute();

        $result = $stmt->fetch();

        if (false === $result) {
            throw new \Exception('Contract not found');
        }

        return Details::fromArray($result);
    }
}

Is this a valid use case to use a JOIN on a local copy of the data that has to be up-to-date?

final class EmploymentContractDetailsRepository extends DbalReadModelRepository implements DetailsRepository
{
    public function ofId(string $id): Details
    {
        $stmt = $this->getConnection()->prepare(sprintf(
            "SELECT * FROM `%s` JOIN `employers` USING (employer_id) WHERE contract_id = :id",
            Table::EMPLOYMENT_CONTRACT_DETAILS
        ));
        $stmt->bindValue('id', $id);
        $stmt->execute();

        $result = $stmt->fetch();

        if (false === $result) {
            throw new \Exception('Contract not found');
        }

        return Details::fromArray($result);
    }
}

Advantage:
Whether to use the current or the old Employer name can still be handled per READ (VIEW) model (UI Use Case).

Create or join GitHub Organization

Hello @juliendufresne!

We recognized you already create a great organization here:
https://github.com/php-ddd

And you already added some @symfony bundle integrations:

Are you interested in joining us resp. adding us to your organization? Feel free to ask me or @yvoyer about our plans. Just comment here or send us a DM on twitter. Thanks!

PHP Arrays vs. DTO READ models / VIEW collections using Doctrine

Currently I'm using PHP Arrays for single READ models. Actually it was a performance driven decision since I use them for exporting 100s of rows into a generated PDF.

But looks like this myth has been busted or at least is no longer regarded "relevant" since PHP_7_:

Looking at some DDD repository examples Java and DotNET write results coming from a persistence storage into a collection of objects.

Mostly these objects are then serializable to deliver primitive types like Command DTOs.

Currently I'm using the Doctrine Array hydration to handle a more complex query:

        $qb->select(array(
                'b.id',
                'b.date',
                'b.shift',
                'b.shippingnumber AS shipping_number',
                'GroupConcat(DISTINCT b.comment) AS comment',
                'SUM(b.customFieldNum1) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS custom_field_num_1',
                'SUM(b.customFieldNum2) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS custom_field_num_2',
                'SUM(b.customFieldNum3) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS custom_field_num_3',
                'SUM(b.customFieldNum4) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS custom_field_num_4',
                'SUM(b.customFieldNum5) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS custom_field_num_5',
                'b.customFieldStr1 AS custom_field_str_1',
                'b.customFieldStr2 AS custom_field_str_2',
                'b.customFieldStr3 AS custom_field_str_3',
                'b.customFieldStr4 AS custom_field_str_4',
                'b.customFieldStr5 AS custom_field_str_5',
                'p.id AS parttype_id',
                'p.integratorNumber AS part_number',
                'l.name AS location',
                'SUM(bps_ok.quantity) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS ok_total',
                'SUM(bps_ok.quantity+bps_nok_processed.quantity+bps_nok_blocked.quantity) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS total',
                'SUM(bps_nok_processed.quantity+bps_nok_blocked.quantity) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS nok_total',
                'SUM(bps_nok_processed.quantity) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS nok_processed',
                'SUM(bps_nok_blocked.quantity) / CASE WHEN COUNT(bps_ok.id) = 0 THEN 1 ELSE COUNT(bps_ok.id) / COUNT(DISTINCT bps_ok.id) AS nok_blocked',
            ))

Since Doctrine 2.4 it is possible to directly write a result into a DTO:

<?php
class CustomerDetails
{
    public function __construct($name, $email, $city, $value = null)
    {
        // Bind values to the object properties.
    }
}

<?php
$query = $em->createQuery('SELECT NEW CustomerDetails(c.name, e.email, a.city) FROM Customer c JOIN c.email e JOIN c.address a');
$users = $query->getResult(); // array of CustomerDetails

This works for any PHP class / POPO. While using ResultSetMapping only seems to work with Doctrine Entities:

What are your experiences / favorited strategies?

Related:

Protecting invariants when creating value objects through Entity or Factory

My original post and poll started here:
https://twitter.com/webdevilopers/status/1100102866583339008

This was the original gist:

Use case:
I need to calculate a Dormer based on specific DormerType (Entity).
Business rule: Some DormerTypes can not have a Gutter attached.
Dormer in the end will just be a value object stored inside an DormerCalculation Entity. / Aggregate Root that could throw the DormerCalculated Domain Event.
This means that the DormerTypeId should only be a reference.

I currently think of three approaches.

Version 1:

The full Entity is passed to the value object. This feels ugly. Allthough it is now impossible for a developer to create an inconsistent object. E.g. by attaching a gutter to a dormer with a type that does not allow gutters.

<?php

final class Dormer
{
    /** @var DormerType $type */
    private $type;

    public static function construct(DormerType $type)
    {
        $this->type = $type;
    }

    public function addGutter(Gutter $gutter)
    {
        if (!$this->type->canHaveGutterInstalled()) {
            throw new DormerTypeCannotHaveGutterInstalledException();
        }
 
       $this->gutter = $gutter;
    }
}
final class DormerCalculationFactory extends AbstractCalculator
{
    private $dormerTypeRepository;

    public function createWithDto(CalculateDormerCommand $dto)
    {
        $dormerType = $this->dormerTypeRepository->ofId(DormerTypeId::fromInteger($dto->dormerTypeId));

        /** @var Dormer $dormer */
        $dormer = Dormer::construct($dormerType);
        $dormer->addGutter(Gutter::fromDto($dto->gutter));
    }
}

Version 2:

Again the business rule is ensured. The value object is created through the dormer type Entity which sounds logical. But in the end it is not an Aggregate Root that e.g. could throw a "DormerCalculated" event.
In addition the factory method calculateDormer and the factory method on the dormer value object would look too identical.

<?php

class DormerType()
{
    private $id;

    public function calculateDormer(Gutter $gutter)
    {
         if (!$this->canHaveGutterInstalled()) {
            throw new DormerTypeCannotHaveGutterInstalledException();
        }
       
        return new Dormer($this->id(), $gutter);
    }
}

Version 3:

All creation logic is inside the factory. It protects the invariants and creates the Dormer only with the DormerTypeId.
But a developer could create an inconsistend value object by skipping the factory. Ouch!?

final class DormerCalculationFactory extends AbstractCalculator
{
    private $dormerTypeRepository;

    public function createWithDto(CalculateDormerCommand $dto)
    {
        $dormerType = $this->dormerTypeRepository->ofId(DormerTypeId::fromInteger($dto->dormerTypeId));

        /** @var Dormer $dormer */
        $dormer = Dormer::construct($dormerType->id());

        if (null !== $dto->gutter && $dormerType->canHaveGutterInstalled()) {
            $dormer->addGutter(Gutter::fromDto($dto->gutter));
        }
    }
}

After reading a post on @culttt b @philipbrown version 3 still feels like the best way though.

Due to the coupled nature of the Factory and the object, it is usually fine to allow the Factory to protect the invariants of the object.

What do you guys think?

How to merge event-sourced aggregate roots (A+ES) with Prooph PDO event stream

All events of multiple aggregates have to be selected, changed in some domain relevant ways and be merged into a new aggregate.

I decided to select the events from the default repository using the metadata matcher.

I did not create a new aggregate by calling the factory methods with the new data. Since the data passed was already valid at the time to original events took place and were applied.
Instead I decided to use reflection to pass the "recorded" events.

This was inspired by @prooph code:

namespace Prooph\Common\Messaging;

abstract class DomainMessage implements Message
{
    public static function fromArray(array $messageData): DomainMessage
    {
        MessageDataAssertion::assert($messageData);

        $messageRef = new \ReflectionClass(\get_called_class());

        /** @var $message DomainMessage */
        $message = $messageRef->newInstanceWithoutConstructor();

        $message->uuid = Uuid::fromString($messageData['uuid']);
        $message->messageName = $messageData['message_name'];
        $message->metadata = $messageData['metadata'];
        $message->createdAt = $messageData['created_at'];
        $message->setPayload($messageData['payload']);

        return $message;
    }
}

At the bottom line a lot of prooph-inspired code was used. At the bottom line I think there is not too much coupling.
This example will have a unit test with an in-memory solution. Will add it soon.

Just like any other use case the new aggregate is stored to the repository. The event publisher publishes all events and the projector create the read models and process manager eventually publish some messages to the outside world.
Another process manager will catch the final MergedWithStaffMembers event and fire some "removeMergedStaffMember" commands.

Would love to have your feedback on this approach @Ocramius, @prolic.

The factory:

<?php

namespace Acme\Staff\Domain\Service;

use DomainException;
use Prooph\EventSourcing\AggregateChanged;
use Ramsey\Uuid\Uuid;
use ReflectionClass;
use ReflectionProperty;
use Acme\Staff\Domain\Model\StaffMember\ContractId;
use Acme\Staff\Domain\Model\StaffMember\EmploymentPeriod;
use Acme\Staff\Domain\Model\StaffMember\Event\MergedWithStaffMembers;
use Acme\Staff\Domain\Model\StaffMember\Event\StaffMemberAdded;
use Acme\Staff\Domain\Model\StaffMember\Event\StaffMemberContractModified;
use Acme\Staff\Domain\Model\StaffMember\StaffMember;
use Acme\Staff\Domain\Model\StaffMember\StaffMemberId;
use Acme\Staff\Domain\Model\StaffMember\StaffMemberRepository;

final class MergedStaffMember
{
    /** @var StaffMemberRepository */
    private $staffMemberRepository;

    /**
     * Current version
     *
     * @var int
     */
    private $version = 0;

    /**
     * List of events that are not committed to the EventStore
     *
     * @var AggregateChanged[]
     */
    private $recordedEvents = [];

    /** @var StaffMemberId */
    private $newStaffMemberId;

    /** @var StaffMemberId[] */
    private $mergeWithStaffMemberIds = [];

    /** @var ContractId */
    private $newContractId;

    /** @var EmploymentPeriod */
    private $newEmploymentPeriod;

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

    public function fromMergedHistory(
        StaffMemberId $newStaffMemberId, array $mergeWithStaffMemberIds,
        ContractId $newContractId, EmploymentPeriod $newEmploymentPeriod
    ): StaffMember
    {
        if (0 === count($mergeWithStaffMemberIds)) {
            throw new DomainException('Missing staff members to merge');
        }

        $this->newStaffMemberId = $newStaffMemberId;
        $this->mergeWithStaffMemberIds = $mergeWithStaffMemberIds;
        $this->newContractId = $newContractId;
        $this->newEmploymentPeriod = $newEmploymentPeriod;

        $this->buildHistoryFromMergedStaffMembers();
        $this->finalizeNewStaffMemberHistory();

        $newStaffMemberRef = new ReflectionClass(StaffMember::class);

        /** @var StaffMember $newStaffMember */
        $newStaffMember = $newStaffMemberRef->newInstanceWithoutConstructor();

        $newStaffMemberRecordedEventsRef = new ReflectionProperty($newStaffMember, 'recordedEvents');
        $newStaffMemberRecordedEventsRef->setAccessible(true);
        $newStaffMemberRecordedEventsRef->setValue($newStaffMember, $this->recordedEvents);

        $newStaffMemberStaffMemberIdRef = new ReflectionProperty($newStaffMember, 'staffMemberId');
        $newStaffMemberStaffMemberIdRef->setAccessible(true);
        $newStaffMemberStaffMemberIdRef->setValue($newStaffMember, $newStaffMemberId);

        return $newStaffMember;
    }

    private function buildHistoryFromMergedStaffMembers(): void
    {
        $oldEvents = $this->staffMemberRepository->ofStaffMemberIds($this->mergeWithStaffMemberIds);

        // Ensure chronological order
        uasort($oldEvents, function(AggregateChanged $a, AggregateChanged $b) {
            return $a->createdAt() <=> $b->createdAt();
        });

        $initialStaffMemberId = StaffMemberId::fromString(reset($oldEvents)->aggregateId());

        /** @var AggregateChanged[] $oldEvent */
        foreach ($oldEvents as $oldEvent) {
            $newMessageData = $oldEvent->toArray();
            // The new event needs an own unique ID.
            $newMessageData['uuid'] = Uuid::uuid4()->toString();
            // Set the new staff member ID instead of the merged one.
            $newMessageData['metadata']['_aggregate_id'] = $this->newStaffMemberId->toString();
            // This will be automatically reset correctly.
            unset($newMessageData['metadata']['_position']);

            if ($oldEvent instanceof StaffMemberAdded) {
                /** @var StaffMemberAdded $oldEvent */
                if (!$oldEvent->staffMemberId()->sameValueAs($initialStaffMemberId)) {
                    // Only the initial event can add a staff member.
                    // All other events can only be modifications of the contract.
                    $newMessageData['message_name'] = StaffMemberContractModified::class;
                }
            }

            if ($oldEvent instanceof StaffMemberAdded || $oldEvent instanceof StaffMemberContractModified) {
                $newMessageData['payload']['contractId'] = $this->newContractId->toString();
                // Set new employment period to satisfy all time-period relevant policies.
                $newMessageData['payload']['employmentPeriod'] = $this->newEmploymentPeriod->toArray();
            }

            $eventClassName = $newMessageData['message_name'];

            $newEvent = $eventClassName::fromArray($newMessageData);

            $this->recordThat($newEvent);
        }
    }

    private function finalizeNewStaffMemberHistory(): void
    {
        // Create final event
        $mergedWithStaffMembers = MergedWithStaffMembers::with(
            $this->newStaffMemberId, $this->mergeWithStaffMemberIds,
            $this->newContractId, $this->newEmploymentPeriod
        );
        $mergedWithStaffMembers = $mergedWithStaffMembers
            ->withAddedMetadata('_aggregate_type', StaffMember::class)
        ;

        $this->recordThat($mergedWithStaffMembers);
    }

    /**
     * Record an aggregate changed event
     */
    protected function recordThat(AggregateChanged $event): void
    {
        $this->version += 1;

        $this->recordedEvents[] = $event->withVersion($this->version);
    }
}

The command handler:

<?php

namespace Acme\Staff\Application\Service\StaffMember;

use Acme\Staff\Domain\Model\StaffMember\StaffMemberRepository;
use Acme\Staff\Domain\Service\MergedStaffMember;

final class MergeStaffMembersHandler
{
    /** @var MergedStaffMember */
    private $mergedStaffMember;

    /** @var StaffMemberRepository */
    private $staffMemberRepository;

    public function __construct(MergedStaffMember $mergedStaffMember, StaffMemberRepository $staffMemberRepository)
    {
        $this->mergedStaffMember = $mergedStaffMember;
        $this->staffMemberRepository = $staffMemberRepository;
    }

    public function __invoke(MergeStaffMembers $command): void
    {
        $newStaffMember = $this->mergedStaffMember->fromMergedHistory(
            $command->newStaffMemberId(),
            $command->mergeWithStaffMemberIds(),
            $command->newContractId(),
            $command->newEmploymentPeriod()
        );

        $this->staffMemberRepository->save($newStaffMember);
    }
}

Just some framework - Symfony and YAML for Marco ;) - config:

services:

    Rewotec\Staff\Domain\Service\MergedStaffMember:
      arguments:
        - '@staff_member_collection'

    Rewotec\Staff\Application\Service\StaffMember\MergeStaffMembersHandler:
        public: true
        tags: [messenger.message_handler]
        arguments:
            - '@Rewotec\Staff\Domain\Service\MergedStaffMember'
            - '@staff_member_collection'

Should a Specification / Policy be injected?

How to handle large aggregates - defining multiple aggregate roots

We have a Contract Domain Model holding a huge collection of Timesheets. I just added some simple Symfony code snippets using Doctrine Entities and Annotations.
All Entities are later moved to Acme\Contract\Domain\Model namespace and XML Mapping inside Acme\Contract\Infrastructure\Persistence\Doctrine etc..

use Acme\Contract\ContractBundle;

/** 
 * @ORM\Entity
 */
class Contract
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    public $id;

    /**     
     * @ORM\Column(type="string", name="status")
     */
    protected $status;

    /**
     * @ORM\OneToMany(targetEntity="Timesheet", mappedBy="contract", cascade={"persist"})
     * @todo Use LAZY loading?
     */
    private $timesheets;

    public function addTimesheet(Timesheet $timesheet) {
        // Policy
        if ($this->isClosed()) {
            throw new ContractAlreadyClosedException();
        }

        $this->timesheets[] = $timesheet;
    }
}
use Acme\Contract\ContractBundle;

/**
 * @ORM\Entity
 */
class Timesheet
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Contract", inversedBy="timesheets")
     * @ORM\JoinColumn(name="contract_id", referencedColumnName="id")
     */
    private $contract;
}

Currently we are using CQRS and a single Symfony app. Using Contract as the only AR forces our policy on the invariant:

use Acme\Contract\Application\Service;

class WriteTimeSheetHandler
{
    public function handle($command)
    {
        $contract = $this->contractRepository->find($command->contractId());
        $timesheet = new Timesheet($command->start(), $command->end());
        $contract->addTimesheet($timesheet); // Check invariant
    }
}

There can be many related timesheet aggregates. This gives us the hint that Timesheet itself should be handled as an Aggregate Root (AR):

Another solution we thought about is creating a new Contract Domain Model inside a different Bounded Context where the Timesheet will be living in. This would lead us to Eventual Consistency but it would be possible with our current infrastructure where bounded contexts are shared across the app.

As suggested by @mgonzalezbaile:

use Acme\Contract\Application\Service;

class WriteTimesheetHandler
{
    public function handle($command)
    {
        $timesheet = new Timesheet($command->start(), $command->end());
        $this->timesheetRepository->save($timesheet);

        $this->raiseEvent(TimesheetCreated($contractId));
    }
}
class TimesheetCreatedListener
{
    public function __constuct($event)
    {
        $contract = $this->contractRepository->find($event->contractId());
        $timesheet = $this->timesheetRepository->find($event->timesheetId());
        $contract->addTimesheet($timesheet); // Check invariant
        $this->contractRepository->save($contract);
    }
}

But this way the Contract still has the relations to existing timesheets and all of them would be loaded.

Other thought on large aggregates by @voroninp:

Instead of loading all the timesheets we thought about removing them completely from the Contract. The only connection would be on the Timesheet aggregate via reference (ContractId):

class Timesheet
{
    private $contractId;

    public function __construct(Contract $contract, $start, $end)
    {
        // Policy - move to domain service using repository?
        if ($contract->isClosed()) {
            throw new ContractAlreadyClosedException();
        }

        $this->contractId = ContractId::create($contract->id()); // UUID value object soon
        $this->start = $start;
        $this->end = $end;
    }
}

Still we would load the Contract Aggregate and pass it to the Timesheet aggregate in order to check the invariant.
But passing the Contract AR to the Timesheet constructor doesn't feel good.

Another variation is moving the policy out of the Timesheet into a Domain Service. This Domain Service CanOnlyAddTimesheetToPendingContract could receive the contract repository. I think this is absolutely legal in DDD.
We would only have to pass the ContractId to the constructor instead of the full domain model.

Though I like this approach @VoiceofUnreason states here for a similar example:

Which is to say, if the rules for modifying a Timesheet depend on the current state of the Employee entity, then Employee and Timesheet definitely need to be part of the same aggregate, and the aggregate as a whole is responsible for ensuring that the rules are followed.

An aggregate has one root entity; identifying it is part of the puzzle. If an Employee has more than one Timesheet, and they are both part of the same aggregate, then Timesheet is definitely not the root. Which means that the application cannot directly modify or dispatch commands to the timesheet - they need to be dispatched to the root object (presumably the Employee), which can delegate some of the responsibility.

Related:

Using PHP7 type hinting with Symfony Form Text Type and empty_data returning NULL

Currently there is a issue with the Symfony Form Text Type and the empty_data option.

<?php

class ChangeInspectionDetails extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('problemNumber', TextType::class, [
                'empty_data' => '' // no effect
            ]);
    }
}

Though emtpy_data is set to an empty string the field will return null.

This can be a problem if you use PHP7 type hinting on your Domain Model:

<?php

class Contract
{
    public $problemNumber = ''; // no effect

    public function changeInspectionDetails(
        string $problemNumber = ''
    ) {
        $this->problemNumber = $problemNumber;

    }
}

You will get a Expected argument of type "string", "NULL" given.

There are some workarounds using Data Transformers until this issue eventually can be fixed.
A custom TextType Extension suggested by @webmozart:

The fix "simply" is to explicitly type cast the value to a string before passing it to the method.

Most of the time I'm using Commands as data_class in my Forms:

<?php

class ChangeInspectionDetails extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('problemNumber', TextType::class, [
                'empty_data' => '' // no effect
            ]);
    }


    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => ChangeInspectionDetailsCommand::class
        ]);
    }    
}

This allows us to type cast in two places.

The Command:

<?php

class ChangeInspectionDetailsCommand
{
    public $problemNumber = ''; // no effect

    public function problemNumber()
    {
        return (string) $this->problemNumber; // workaround 1
    }
}

Or the Handler:

<?php

class ChangeInspectionDetailsHandler
{
    public function handle(ChangeInspectionDetailsCommand $command)
    {
        // ...
        $contract->changeInspectionDetails(
            #$command->problemNumber() // no effect
            (string) $command->problemNumber() // workaround 2
        );
    }
}

Hope this helps!

Gist:

Came from:

Related issues:

When, where and how to create Summary Events

Currently we have a service A that publishes messages to the outside world (e.g Service B) whenever 1 of about 20 stage changes happen in our context.

final class StaffMember extends AggregateRoot
{
    public function assignToJobFunction(JobFunctionId $jobFunctionId, string $jobFunctionName): void
    {
        $this->recordThat(AssignedToJobFunction::with($this->staffMemberId, $jobFunctionId, $jobFunctionName));
    }

    // 19 more state changes
}
final class StaffPublisher extends Producer implements MessageSubscriberInterface
{
    /** @var ProducerInterface */
    protected $producer;

    public function whenAssignedToJobFunction(AssignedToJobFunction $event): void
    {
        $this->publish($event->serialize(), 'acme.staff.staff.member_assigned_to_job_function');
    }
}

The consumer does not require to know all the details leading to the actual state change. And we do not want to add a message broker routing key all the time.

After reading the following article by @mathiasverraes we decided to go for a "Summary Event":

You can use a Projection to generate the Summary Event. A Projection is an event listener that persists state from events it listens to, and either exposes that state by responding to queries, or by emitting new events.

The problem with our current implementation:

When the event reaches our Publisher ("Event Listener") only some state data is in-memory of the event. We could try to query the read model. But there is no guarantee that is has already been updated before.

One way (1) to solve that is to force a delay. Or use a cronjob (2 that sends summaries every 2 minutes. The latter solution would require some kind of publishing log to know which summaries were already published.

Another hack (3) would be to query the read model which may not be up-to-date yet. But then overwrite its data only with the properties that changed taken from the event e.g. "jobFunctionId" + Name.

A different approach (4) we are thinking about is adding an event e.g. "StaffMemberModified" or "StaffMemberDetailsSummarized". This could be fired in addition to all the other domain events and include the entire state.

final class StaffMember extends AggregateRoot
{
    /** @var StaffMemberId */
    private $staffMemberId;

    /** @var JobFunctionId */
    private $jobFunctionId;

    /** @var string */
    private $jobFunctionName;

    // 19+x more properties holding the state only for the extra event, currently not required for protecting invariants
    public function assignToJobFunction(JobFunctionId $jobFunctionId, string $jobFunctionName): void
    {
        $this->recordThat(AssignedToJobFunction::with($this->staffMemberId, $jobFunctionId, $jobFunctionName));
        $this->recordThat(StaffMemberDetailsSummarized::with($this->staffMemberId, $jobFunctionId, $jobFunctionName, // entire state...));
    }

    // 19 more state changes
}

This solves the publisher problem. But it adds the entire state on the event-sourced aggregate-root. This has no technical disadvantages though. It's just more code.

Do you have any suggestions?

Thanks in advance.

Came from:

Project suggestion for example

Here is what I thought could represent a good use case to start an example of ddd with all types of relations (one to many, many to many), while being not too complex.

I will make a draft with tests to start a domain model (without symfony, doctrine or command bus). From there, we'll be able to start doing infrastructure, and application context.

Domain requirements:

Mr. Smith is the proud owner of the restaurant Smith's Pizzeria. He wish to have a site to manage it's company and promote it's products on the Web. Here are the requirement he wishes you to implement.

  • The owner is the only employee granted to create new or delete recipes.
  • Recipes have ingredients that can be categorized with allergens
  • a cashier takes the orders of phone, drive through or take out customers
  • a delivery boy (or girl) delivers prepared meals to the customers who ordered by phone
  • a waitress takes orders to seated clients
  • waitress serve the meals to in house customers.
  • performance bonus are alloted to each trimester based on waiting time of customers. Lower it is greater the bonus.
  • bonus are calculated according to the following chart, based on type of client and average time to serve clients (todo)
  • recipes are not available to customers as long as they have not been released.
  • recipes can be back ordered which means that no customers can order them.
  • retired recipes no longer appears on the menu, they have been removed from production.
  • front desk, drive through and in house customers do not keep order history, they are considered anonymous customer, while Web and phone customers are identified with their phone number to track their orders.
  • delivery boy pickup the customer order at an hour, and are expected to deliver it in order of priority based on ordered at time.
  • Waitress should serve all meals of the customer table at the same time, but for bonus reason, the clock start ticking when the first table customer order is entered to the last meal served
  • cashier make the customer pay on order, while delivery boy make them pay when order delivered, and waitress make them pay at end of diner.

@webdevilopers Any other cases you would need that could represent the domain?

Hiding technical implementation for uploading Files with Metadata using MongoDB GridFS

Code examples:

Doctrine MongoDB offers a Default GridFS Repository to upload files and map them to a MongoDB Document. This document has default fields e.g. "uploadDate" that get populated when the document was successfully added to the bucket / collections.
In addition custom data can be stored to a Metadata document.

In our Domain Models we do not want to care about MongoDBs way of storing their native ObjectIds in a "_id" field. That's why we try to hide this "Surrogate Key" by extending the SurrogateIdEntity. We then map the auto-generated MongoDB ObjectId to a column named surrogateId`.

Internally we generated our own UUIDs. Since we want this as our primary key we map the id() method to the UUID mapped to "metadata" since this is the only place we can put it.

These are the first compromises between integrating the infrastructure and keeping the domain "clean".

Next: Object creation

When uploading the file to GridFS it directly adds it to the bucket AND returns a proxy of the created object.
There is no way to create the object and then store it separately.

In order to offer an API without caring about the GridFS implementation we added a factory for the InvoiceDocument.

The factory / domain service takes the required arguments (should be value objects BTW).
Then it passes it to the repository implementation. There is an interface in the domain layer that defines the required method.

This method then uploads the file and returns it to the factory. Done.

What we don't like about this approach:

  • The repository method does two things: saving the object to the bucket AND returning it
  • The save method is returning something at all

We also thought about moving the repository method to the factory. But then it would mix up persistence implementation and object creation.

Are there better approaches? Are these acceptable comprimises due to infrastructure choices?

Thank you for your feedback!

DDD directory structure using Symfony Flex (4/5)

Symfony 4/5 create an App namespace by default refering to alle files inside the src folder.
Initially a Controller folder may be created.
When adding the doctrine recipe it will add Entity, Repository and Migration.
Prooph Event Store will add a Command namespace for instance.

Currently we remove the unused folders e.g. "Entity" since we only use the DBAL not the ORM.
Required folders e.g. "Controller" are moved to src\Acme\BoundedContext\Infrastructure\Symfony. It could also be Framework.

But after creating some new projects moving the files and reconfiguring the Symfony autowiring we are thinking about skipping this task.
We only want to focus on adding our bounded context and (sub-)domains to the organization folder e.g. Acme and keep the Symfony App as it is for a better quickstart.

What do you guys prefer?

Also see:

Creating aggregates and enforcing invariants spanning multiple aggregates

Given is an event-sourced EmploymentContract holding an EmploymentPeriod with a start and end date.
It is then possible to "deploy employees of a contract to other work places".
But the DeploymentPeriod must be inside the EmploymentPeriod.

We want to keep our aggregates small and decided to make Deployment a separated event-sourced aggregate root (A+ES) too.

In a future solution we would create a separate microservice and make them event-driven. Then the could suggest a deployment, check the business rule and eventually fire a `DeploymentRejected" event or similar.

For now we are using CQRS and a deployEmployeeToWorkplace is handled by the command handler application service.

Currently we are thinking of three approaches ("Deployment" and "Solution" 1-3):

Currently we prefer solution 1 which creates the Deployment thought a factory method on the EmploymentContract which already will hold the EmploymentPeriod.
All rules - actually they are not invariants since this does not concern the "transaction" of the Deployment A+ES - can be validated here and return the Deployment.

The only thing we ask ourselves:
Could and should we prevent other team developers from skipping the factory method and calling the named constructor on Deployment directly?

final class Deployment1
{
    public static function with(DeploymentId $anId, EmploymentContractId $anEmploymentContractId,
                                DeploymentPeriod $aPeriod): Deployment
    {
        // No period range validation, final period was already validated and passed
    }
}

Should this be done in your code base in general or should these constructors just be available as is and the rules explained in pair-programming. What are your thoughts?

Final thoughts:
Whatever is the best solution, we are thinking of moving the responsibility away from the handler into a domain factory.

Similar topics:

Discussion started here:

Interesting reads:

Single Commands / Handler vs. Abstract Commands

The discussion originally started on Twitter:

The question was triggered by an older post by @shijuvar:

Later @gnugat and @mauroservienti joined the discussion and @SelrahcD opened a gist here:

Voting results:

In the end I pretty much agree with @SelrahcD

I guess a service and an abstract command are quite the same in the end, so yes, an abstract command would work. I won't use the same command yet and keep the two "real" commands separated.

Directory Structure, Namespaces and Naming Conventions

I initially startet a post here focussing on where to put @symfony bundles:

Recently I found an example for @silexphp by @danitome24:

He was also inspired by the @dddinphp book.
Allthough he does not use the Domain\Model directory. Instead he is separating Entity from ValueObject.
I prefer the approach to share the same folder for Domain Models that belong to an Aggregate.

Instead of Domain\Entity\User and Domain\ValueObject\UserId it should be Domain\Model\User\User, Domain\Model\User\UserId etc..

I'm not sure about the Application layer though. Though it is not the Domain Layer I prefer the same approach in order to quickly identify which Commands, Handlers etc. relate to an aggregate:

  • Application\Service\User\RegisterUserCommand
  • Application\Service\User\RegisterUserCommandHandler
    etc.

What are your thoughts on this @danitome24?

When do I create the UUID and how?

I see everyone everywhere writing about DDD, CQRS, ES but no concrete examples especially when it comes to Symfony or Doctrine. So I am very puzzled about this becuase my requirements are very low:

class Customer {
    protected $customerId; // CustomerId Value Object - uuid5 using the username
    protected $username;
}

class Wallet {
    protected $uuid; // uuid5 with customer uuid as namespace and something random.
    protected $balance;
    protected $customer;
    // __construct($customer, $balance = 0)
}

What made me some headaches yesterday was:

When do I generate the UUID?

This issue might come down to some "best practice". But there are really some points I see with advantages and disadvantages.

I could do it while creating a new object in __construct (DDD-Layer). But in the app-layer I need some setter for doctrine when it loads the entity from the database. I surely don't want a setter for UUIDs - they shall never change.

This could also be done via a factory. Compatible with Doctrine when using a prePersist hook / generate uuid on the fly. But static methods are just bad solutions.

Speaking about on-the-fly I could also do it in the getter getUuid which checks if a uuid is already present and if not it generates a new one.

Read models vs. View models

Came from:

/cc @matthiasnoback @ashishkpoudel @cherifGsoul @stemmlerjs

Just finished reading your "Web Application Architecture" book @matthiasnoback . I finally understood the difference between READ and VIEW models. In your chapter about layers you suggest to put VIEW models into the application layer. But where to put the READ models?
Since in #CQRS for instance a read model represents the state of the write model, some people state it should belong to the domain layer. I agree VIEW models are optimized for presentation and should live elsewhere. E.g. application or as we like to add a "presentation" layer.

@matthiasnoback states:

I'm not sure if the distinction between read and view model is a very common one. For a view model is used to expose data to actors (e.g. users). A read model exposes data to the system itself (i.e. it's for internal use). It makes sense to put that one in the domain layer.

Related:

Unit testing value objects with internal datetime calculation

Came from:

Given a Token #valueobject that internally uses current time to generate expiresAt date.
For unit testing expiration I would prefer reflection and set date - X days.
I would not like to refactor the constructor to receive a expirationStartsAt instead. Other suggestions?

/cc @plalx @BrunoRommens @AntonStoeckl @kapitancho @iosifch @tomjvdberg @swyrazik @dkarlovi @mannion @gilbertotcc @SelrahcD

The original idea was to NOT create an extra service but move all the logic into a value object.

The aggregate root

final class Host extends AggregateRoot
{
    private HostId $hostId;

    private EmailAddress $emailAddress;

    private VerificationToken $verificationToken;

    private bool $verified = false;

    public static function register(HostId $hostId, EmailAddress $emailAddress): Host
    {
        $emailAddressVerificationToken = VerificationToken::generate($hostId, $emailAddress);

        $self = new self();
        $self->recordThat(HostRegistered::with($hostId, $emailAddress, $emailAddressVerificationToken));

        return $self;
    }

    public function verify(string $token): void
    {
        if ($this->isVerified()) {
            return;
        }

        if ($this->verificationToken->token() !== $token) {
            throw new InvalidVerificationTokenException();
        }

        if ($this->verificationToken->hasExpired()) {
            throw new VerificationTokenExpiredException();
        }

        $this->recordThat(HostVerified::with($this->hostId));
    }

Value object

final class VerificationToken
{
    public const LIFETIME = 345600;

    private string $token;

    private DateTimeImmutable $expiresAt;

    private function __construct()
    {
    }

    public static function generate(HostId $hostId, EmailAddress $emailAddress): VerificationToken
    {
        $encodedData = json_encode([$hostId->toString(), $emailAddress->toString()]);
        $signingKey = 'some_secret';
        $token = base64_encode(hash_hmac('sha256', $encodedData, $signingKey, true));

        $expiryTimestamp = time() + VerificationToken::LIFETIME;
        $expiresAt = (new DateTimeImmutable())->setTimestamp($expiryTimestamp);

        $self = new self();
        $self->token = $token;
        $self->expiresAt = $expiresAt;

        return $self;
    }

    public function token(): string
    {
        return $this->token;
    }

    public function expiresAt(): DateTimeImmutable
    {
        return $this->expiresAt;
    }

    public function hasExpired(): bool
    {
        return time() > $this->expiresAt->getTimestamp();
    }

In order to this it would require Relfection. One approach to make it more testable - WITHOUT moving it to an extra service - would be passing the expiration date:

final class Host extends AggregateRoot
{
    public static function register(HostId $hostId, EmailAddress $emailAddress): Host
    {
        $expiryTimestamp = time() + VerificationToken::LIFETIME;
        $expiresAt = (new DateTimeImmutable())->setTimestamp($expiryTimestamp);

        $emailAddressVerificationToken = VerificationToken::generate($hostId, $emailAddress, $expiresAt);

That makes the VerificationToken value object easier to test. But it's still hard to check if the aggregate throws an exception without again using Reflection:

final class Host extends AggregateRoot
{
    public function verify(string $token): void
    {
        if ($this->isVerified()) {
            return;
        }

        if ($this->verificationToken->token() !== $token) {
            throw new InvalidVerificationTokenException();
        }

        if ($this->verificationToken->hasExpired()) {
            throw new VerificationTokenExpiredException();
        }

This could be changed by also expanding the aggregate root by passing a registeredAt date:

final class Host extends AggregateRoot
{
    public static function register(HostId $hostId, EmailAddress $emailAddress, DateTimeImmutable $registeredAt): Host
    {
        $expiryTimestamp = $registeredAt + VerificationToken::LIFETIME;
        $expiresAt = (new DateTimeImmutable())->setTimestamp($expiryTimestamp);

        $emailAddressVerificationToken = VerificationToken::generate($hostId, $emailAddress, $expiresAt);

Though the code looks fine I'm just worried about adding "behaviour & data" that is only required for testing and not originally in the domain logic.

The main idea was to NOT write an extra service and keep the logic inside the value object.
If you want to use a service there a some good examples using a Clock Interface and implementation:

Where and how to handle command (superficial) and domain validation?

Originally posted by @gquemener :

Let's define domain related validation once and for all within well-defined VO and expose those errors to the world. > https://gist.github.com/gquemener/09b2bc303e63dfc3123f7d540e9891a0

It prevents to add extra constraint metadata!
Is this too much magic?

@matthiasnoback and @AntonStoeckl joined the discussion.

@AntonStoeckl also blogged about a solution in GO:

On our case:

We decided to work w/ the #symfonymessenger usind the mentioned validation middleware based on symfony constraints.
VOs are created from command getters. Every domain exception is caught by an listener and converted into human-readable messages.

Example:

# config/packages/messenger.yaml
framework:
  messenger:
    default_bus: messenger.bus.commands
    buses:
      messenger.bus.commands:
        middleware:
          - validation
# config/services.yaml
services:
    Acme\Common\Infrastructure\Symfony\EventListener\ExceptionListener:
        tags:
            - { name: kernel.event_listener, event: kernel.exception }
        arguments: ['@translator.default']
<?php

namespace Acme\Common\Infrastructure\Symfony\EventListener;

use DomainException;
use Prooph\EventStore\Exception\ConcurrencyException;
use Acme\Common\Presentation\Model\NotFoundException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Messenger\Exception\RuntimeException;
use Symfony\Component\Messenger\Exception\ValidationFailedException;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * Handles bad requests and returns a 400 status with human-readable errors to the client.
 * Returns a 500 status otherwise.
 */
final class ExceptionListener
{
    /** @var TranslatorInterface */
    private $translator;

    private $translationDomain = 'exception';

    private $locale = 'de';

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

    public function onKernelException(ExceptionEvent $event)
    {
        // You get the exception object from the received event
        $exception = $event->getThrowable();

        if (!$exception instanceof RuntimeException) {
            return;
        }

        $response = new JsonResponse();
        $response->setStatusCode(Response::HTTP_BAD_REQUEST);

        if ($exception instanceof ValidationFailedException) {
            // Handle domain specific exceptions - public to the client
            $errors = [];

            /** @var ConstraintViolation $violation */
            foreach ($exception->getViolations() as $violation) {
                $errors[] = [
                    'message' => $violation->getMessage(),
                    'path' => $violation->getPropertyPath()
                ];
            }

            $response->setContent(json_encode(['errors' => $errors]));
            $event->setResponse($response);

            return;
        }

        if ($exception instanceof HandlerFailedException) {
            // Handle presentation and domain specific exceptions - public to the client
            $errors = [];

            if ($exception->getPrevious() instanceof NotFoundException) {
                $errors = [
                    'message' => $exception->getMessage(),
                    'path' => null
                ];

                $response->setContent(json_encode(['errors' => $errors]));
                $response->setStatusCode(Response::HTTP_NOT_FOUND);

                $event->setResponse($response);

                return;
            }

            if ($exception->getPrevious() instanceof DomainException) {
                $errors[] = [
                    'message' => $this->translator->trans(
                        $exception->getPrevious()->getMessage(), [], $this->translationDomain, $this->locale
                    ),
                    'path' => null
                ];

                $response->setContent(json_encode(['errors' => $errors]));
                $event->setResponse($response);

                return;
            }

            // Handle individual server errors if relevant to the client
            switch (get_class($exception->getPrevious())) {
                case ConcurrencyException::class:
                    $errors = [
                        'message' => 'Duplicate entry',
                        'path' => null
                    ];

                    $response->setContent(json_encode(['errors' => $errors]));
                    $event->setResponse($response);

                    return;

                    break;
            }
        }
    }
}

The listener can be expanded for individual exceptions. Exception messages are simply translated.
If it is a Symfony Constraint error message it normally was already translated. In addtion the path will be added.

The JSON result normally looks like this:

errors: {
    message: "Some error in the command DTO"
    path: "firstName"
}
errors: {
    message: "Some error in the domain e.g. thrown inside value object"
    path: null
}

AFAIK Symfony Messenger can be used without Symfony. You can use it as a standalone service bus.
The Validation middleware is included too. Putting the "logic" of the listener elsewhere should be no problem.

WDYT?

Older possibly related issues:

Directory Structure and Namespaces using Symfony Bundles

My structure is inspired by this highly recommended book written by @carlosbuenosvinos, @theUniC and @keyvanakbary:

src/Acme/QualityManagement\Application\Services\ (All my Commands, Queries and Handlers go here)
src/Acme/QualityManagement\DomainModel\Order\Order.php (and OrderId, OrderRepository etc.)
src/Acme/QualityManagement\Infrastructure\Persistence\Doctrine\OrderRepository.php (ORM and ODM Repositories implementing the DomainModel Interfaces)

The UserInterface aka PresentationLayer is currently missing. Maybe there won't be a UserInterface in this src folder after all when a frontend developer creates a separate AngularJS app for instance.

Currently I'm using a Symfony Bundle for this:
src/Acme/QualityManagementBundle

It holds the Doctrine XML Mapping for ORM and ODM, Forms, Controllers and Tests.
Translations and templates were moved to the app/Resources/QualityManagementBundle folder.

I ask myself if this "Bundle" actually is some kinde of Presentation or even Application Layer.
Should I move it to:
src/Acme/QualityManagement\Application\QualityManagementBundle

Should I try to include the Bundle into the Application folder?
src/Acme/QualityManagement\Application\Controller (formerly QualityManagementBundle\Controller)
src/Acme/QualityManagement\Application\Form (formerly QualityManagementBundle\Form)

Or is the Bundle the Presentation Layer? Should I keep the QualityManagementBundle name and move it to the Domain:
src/Acme/QualityManagement\UserInterface\QualityManagementBundle
or
src/Acme/QualityManagement\Presentation\QualityManagementBundle

or try to override the Symfony internals to use the correct layer naming instead of the Domain name QualityManagement with the the Bundle suffix?
src/Acme/QualityManagement\UserInterface (formerly QualityManagementBundle)
or
src/Acme/QualityManagement\Presentation (formerly QualityManagementBundle)

Looking forward to thoughts from @leopro and @willdurand!

How to test application service command handlers dealing with read models?

The following implementation uses a CQRS Login Query through the service bus.
The authentication currently is based on Symfony Security and the UserPasswordEncoderInterface.

How would you test this scenario?

  1. First of all, would you move the logic away from the handler e.g. to a "LoginService"?

  2. Would you even consider loading the WRITE model (event-sourced aggregate-root) though you don't want to change state?

Though you could add events e.g. "LoginFailed" to fill some audit log. But this actually is a query that should return a valid token.

  1. Would you e.g. mock the repository or type-hint the interface and test different read model scenarios. Keeping the logic inside the command handler?

Login Controller

final class LoginController
{
    private MessageBusInterface $queryBus;

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

    public function __invoke(Request $request): Response
    {
        $query = new LoginHost(json_decode($request->getContent(), true));

        $envelope = $this->queryBus->dispatch($query);
        $handledStamp = $envelope->last(HandledStamp::class);

        return new JsonResponse($handledStamp->getResult(), Response::HTTP_OK);
    }
}

Read Model

final class HostReadModel
{
    private HostId $hostId;

    private EmailAddress $emailAddress;

    private EncodedPassword $password;

    private bool $verified = false;

    private string $locale = 'de';

    public static function fromArray(array $data): HostReadModel
    {
        $self = new self();
        $self->hostId = HostId::fromString($data['hostId']);
        $self->emailAddress = EmailAddress::fromString($data['emailAddress']);
        $self->password = EncodedPassword::fromString($data['password']);
        $self->verified = $data['verified'];
        $self->locale = 'de';

        return $self;
    }

    public function isVerified(): bool
    {
        return $this->verified;
    }
}

Command handler

final class LoginHostHandler
{
    private HostDetailsFinder $hostDetailsFinder;

    private UserPasswordEncoderInterface $passwordEncoder;

    private JWTEncoderInterface $jwtEncoder;

    public function __construct(
        HostDetailsFinder $hostDetailsFinder,
        UserPasswordEncoderInterface $passwordEncoder,
        JWTEncoderInterface $jwtEncoder
    )
    {
        $this->hostDetailsFinder = $hostDetailsFinder;
        $this->passwordEncoder = $passwordEncoder;
        $this->jwtEncoder = $jwtEncoder;
    }

    public function __invoke(LoginHost $query): array
    {
        /** @var HostReadModel $hostDetails */
        $hostDetails = $this->hostDetailsFinder->ofEmailAddress($query->emailAddress()); // CQRS Read Model

        if (!$hostDetails->isVerified()) {
            throw new HostNotVerifiedException();
        }

        if (!$this->passwordEncoder->isPasswordValid($hostDetails, $query->password()->toString())) {
            throw new InvalidCredentialsException();
        }

        return ['token' => $this->jwtEncoder->encode($hostDetails->toArray())];
    }
}

How to pass (which) data from Handler to Domain Service

My CalculateDormerHandler needs to pass data from a CalculateDormerCommand to a DormerCalculator Domain Service.

Currently the Handler is passing the complete DTO (Command including a lot of args) and some Infrastructure concerns to the Domain Service:

class CalculateDormerHandler extends AbstractCalculateDormerHandler
{
    public function handle(CalculateDormerCommand $command)
    {
        // Pass to domain service calculator
        $dormerCalculation = $this->dormerCalculator->calculate(
            PartnerId::create($this->getTokenStorage()->getToken()->getUser()->getId()),
            $command
        );

        $this->dormerCalculationRepository->save($dormerCalculation);
    }
}

ATM the Domain Service then transforms some of the args from the DTO into Domain Models:

class DormerCalculator
{
    public function calculate(
        PartnerId $partnerId,
        $command
    ) {
        $this->partner = $this->partnerRepository->ofId($partnerId);
        $this->gutter = $this->calculateGutter($command->gutter());
    }

    private function calculateGutter($gutterData) : Gutter
    {
        if (true !== $gutterData['add']) {
            return;
        }
        return new Gutter(
            GutterMaterial::create($gutterData['materialId']),
            GutterColor::create($gutterData['colorId'])
        );
    }
}

I'm thinking about refactoring the Domain Service and move the creation of the required Domain Models to the Handler.

class CalculateDormerHandler extends AbstractCalculateDormerHandler
{
    public function handle(CalculateDormerCommand $command)
    {
        if (true !== $command->gutter()['add']) {
            $gutter = new Gutter(
                GutterMaterial::create($gutterData['materialId']),
                GutterColor::create($gutterData['colorId'])
            );
        }

        // Pass to domain service calculator
        $dormerCalculation = $this->dormerCalculator->calculate(
            PartnerId::create($this->getTokenStorage()->getToken()->getUser()->getId()),
            $gutter
            // ...
        );
    }
}

class DormerCalculator
{
    public function calculate(
        PartnerId $partnerId,
        Gutter $gutter = null,
        // ...
    ) {
        $this->partner = $this->partnerRepository->ofId($partnerId);
        $this->gutter = $this->gutter;
    }
}

This way the Domain receives Models that it understands. In addtion every other Application or even Domain Service could use the DormerCalculator Domain Service.

As I mentioned my command holds a lot(!) of arguments. Should the Handler still carry out the task as refactored?

Implementing the Specification Pattern with Doctrine Repositories

There are some nice tutorials out there:

For Zend Framework too by @UFOMelkor:

Another implementation by @mbrevda:

And @maximecolin:

But there is also a implementation for Doctrine by @Happyr @Nyholm @cakper:

Anybody else with other implementations? What are your experiences? Any best practices?
Thanks for the feedback!

Using the Symfony Event Dispatcher for Application Events

Here is a question by @chrisguitarguy that came to mind before when I was using the @SimpleBus by @matthiasnoback:

Do you hide your event emiting code behind an interface you own in your #php apps? Why or why not?
By that, I mean instead of using Symfony's event dispatcher directly you create an interface and a Symfony adapater implenting it.

The @SimpleBus has a SmyfonyBridge:

And for the RabbitMQBundle too:

I remember trying out the tutorial:

But I think I didn't figure out why the events did not actually fire.

Does anybody have a working example or code on github?

Synch vs asynch messaging

I recently started implementing RabbitMQ for some memory and time expensive operations e.g. mailing or generating reports.

Before I had a WRITE model that raised an event with all required data.
This event was then consumed by an event bus (@SimpleBus with DoctrineORMBridge) and mailing for instance was handled synchronous.

My first step was to simply serialize the event and publish it via RabbitMQ producer instead of the event bus.

Is this simple replacement fine?

Do you prefer using asynch messages in general or keep simple tasks (updating READ model) synch?

Related:

Came from: https://twitter.com/webdevilopers/status/829703225452457988

Inviting @michaelperrin @Sander-Toonen @mablae @tPl0ch @cakper @ChristianRiesen

Passing read models (value objects representing state) / domain service to aggregate methods

Came from:

Given a Rule spanning aggregates: existing contract periods should not overlap new period. How much „logic“ in repository? 1) query all contracts and service class checks overlap 2) query and check overlap by passing new period (logic in SQL)

/cc @AntonStoeckl @plalx @BrunoRommens

After the discussion I moved my entire logic from an extra service and repository methods to the aggregate.

A few things to consider: - Logic in infrastructure implies integration tests verification. - Checking in service leaks BL outside the model -- I'd start trying: 1. contract.changePeriod(period, otherPeriods) 2. contract.changePeriod(period, overlapDetector). -- Refactor...

As suggested by @plalx.

Event-sourced aggregate root (A+ES)

final class ServiceContract extends AggregateRoot
{
    public static function enter(
        ServiceContractId $anId, PersonId $anPersonId, PersonalData $aPersonalData,
        AssignmentPeriod $anAssignmentPeriod, WorkweekDays $aWorkweekDays, WeeklyWorkingHours $aWeeklyWorkingHours,
        JobFunctionId $aJobFunctionId, string $aJobFunctionName,
        AgencyId $anAgencyId, string $anAgencyName,
        WorkplaceId $aWorkplaceId, string $aWorkplaceName,
        array $enteredContracts
    ): ServiceContract
    {
        Assert::notEmpty($aJobFunctionName);
        Assert::notEmpty($anAgencyName);
        Assert::notEmpty($aWorkplaceName);

        $self = new self();
        $self->detectConflicts($anId, $enteredContracts, $anAssignmentPeriod);
        $self->recordThat(ServiceContractEntered::withData(
            $anId, $anPersonId, $aPersonalData,
            $anAssignmentPeriod, $aWorkweekDays, $aWeeklyWorkingHours,
            $aJobFunctionId, $aJobFunctionName, $anAgencyId, $anAgencyName,
            $aWorkplaceId, $aWorkplaceName, new DateTimeImmutable()
        ));

        return $self;
    }

    private function detectConflicts(ServiceContractId $id, array $enteredContracts, AssignmentPeriod $period): void
    {
        Assert::allIsInstanceOf($enteredContracts, Term::class);

        foreach ($enteredContracts as $enteredContract) {
            if ($id->sameValueAs(ServiceContractId::fromString($enteredContract->contractId()))) {
                continue; // Used only when editing the assignment period, not for actual creation of the contract
            }

            if ($period->overlapsWith($enteredContract->assignmentPeriod())) {
                throw new AssignmentPeriodOverlapsException();
            }
        }
    }
}

Application Service Command Handler

final class EnterServiceContractHandler
{
    public function __invoke(EnterServiceContract $command): void
    {
        $person = $this->personDetailsRepository->ofPersonId($command->personId()->toString());

        $enteredContracts = $this->contractTermRepository->ofPersonId($command->personId()->toString());

        $contract = ServiceContract::enter(
            $command->contractId(), $command->personId(),
            ... personal data from the person etc. ...
            $enteredContracts
        );

        $this->contractCollection->save($contract);
    }
}

As @BrunoRommens suggested the enteredContracts are no full details read models. They are just value objects holding the actual assignment periods of the other contracts entered by the person.

The "Available periods" aggregate contains only "Period" value objects. Maybe the tradeoff (against nature in classical DDD), is to implement this type of aggregate as an interface in the domain with an implementation in infrastructure handling available periods finding operation

Unit test

final class ServiceContractTest extends EventSourcedAggregateRootTestCase
{
    /** @test */
    public function signingDetectsConflictWhenExistingPrecedingContractIsStillActive(): void
    {
        $this->expectException(AssignmentPeriodOverlapsException::class);

        $enteredContracts = [
            Term::fromArray([
                'contract_id' => 'a391c713-2cf7-4500-b997-e37ea24c5889',
                'person_id' => '71efb6b7-6eb1-4b17-b1d5-a3a47bac78f1',
                'start_date' => '2019-02-01',
                'end_date' => '2020-01-31',
            ])
        ];

        ServiceContract::enter(
            ServiceContractId::fromString('b88f676e-221d-4807-97c9-8f549b13425a'),
            PersonId::fromString('71efb6b7-6eb1-4b17-b1d5-a3a47bac78f1'),
            $this->getPersonalData(),
            AssignmentPeriod::with(
                new DateTimeImmutable('2020-01-01'),
                new DateTimeImmutable('2020-12-31'),
            ),
            WorkweekDays::fromArray(["monday","tuesday","wednesday","thursday","friday"]),
            WeeklyWorkingHours::fromFloat(40),
            JobFunctionId::fromInteger(4), 'Qualitätsprüfer/in',
            AgencyId::fromString('904c0436-4226-4725-afbe-14526906afdb'), 'Office People',
            WorkplaceId::fromString('1e522e02-b28d-44d2-a3fb-4efbdacec462'), 'Bratislava',
            $enteredContracts
        );
    }
}

How to return created entity with REST and DDD using POST request

Hi,

I have an API and use command bus (the @thephpleague's Tactician) to handle commands.

The question is how to return new created entity after POST request in the body?

Controller's code (simplified):

public function postAction(Request $request)
{
    $command = new CreateTaskCommand($request->get('name'), $request->get('startDate'));

    $this->get('command_bus')->handle($command);

    return $this->createView(???); // here I want to return new created entity as a response body with 201 CREATED status code
}

Keeping in mind that command bus can't return anything, what is the good way to get the created entity?

I found two recommendations:

  • To use domain events and listeners, but it's not clear whether I need to iterated over recorded events in the controller and how to get created entity
  • to fetch the created entity after it's created by the command bus, using the UUID

For the second point the code might look like:

public function postAction(Request $request)
{
    $uuid = $request->get('uuid'); // or new TaskId();
    $command = new CreateTaskCommand($uuid, $request->get('name'), $request->get('startDate'));

    $this->get('command_bus')->handle($command);

    // here we fetch the new entity from the storage
    $entity = $someService->find($uuid);

    return $this->createView($entity);
}

How do you guys solve this case? Maybe creators of command buses (@matthiasnoback, @rosstuck) know something about this?

Thanks in advance.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.