Comments (8)
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.
@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.
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.
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.
@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.
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.
from php-ddd.
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)
- Repositories inside or outside Domain Services HOT 1
- Event Enriching and external changes to read-model data
- When, where and how to create Summary Events HOT 3
- Passing read models (value objects representing state) / domain service to aggregate methods HOT 6
- Unit testing value objects with internal datetime calculation HOT 14
- How to test application service command handlers dealing with read models? HOT 12
- Process Manager example with Symfony Messenger Command / Event Bus and ProophOS HOT 5
- Batch / Bulk operations handling multiple event-sourced aggregate roots HOT 3
- How to use factory methods on aggregates in CQRS - WRITE vs. READ model HOT 1
- How to keep read-models up-to-date when a name property was externally changed?
- How to upcast events with Prooph HOT 1
- Are CQRS commands part of the domain model? HOT 13
- Populate Projection with multiple tables HOT 2
- Where to call or pass a domain service? HOT 16
- How to implement the Equatable interface / Equals or SameValueAs method in value objects
- Domain Event Publisher for Doctrine Entities HOT 1
- Event Sourcing vs. Event-Driven Architecture (EDA)
- The repository pattern HOT 4
- Properties on Domain Events HOT 3
- PHP Command DTO with Symfony Constraints equivalent in Angular Forms HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from php-ddd.