I love it Nuno, but the transaction block seems to have a lot of operations which means it will lock rows and tables for a long time potentially (Deadlock error)
Well actions look very good, but what is the main difference between an action and a job? I can also run a job anywhere in the codebase (synchronously or asynchronously).
That's an interesting way to look at a job. IMHO, Jobs are intended for working with the queue and to be run asynchronously. Yes they can be run synchronously but I always thought of that as an exception to the normal use case for a Job. Most of the time, my Jobs actually call 1 or more Actions. This separates the responsibility of performing the business logic from the details of working with the queue which is what the Job is for. All that being said, you do what makes sense to you. If running Jobs synchronously is what makes the most sense to you, go for it.
When would you say it's better to go with an Action like that than with a service of sorts? Like a TodoService class that has a create() method... What you use as a guide to choose between them?
Is there any problem to have nested 'DB:transaction', a main action calling a sub-action? Never tried it, tbh 🤔 I prefer to put those on the code that calls the main action, meaning, controllers/commands/jobs and so on...😅
Good question yes, it has been a while perhaps more than a year a ago but I can recall having issues with nested transactions cant quite remember the exact error tho. Which I then fixed by creating an action that runs another actions within a transaction, or like you said doing the transaction in a controller/console command.
One issue I faced is that if the nested transaction fails, it would caused the outer transaction to fail as well, which I think is okay but if the inner/child transaction succeeds but the outer/parent fails, actions done in the child will be committed and those done in the parent rollback. So if all the actions were somehow related, you could end up with an inconsistent database.
I'd love more on this topic! I have many questions! XD Example: I've read that using events\listeners is not that great because your code get's "hidden" and it's somewhat of a mid level dev trap (along side with model events). Why do you have "strict" names for the actions (create, update or delete)? why not something like a SendOrderCreatedEmail ? (you probably use an event for this, right? looking back on what you covered in the video)
As always it depends. I have worked on some non-trivial (non-crud) apps where events/listeners are a great way to decouple code. It makes it easier to extend your code by just adding more (async) listeners.
extend this abstract class to allow dependency injection for actions abstract class Action { public static function make(): static { return app(static::class); } public static function run(...$arguments): mixed { if (! method_exists(static::class, 'handle')) { throw new RuntimeException('Unimplemented function [handle] in class ['.static::class.']'); } return static::make()->handle(...$arguments); } }
I am still confused about the difference between the Actions and Service patterns. Do Actions replace the Service pattern, or can they work side by side?
I shared this same confusion when I first saw Actions. What I settled on is that Actions are meant to be stand alone classes that do one discrete thing. Services are classes with many different operations that are grouped around a certain concept. Any Service can be broken into many Actions where each Action replaces 1 method in your Service. Most of the time, I use Actions to perform my business logic and a Service if I need a class for interacting with a 3rd party API or package. Services are great for that since they will share certain logic like connecting with the API and can be extracted so you can switch out the implementation if you need to later on. I will, however, occasionally use a Service for business logic if it makes sense to group the methods together in some way.
@@DerekCaswell272 this isn't exactly doing one thing... if you create todo, why should you also broadcast it under "create todo"? That's the beauty of patterns - you don't have to follow them to a tee, they are purely recommendations on how to solve creational, grouping or communication problems.
@@ward7576 When I said they do 1 discreet thing, I didn't mean to imply that the body of the Action can only do 1 thing. Just like any other method, the handle method can do whatever needs to be done when accomplishing its task. The difference is that an Action's purpose is just to perform that 1 task as opposed to a Service where it has many methods that can be called individually to perform different tasks.
Thanks for sharing Nuno! I think actions could be the answer to "where to put the logic" in many cases. I've been throwing general logic mostly into custom service classes that can perform certain "actions", but this is a cool concept. Was this inspired by Jetstream? (the only place I've seen it before)
Man, your energy is awesome.
And... thanks for the tips :)
Awesome, You should make series on laracasts !
Agree
Thanks! Finding your tips very useful 🚀
Bro i’m a frontend dev transitioning to laravel.
And your videos make me want to learn more and more !
Just knew that you have TH-cam channel, Finally I can see the "Mr. Laravel Package" I saw everyday.. 😅😅😅
I love it Nuno, but the transaction block seems to have a lot of operations which means it will lock rows and tables for a long time potentially (Deadlock error)
Well actions look very good, but what is the main difference between an action and a job? I can also run a job anywhere in the codebase (synchronously or asynchronously).
That's an interesting way to look at a job. IMHO, Jobs are intended for working with the queue and to be run asynchronously. Yes they can be run synchronously but I always thought of that as an exception to the normal use case for a Job. Most of the time, my Jobs actually call 1 or more Actions. This separates the responsibility of performing the business logic from the details of working with the queue which is what the Job is for. All that being said, you do what makes sense to you. If running Jobs synchronously is what makes the most sense to you, go for it.
thanks Nuno, can you share the list of the rules in pint.json that you use?
When would you say it's better to go with an Action like that than with a service of sorts? Like a TodoService class that has a create() method... What you use as a guide to choose between them?
Love it! Any particular reason you don’t make it static?
because you may inject other actions / services, on the __construct.
which theme are you using? your phpstorm is very nice
Is there any problem to have nested 'DB:transaction', a main action calling a sub-action? Never tried it, tbh 🤔
I prefer to put those on the code that calls the main action, meaning, controllers/commands/jobs and so on...😅
Good question yes, it has been a while perhaps more than a year a ago but I can recall having issues with nested transactions cant quite remember the exact error tho. Which I then fixed by creating an action that runs another actions within a transaction, or like you said doing the transaction in a controller/console command.
One issue I faced is that if the nested transaction fails, it would caused the outer transaction to fail as well, which I think is okay but if the inner/child transaction succeeds but the outer/parent fails, actions done in the child will be committed and those done in the parent rollback.
So if all the actions were somehow related, you could end up with an inconsistent database.
I'd love more on this topic! I have many questions! XD
Example:
I've read that using events\listeners is not that great because your code get's "hidden" and it's somewhat of a mid level dev trap (along side with model events).
Why do you have "strict" names for the actions (create, update or delete)? why not something like a SendOrderCreatedEmail ? (you probably use an event for this, right? looking back on what you covered in the video)
As always it depends. I have worked on some non-trivial (non-crud) apps where events/listeners are a great way to decouple code. It makes it easier to extend your code by just adding more (async) listeners.
extend this abstract class to allow dependency injection for actions
abstract class Action
{
public static function make(): static
{
return app(static::class);
}
public static function run(...$arguments): mixed
{
if (! method_exists(static::class, 'handle')) {
throw new RuntimeException('Unimplemented function [handle] in class ['.static::class.']');
}
return static::make()->handle(...$arguments);
}
}
I am still confused about the difference between the Actions and Service patterns. Do Actions replace the Service pattern, or can they work side by side?
I shared this same confusion when I first saw Actions. What I settled on is that Actions are meant to be stand alone classes that do one discrete thing. Services are classes with many different operations that are grouped around a certain concept. Any Service can be broken into many Actions where each Action replaces 1 method in your Service. Most of the time, I use Actions to perform my business logic and a Service if I need a class for interacting with a 3rd party API or package. Services are great for that since they will share certain logic like connecting with the API and can be extracted so you can switch out the implementation if you need to later on. I will, however, occasionally use a Service for business logic if it makes sense to group the methods together in some way.
@@DerekCaswell272 this isn't exactly doing one thing... if you create todo, why should you also broadcast it under "create todo"? That's the beauty of patterns - you don't have to follow them to a tee, they are purely recommendations on how to solve creational, grouping or communication problems.
@@ward7576 When I said they do 1 discreet thing, I didn't mean to imply that the body of the Action can only do 1 thing. Just like any other method, the handle method can do whatever needs to be done when accomplishing its task. The difference is that an Action's purpose is just to perform that 1 task as opposed to a Service where it has many methods that can be called individually to perform different tasks.
Thanks for sharing Nuno!
I think actions could be the answer to "where to put the logic" in many cases.
I've been throwing general logic mostly into custom service classes that can perform certain "actions", but this is a cool concept.
Was this inspired by Jetstream? (the only place I've seen it before)
ok, but what is the fail strategy, exceptions or ?
Great 👍, i would like to know more about broadcasting as well
Great content 🚀
massive
Hate those "pretty" comments.