The Intermediate Developer Trap
Вставка
- Опубліковано 12 вер 2024
- All it takes is one book about SOLID to radically transform the way you write code. Often, it's a transformation for the better. ...But not always. Let's chat about what I call the intermediate trap, or the tendency to turn a small handful of lines into a web of confusion. In this Larabit, we'll reverse-engineer our code for the better.
Watch thousands of videos, track your progress, and participate in a massive Laravel community at Laracasts.com.
Laracasts: laracasts.com
Laracasts Twitter: / laracasts
Jeffrey Way Twitter: / jeffrey_way
Watch thousands of videos on laracasts.com
As a solo developer, I tried to stock to all the canonical practices, but soon realized that I'm overcomplicating my life simply because "this is the way". And the more events, the more listeners and more complicated the structure would be. I had days when I was navigating mindlessly through controller->event->listeners->actions/services.... until I found my own way, and now I'm refactoring events and listeners into actions and life is beautiful again.
This sparked a discussion within our team. One of the argument to this approach is that in a larger project, each listener often has many more than the 3 lines of code shown in this example and grouping all listeners logic together will result in a very bloated Action. More importantly, the code in the action is synchronous procedure meaning if one of them failed then the remaining won't execute whereas in the event and listener approach, you can simply make a directory and group the listeners together by event.
True
Could catch exceptions to help with that.
Could also argue that in many cases, you would want transactions and rollbacks if one thing fails, so having it in one file in that case is clearer.
Your points are valid, but the main thing is that there are always arguments to both sides.
You can and should split out actions into multiple actions, once an action starts to experience that "bloat" or when you find you need to re-use a subset of an actions logic in another action. You still retain the benefit, in this example, of being able to see all the side effects of marking a best reply, because you are invoking the logic explicitly from the MarkBestReply action.
@@joerushton886 Yes, that is what I do as well, There is nothing wrong with calling another action inside your action.
Yea, I don't really get it either. Even more so when you don't use auto-discovery for event listeners, you can simply do array key-value mapping in EventServiceProvider, making it plain and simple as to what happens on certain event. It's not much work and provides more visibility to side-effects.
First of all, the videos are looking amazing!
I agree with you on that example, but I wanted to add to the conversation (which I find very interesting): I think two of the most important things when it comes to event-driven functionality are 1) the failure model and 2) coupling.
First, I think that if you can't justify doing something (e.g doing something because of SOLID, or because a book said so) yourself, this is problem itself.
More often than not you have relatively complex processes - IIRC, on Modular Laravel I use e-commerce as an example: checking out a cart isn't just processing a payment and creating a record on orders: there is updating inventory and shipment to begin with, but more complex systems might also deal with warehouses, reaching out to distributors, shipping from different warehouses, cashback systems, etc., and in these situations the isolation/failure and decoupling model provided by events makes a lot of sense.
By having each process independent from one another it's easier to deal with coupling between subsystems, handling non-catastrophic failures or gracefully fail, fault containment, observability, etc.
Events also map closely to business requirements - they often go "when X happens, do Z", and events are a perfect narrative factor for that. By inspecting the commands and events of an application you can get a very nice overview of its capabilities.
All in all, I agree with you - when we reach that intermediate level, it's easy to want to apply all the fancy techniques and patterns. Indirection always comes with a cost, so when implementing something, I think a fair question is: can we justify *why* we want to implement it in such a way, and can we describe the benefits and downsides of that?
I am going to follow this approach. Thank you for these great insights! Keep it up; we love your content, Jeff!
Read a blog post by Freek a couple years ago about this; adopted it and never looked back. This is the way.
Saaaame.
This idea of having a sort of "entry point class" that provides an overview of a given functionality, where you can easily see the flow based on the functions defined, is very clever. Coz in just one file, you get to see how everything is pieced together. Then, every other file after it, regardless if it's a mountain of code, we already have a pretty good idea of the path we're trying to navigate.
I always break the rules, I often focus on how readable, and maintainable the codes are. You simply nailed it
This is such a great video. So much can be learned when you're not so SOLID minded and more like a "gut feeling" guy.
100 with you. events are for comunication over boundaries like websockets / message bus / modules
I like code simplicity as much as the next guy. But it's another intermediate dev trap to think that our job as software developers is to implement the happy path and be done with it. The code _will_ face issues. The developer has to think about and write code for handling errors and doing them in a user friendly way (doesn't just mean showing a nice error message), keeping data integrity both by guarding against obvious problems and by thinking about recovery paths.
If the app is anything but a simple todo or another learner's app, the code will get considerably more complex than this.
This is *not* a jab at the video, but simply a reminder to everyone that what you see in the video is not the full story.
More content like this please, Jeff😊
after that amazing course of laravel we want another course about vuejs3 in laravel and livewire pls and thank you Laracasts for everything
I'd suggest putting all 3 side effects in queued jobs. The API consumer (user in most cases) doesn't directly care about any of those 3 side effects. The thing they are doing is marking a reply as "best." The other things can be eventually consistent, by means of queue.
Depends on the importance of the side effects, for some use cases this might not be efficient
I agree especially with scalability. But to his point, this lets you know what actions are part of the process. You can choose to queue instead of run the actions. But you still get one source of truth.
Depends on whether the order of side effects is important. Your notification might need the result of adding the experience for instance. You can still use the action in a queued job.
I follow your video since codeigniter 2..thank you for your guidances
This really helps me refactor a messy project that is hard to understand every time I come back to it to make small changes or fix bugs. Even though I am the only person who build it, it still makes my brain hurt.
This is awesome. Thank you sir. I learned this simplicity
You have just solved my team's issues
Thanks for making coding fun!
Actions seems to be a similar to concept or service classes. You extract the advanced logic grouped arround the same topic out of the controller to a dedicated class.
Great. I will do that to my project. This will help a lot. Thanks.
I use event listeners when the action to be taken is optional or when I need to trigger external functionality that is not directly affecting my app workflow usually in some kind of general utility classes
Thanks Jeff!
I tend to return '$this' from these side effect type methods so I can chain them. It reads better IMO, and saves some characters and a few semicolons.
This is fundamentally a very similar principle to state pattern, transactions, or even activity log. I prefer to use Job as a base class in Laravel for these classes so they can be dispatched (synchronously or asynchronously) and I normally try to include a validation step (to ensure that e.g. the reply instance is in a valid state to be marked as best) but the benefits of this structure are obvious in hindsight. Audit logging of the execution of Action classes are also much more user friendly rather than the standard practice of audit logging database update statements.
Great quality on your videos as always :D
But I disagree 😅 This breaks on larger projects.
On a larger projct, you need to mange this yourself:
* Set the $reply->status('becoming-best')
* Chain those 3 as jobs
* Have a 4th job to "complete" the action and set the status as $reply->status('best')
Absolutely logical
One thing to remember is that time spent in an action is time a user is waiting
you were right, this pissed me off 😅
i do use actions but for repeated actions only , and i use the event service provider to link events to listeners in order to not forget what tirgers what.
Thanks for the insights...
I was watching the beginning of this video and thinking... "This seems like the 'right' way to do this with all the listeners, but in practice it can be so confusing and hard to follow" and then you said basically the same thing. This is how I would do it. And to be honest, I might even just leave it in the controller occasionally, depending on the size of the project and how many side effects of the action.
Simply logical. Good stuff
I really enjoy these personal opinion-based videos-thanks for sharing, Jeff! Does that mean you don’t typically use events/listeners for your own code within your Laravel apps (other then as a package API)? If you do use them, I'd love to see an example where the event/listener approach is more maintainable for you compared to placing code in services or actions. I'm also curious if that means you're not a big fan of the techniques proposed in the "Modular Laravel" course from Laracasts.
Hey! What editor or IDE he is using in the video? I like the clean design.
yeah .. if readability is our priority this approach is the best. Still, if you think those actions will use in other places in the application, event listeners are a better approach, in my opinion, whoever uses laravel to create applications he/she must know about the framework feature.
It's a good one I'm using laravel action package this kind of thing.
I like this approach.
Such a great insight!
Hi Jeff, is it a good practice to reuse listener as opposed to grouping it as it is shown when refactored? thanks
I've switched between both approaches. I like listeners for events that are triggered by an external resource, which is similar to your package example. Actions work better where the sequence is important. In your original code, if you wanted to notify the user that their XP had increased to nnnnn then its important that the increments XP code is executed first. In Laravel 11, the ability to order the listeners has been lost in favour of auto-discovery which probably requires juggling class names to set the order - fragile !!!
So using a Service Class approach could the action class lead to a set of rules that is not present on the service?
You still need an event listener to do an action after the async task is done. Example i have to log or update a status after the notification sent. If the notification failed to send i need to know.
While i agree about the code organization etc. primary actions shouldn’t be put in listeners. But as soon as you have side effects for processing on the queue, or using cqrs you do need to use some sort of queuable events. So this is still within the intermediate trap in my opinion, since all you have done is extract from the controller into an action. No db transaction used, no knowing about failure etc. I was hoping for a more advanced level solution to see how do you make that step up…. I think this needs follow up with more gradual complexity.
Hello, just one question since you do this, what stops you from putting it in the controller, wouldn't it be the same?
I follow pretty much the same approach (move from initial listeners to actions/jobs/pipelines). However my epiphany was driven by implementing transactions and dealing with race conditions rather than thinking about how it would look a year from now 😅
What is this theme and font name that you are using?
I really like this way of coding, it's simple and elegant, but what if you need some kind of response from the action or truck any failures on the way?
You can use an event or queue if you must trigger so many other things.
Was going to ask the same thing
throw an exception in the action and use try-catch in the controller if you expect there could be errors.
Please post some more such trap videos.
Isn't it good practice to run these sides as queue? Where you could easily do using listeners.
I love this!
I would like your opinion on event sourcing, it kinda enforce us to use the first approach. I don't mean on a microservices separated app. Thanks
I follow the same approach.
this is the way indeed :)
I have been using Laravel since version 3. I'm still using this pattern, I never liked the listener and events.
What would you consider non-primary actions / sideeffects?
Hey, where can I get that cool wallpaper?
Does action class also good for utility functions? which can be accessible in every controller of the app? Still learning Laravel so please dont get mad on my noob question.
This makes sense.
Hie Jefrey what advice can you give to someone who wants to get a Job as a junior PHP developer I mean there is too much to learn when starting out.😢
Awesome thanks
Most websites are massively over-engineered.
100% agreed!
I'm curious how do you move code to just created function? :)
Nice refactor, but in my opinion we are not concidering reusability here !
I'm pretty sure that there are alot of other places where we need to record some kind of activity or increasing the user's experience by different values,
So why not exracting those to their seperate actions also ?
My colleagues always tell me to say "dump and die" instead of "die and dump". I always wondered why I say that..just realised while watching this video that Jeffery Way spoiled me lol
Hi, what do you use for local development ? I need multiple version of PHP so I am using docker, but doing testing etc is bit harder.
Try using Laravel Herd
I'd do the same except i'd move everything within the action handle method back to the invokeable controller. That's technically why they exist.
Hi jeffrey whats your thought about repo and sercives pattern im using uaualy Actions + Services in my code and Thanks alot Mr best teacher in the world
That’s the way, I would just add database transaction.
Yep.
Laravel beyond CRUD
For some time now, I noticed you `whistle in the church` of so called best practices that choke the solving problem process. I'm in php since v4, I agree with you & I would like to see a more practical Laravel than 'smart'.
ty nice vid , how did you do that 15:16 - 15:20
On VS code, it's Command + Shift + L
On PhpStorm Ctrl+Alt+Shift+J ( www.jetbrains.com/guide/java/tips/find-next-word/ )
@@Laracastsofficial ty
I need that wallpaper
Why not to make one listener with all these private functions?
To simplify, dont over architect things / complicate things. Just make your code more intuitive/straightforward as much as possible.
👍👍
In defo a controller stuffer….
Nobody is doing that
You guys always over engineer things, you are thinking like when someone comes back to the code he will be a complete idiot. And the solution you propose is to over complicate things so when this idiot comes he has to check 10 more files to figure out what the code does. I feel the weird urge to be over organized doesn't make any sense. But thank you for the video
Did you watch the full video??
@@jeffreyway520 no i didn't. When he started to sayvwe have to create a new file for each one of them. I stoped the video.
Yes, but this is the exact issue Jeffrey is discussing in the video, he is doing that to show the issue in the first place, you need to watch full video :)
@@Laracastsofficial lol, I judged really fast. Apologies. He is saying exactly what I am saying