Giter VIP home page Giter VIP logo

Comments (8)

codeliner avatar codeliner commented on July 17, 2024 2

IMHO you have to distinguish between the event itself (the fact stored in the event store) and the representation of the fact in different contexts. F.e. we usually work with a mapping, so our events follow a naming convention: [Context].[EventName] -> ParcelTracking.ParcelDelivered

In the owning context we have a dedicated ImmutableRecord class representing the event. The record is strongly typed. No primitives are exposed. Every information is represented by a type class. Most of the time this is a value object from the domain model, but it could also be a type defined under the event namespace.

If the same event is consumed by another context, we usually put the information in a "GenericEvent" class. You can compare it with a PSR-7 request. It is an immutable class that gives you access to the information of the event.

As you can see it depends on where the event is used, but one thing is always true: the Information carried by the event never changes (unless we migrate events on database level).

That said, I would not pass primitives around in my domain model. When information leaves the event (calling a getter of the event class), the information should be represented by a type. If the type is a value object and the VO changes for whatever reason, then you can keep an old version of the VO as a type under the event namespace. No need to version the VO:

Domain\Model\OldValueObject -> move to -> Domain\Model\SomethingHappened\OldValueObject
-> add new -> DomainModel\ModifiedValueObject

Even better: most of the time you can use the event getter to upcast a primitive before passing it to a modified value object. This way, the rest of the domain model does not need to care about the version of the event.

Disclaimer: Maybe we need to differentiate between Types and Value Objects. In PHP they often look the same. For me a Type represents information. A Value Object on the other hand can protect invariantes and contain domain logic. If both can be handled within the same class - fine. But I still have the option to separate the Type from changed domain logic. No need to fallback to primitives and a higher risk of bugs in the software.

from php-ddd.

webdevilopers avatar webdevilopers commented on July 17, 2024

@yvoyer suggests on Twitter:

Primitives in Event's attributes, Value object as returned value of methods.

In the @prooph example methods return the "primitive" payload:

    class UserWasCreated extends AggregateChanged
    {
        public function username(): string
        {
            return $this->payload['name'];
        }
    }

Then the value objects are created inside the aggregate:

       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;
            }
        }

@gquemener @codeliner @prolic Any reason why you havn't skipped this step entirely using payload directly then?

       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->payload()['username'];
                    break;
            }
        }

from php-ddd.

gquemener avatar gquemener commented on July 17, 2024

I guess it's up to everyone's preference.

Having only primitive values within the messages (commands, queries, events) help serializing them.
However, I believe that getting value objects from them or primitive (and creating value object within the entity) is valid in both case.

Personally, I tend to vote in favor of always storing internally primitive value within an entity or a message and exposing them through value object (created on the fly), but that solely my current opinion.

Finally, I don't like exposing the whole payload of the message, it kind of look like breaking encapsulation (makes me think of a demeter's law break). Imagine UserWasCreated::$payload was an instance of Payload. It means that you would need to call $event->payload()->username(), which is smelly IMO.

But once again, it mostly depends on other factors like the size of your project, the number of people working on it and the coding standard you comply with, and what you show could totally be ok.

Here's an example of event, encapsulating value object creation: https://github.com/gquemener/PhpMyOssContrib/blob/master/src/Contribution/Domain/Event/ContributionOpened.php

from php-ddd.

webdevilopers avatar webdevilopers commented on July 17, 2024

I agree and like your example. Currently the prooph example looks very "aenemic":

    class UserWasCreated extends AggregateChanged
    {
        public function username(): string
        {
            return $this->payload['name'];
        }
    }

Not even properties need to be defined - it all "depends" on the payload.

from php-ddd.

prolic avatar prolic commented on July 17, 2024

@webdevilopers yes, quick and dirty prototyping to help get users up to speed. it's not a recommended best practices section, only something to demonstrate how it works.

from php-ddd.

webdevilopers avatar webdevilopers commented on July 17, 2024

I get it @prolic . In the end I would keep the primitives inside the event for serializing and skipping the toArray boilerplate on the value objects.
But when the events are finalized I use methods that create the value objects from the payload which of course can be arrays.

Would you add a Wither with typed properties to the event similar to the attempt by @gquemener then instead of calling "occur" directly?

        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;
        }

Could the "occur" method not be made protected / private then in order to ensure the usage of a Wither?

Example:

final class UserRegistered
{
    public static function with(UserId $id, string $username):
    {
        $instance = new self();
        $instance->occur($id->toString(), ['username' => $username]);

        return $instance;
    }
}

from php-ddd.

prolic avatar prolic commented on July 17, 2024

from php-ddd.

webdevilopers avatar webdevilopers commented on July 17, 2024

Thanks for your explanation and the examples @codeliner ! I agree.

I guess I was a little bit confused by the RAD demo code. But as Sascha mentioned:

@webdevilopers yes, quick and dirty prototyping to help get users up to speed. it's not a recommended best practices section, only something to demonstrate how it works.

Maybe this code could be improved for future users.

from php-ddd.

Related Issues (20)

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.