HandlesEvents
design considerations
#179
-
With the the current implementation of v5, there are as much as four ways to configure projector and reactor handlers:
class CartProjector extends Projector
{
public function onCartInitialized(CartInitialized $event): void { /* … */ }
}
class CartProjector extends Projector
{
protected array $handlesEvents = [
CartInitialized::class => 'onCartInitialized',
];
public function onCartInitialized(CartInitialized $event): void { /* … */ }
}
class CartInitializedProjector extends Projector
{
// Explicit mapping for one specific event
protected string $handlesEvent = CartInitialized::class
public function __invoke(CartInitialized $event): void { /* … */ }
}
class CartInitializedProjector extends Projector
{
// Explicit mapping using attributes
#[ListensTo(CartInitialized::class)]
public function onCartInitialized(CartInitialized $event): void { /* … */ }
} It's my opinion that this amount of flexibility doesn't serve better DX. It's often times the opposite:
class CartInitializedProjector extends Projector
{
#[ListensTo(CartInitialized::class)]
#[ListensTo(CartItemAdded::class)]
#[ListensTo(CartItemRemoved::class)]
public function __invoke(CartEvent $event): void { /* … */ }
} This doesn't happen very often though. I propose we pick one approach, and use it for reactors, projectors and aggregate root apply methods. |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 4 replies
-
My personal opinion is that we go with attributes, but with a few more tweaks:
Here are a few examples: #[Handles(CartInitialized::class)]
#[Handles(CartItemAdded::class)]
#[Handles(CartItemRemoved::class)]
class CartInitializedProjector extends Projector
{
public function __invoke(CartEvent $event): void { /* … */ }
}
class CartInitializedReactor extends Reactor
{
#[Handles(CartInitialized::class)]
public function onWhateverYouWantToCallThis(CartInitialized $event): void { /* … */ }
}
class CartAggregateRoot extends AggregateRoot
{
#[Handles(CartInitialized::class)]
protected function applyCartInitialized(CartInitialized event): void { /* … */ }
} I'd suggest to drop support for everything else. |
Beta Was this translation helpful? Give feedback.
-
I'm ok with renaming I do like the several methods to configure projectors and reactors now. For our projects we'll likely only use attributes. I can image that newcomers to the package might prefers one of the other options which reflects Laravel's way of configuring event listeners. |
Beta Was this translation helpful? Give feedback.
-
I'm already going to add |
Beta Was this translation helpful? Give feedback.
-
I do not think you all should worry about newcomers and what they prefer. If it were me, the questions I would be asking are:
I don’t think the reflection way is worth having around. It’s certainly a nice little feature, but it probably does have a (very minimal) performance hit and there’s enough of those hidden magical things in Laravel already. I like both attributes and the array. That said, the attributes do not look as clean to me. I think that might be because of how repetitive they are with the class name It sounds like you all are leaning in the direction of attributes, so I’d say roll with it and any users of this package will adapt if necessary. I also like the renaming of ListensTo to Handles. 👍🏼 |
Beta Was this translation helpful? Give feedback.
-
I'm actually in favor of "With a magic mapping based on the argument's type hint". Specifically because we're already auto-discovering projectors and reactors. Otherwise, setting up a projector means:
Why is step 2 automatic, but step 4 deemed too magical? And it's not because PHP 8 attributes are a language construct that using them to register events is less magical. I don't think the difference between argument reflection and attributes is about the magic, it's implicit vs. explicit. I think we need to check these two boxes for a good implementation (feel free to add more):
public function onCartInitialized(CartInitialized $event): void { /* … */ } This snippet checks them both. It also reduces the chance of a certain kind of bugs. Have fun debugging this one: class CartInitializedProjector extends Projector
{
// Explicit mapping using attributes
#[ListensTo(CartUninitialized::class)]
public function onCartInitialized(CartInitialized $event): void { /* … */ }
} Mapping an event to a listener in this context is plumbing. The less plumbing I need to do, the more energy I have left to work on things I care about. The less plumbing in my code, the less lines I need to write, read, or change. |
Beta Was this translation helpful? Give feedback.
-
@brendt Let's just keep supporting the current behaviour |
Beta Was this translation helpful? Give feedback.
@brendt Let's just keep supporting the current behaviour