Grzegorz Korba Profile picture
Dec 16 22 tweets 9 min read
In recent days I saw several examples of "actions" in #Laravel, but one especially got my attention and I wanted to clarify it - so we had little discussion with @wendell_adriel and I would like to share my opinion about this particular approach 🙂

🧵👇

Before we start, one important preface: I'm big fan of explicitness and testability, so I'm using dependency injection everywhere (where's applicable). Examples in this thread will use it too by default, along with #PHP 8.0+ syntaxes.
What caught my attention was instantiating action inside controller's method 🤔 That looked suspicious, because I'm used to different nomenclature.

My assumption after looking at the example was that `CreateUserAction` is more like a #CQRS command rather than action.
What I personally see as "action" is a single-purpose request handler. While multi-method controllers contain multiple actions and somehow break SRP principle, single invokable controllers are crafted to handle one particular kind of requests (actions).
Both #Symfony and #Laravel use this terminology:

- laravel.com/docs/9.x/contr…
- symfony.com/doc/current/co…

It's also part of ADR (action–domain–responder) pattern 🙂
The difference between those two approaches is that multi-action controllers require all dependencies from every method, while single-action controllers require only dependencies used in this one particular action.

The latter makes instantiating and testing easier. Multi-action controllers = sum of each actions' dependenciesSingle-action controller = fine-tailored dependencies
Let's go back to Wendell's example. What baffled me, was that "action" was instantiated but wasn't actually called inside of `UserController::store()` method (which is... an action itself). That doesn't look good to me not only from semantics point of view.
The thing is `UserController::store()` looks pretty magic. Assuming this action should store user somewhere, it probably requires persistence service (database?). Also we can see it throws some validation exception. But there's only DTO passed to action's constructor... 🤔
So we can safely assume that `CreateUserAction` contains hardcoded instantiation of objects or is using Laravel facades/helpers inside. Either way, I don't like it. As I said, I prefer explicit dependencies while this approach hides those relations inside the action class.
I don't know implementation details, wether it's custom stuff or some Laravel off-the-shelf package, but it looks like it has logic in constructor or maybe in jsonSerialize() method. It's just created in controller and has to be transformed to JSON response somehow 🤷‍♂️
There's always possibility that example was oversimplified for tweet purposes and in real application it's done slightly different 😅 Anyway, that doesn't stop us from discussing it, right?
Wendell said it's done like this because he would like to have multi-method controllers calling separate actions, instead of service methods. In general it makes sense, but IMHO not with this particular implementation 😉
I agree that controller should stay as thin as possible. Get data from the request, encapsulate it somehow and pass deeper. But personally I would use #CQRS, so controller would only prepare command/query and pass it to the proper bus (for queries it also should return result).
This way we also would achieve logic segregation, but without introducing new custom concept or tight-coupling with particular package. Yes, having CQRS buses we also make some coupling, but it can be done by introducing app-level contracts & interchangeable implementation.
In the end we could have command/queries, DTOs and handlers - all universal and framework agnostic. The only infrastructure part would be bus implementation and its usage (passing command/queries). Everything testable, maintainable and extendable (through middlewares). Using command bus in HTTP actionSimple read-only DTO, can be invalidBus' command messageBus' command handler
The great advantage of this approach is that command bus offers universal contract for your app. You can have multiple different entry points sharing the same logic (command handler). For example you can have CLI command that uses the same command, but gathers data differently. Using command bus in CLI command
Yes, I'm aware of laravelactions.com and while it looks like it solves the original problem (reusing action's logic in different contexts), it introduces highly-coupling conventions that make our code pretty much Laravelish. It's the opposite of what I like.
I believe Wendell's approach could be modified slightly to achieve something similar to command bus described above, without actually introducing bus. If `CreateUserAction` was injected as invokable service to the controller, it could take advantage of DI, without magic.
💡Command Bus can be easily implemented within your app using @symfony Messenger component. It's really good, flexible & extendable solution for dispatching synchronous and asynchronous messages 😉 For more info, just read the docs:

symfony.com/doc/current/co…
PS. For more content about #CQRS (and other stuff) you can follow @oskar_at_net, visit his blog event-driven.io and/or subscribe to his architecture-weekly.com newsletter 😎
So let's sum it up. I see such issues in his example:
- misleading "action" concept (probably subjective, vide Laravel Actions package)
- there's too much magic behind probably, I would prefer this action-service to be injected by DI (so it could have its own dependencies)
Some may find my thoughts as clinginess and/or #overengineering - I'm fine with it. Others doesn't need to like what I like 🙂 But we're here to discuss, aren't we?

Let me know what you think and how you deal with reusing logic in your apps 😎

Thank you for reading! 🍻

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Grzegorz Korba

Grzegorz Korba Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

More from @_Codito_

Dec 14
📊🧵 #CoditoStats 🙂

While I don't care much about numbers, I check #TwitterAnalytics from time to time mostly because of monthly summaries. Let me tell you that last 28 days were pretty intense comparing to previous period 😁

Let's look what happened! 👇 Image
First of all @davorminchorov mentioned me in his thread about enums. We initially had discussion about them under his PR (github.com/learnhubdev/la…), but this thread extended it even more 🙂



(187 engagements at this point)
That motivated me to write thread about dictionaries, how I see them from theoretical and practical point of view 🙂 If you missed it, grab it here:

Image
Read 7 tweets
Dec 5
🤔 Dictionaries: should they be in the database? 🤔

Dictionary is set of values that are supported in your application. But how they should be defined? Well, as always, it depends 😉 #PHP #architecture

Let me tell you what I think about it 👇🧵
In general, I believe there are two types of dictionaries:

1) Internal, related to your app's logic
2) UI-managable, related to app instance's data

Let's look about difference between them 🙂
Internal dictionaries are represented in the code. With #PHP 8.1+ it's pretty straightforward - you can use enums 🙂 When you need to represent some state, string-backed enum is the best choice, because it's much better for debugging purposes. PHP 8.1 string-backed enum
Read 18 tweets
Dec 4
💡Value Object: what's that?

Basically, it's a simple object representation of some kind of reusable data structure. Unlike entity, it does not have identity, so value objects are considered as equal if underlying data is equal.

Want to know more? Read below 👇🧵 #architecture Value Object representing name
Value Objects encapsulate data and ensure its validity - constructor can, and should, check if provided primitive values are correct. Thanks to that, when you pass VO to other methods, you can be sure it carries proper information 🙂
Value Object's reusability doesn't mean you reuse data it carries. You reuse its structure across other Value Objects or Entities. Name can be used for modelling Employee, Client and other.

So you can have several John Doe employees with the same Name, but different Id 🙂 Examples of `Name` Value Object usage
Read 8 tweets
Aug 24
There were many discussions if #Laravel's facades implement GoF's Facade Pattern, but I think it does not matter at this point - the team won't change naming convention anyway. Naming is not a problem, I see other issues with facades - a #PHP thread 🧵
1) They're basically magic 🪄 Some may see it as advantage, but I consider it as drawback. You don't execute exact code you're calling, but your call is proxied to some underlying service. It strictly couples your code with the framework, which handles it.
Facades' API has to be added as comments in PHPDoc (with `@Method`) which is error prone because it's easy to forget to update facade's phpdoc when underlying service (accessor) is changed. But even if autocompletion in IDE works, you just can't simply navigate to method's code.
Read 18 tweets

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Don't want to be a Premium member but still want to support us?

Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal

Or Donate anonymously using crypto!

Ethereum

0xfe58350B80634f60Fa6Dc149a72b4DFbc17D341E copy

Bitcoin

3ATGMxNzCUFzxpMCHL5sWSt4DVtS8UqXpi copy

Thank you for your support!

Follow Us on Twitter!

:(