Skip to content

Instantly share code, notes, and snippets.

@rosstuck
Last active October 22, 2020 10:53
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rosstuck/bd47d94841c4eb678ebe to your computer and use it in GitHub Desktop.
Save rosstuck/bd47d94841c4eb678ebe to your computer and use it in GitHub Desktop.
About Self-Executing Commands

I've had this discussion with @TaylorOtwell on IRC last night but reposting here again for you.

This all started because of this Tactician example ( https://github.com/rosstuck/tactician/blob/master/examples/5-advanced-self-executing-commands.php ) but that's a demo of custom handling logic in Tactician, not a HOWTO example.

These "classic" commands work fine for building things with very limited interfaces, like state machines or virtual machines. That's why the Tactician example uses a light switch example and actually encodes that in the interface. There's meant to be a very limited number of possibilities here.

Tactician is a standalone library and can be used for service layers or low-level libraries. But the Laravel command bus is meant for service layers and at a service layer there are a ton of possibilities and a ton of potential dependencies, so this isn't the same use case as a state machine. Even Tactician doesn't ship with this as a default option or even a default implementation.

Anyways, at the service layer level, we want to do more with commands. We want to put them in queues, send them over networks and post them to endpoints. We also want them to evolve separately from the forms and the models around them.

The first set of features all rely on good serialization, which you can't do with lots of dependencies. Yes, Laravel tries to work around this with serializing entities and method level DI. But entity serialization in general is a rotten mess and it will break. Why base this entire system on something inherently flaky?

Also, method level DI is something you see rarely in PHP, making this even more Laravel specific. And, more damning, this all counts on there being a remote side with the exact same software, in the exact same language, with the exact same version. That's a lot of assumptions about a distributed system.

The second thing we want is for the commands to evolve separately from the forms and models. But when the execution method is inside the object, the coupling is going to happen. People will make little extra fields or getters or hacks inside the object to make forms or execution easier and it's going to get confusing and messy.

So, the commands for a service layer work best as plain data. They're more like “structs” in C, than objects in PHP. This helps us get the serialization and decoupling right. You might make other choices when building libraries or virtual machines but it's a different set of tradeoffs.

Finally, I don't see the major advantages of self-executing commands as a framework default. The ones I see cited are:

It's simpler: Because it's one object less? That can actually make it more complex. You have a choice: That's doesn't fix a poor default. We can: it doesn't mean you should. It doesn't break SRP: See above point but even if it’s true, there are two ways to look at this, neither of which holds with SRP.

  • A command is a class: If this is true, I agree with Mathias Verraes: a command represents an intention, a handler represents an interpretation. Those are two different responsibilities.
  • A command is a data structure: If that’s the case, it’s just dumb data and shouldn’t have any responsibilities.

In conclusion, as the author of a fellow command bus library and someone who's used this in the past, I'm recommending: don't do this. If you're worried about inadvertently producing an era of bad, command-oriented Laravel apps, this is a pretty good way to start. On the other hand, this could be a good opportunity to help devs capture user intent and promote good good architecture, in a way that won’t blow minds.

@BenCavens
Copy link

Major plus for separating command and handler is the ability to run the command through middleware like sanitizer or a validator. When the command reaches the handler, the command properties are ready to be digested.

@keiosweb
Copy link

Laravel ended up with self-handling commands by default in 5.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment