Интегрируем Temporal в Laravel с Пашей Бучневым

Интегрируем Temporal в Laravel с Пашей Бучневым

Show Video

We are live. Can I be the first one to say something? Sure, go ahead. Let's sit down for a bit and wait to see if the sound is good, the picture is good, the music is good. The music was awful, it was impossible to watch. Pasha just lagged a lot, I think it was noticeable on the stream.

Yeah, I think it was me, by the way. But it turns out it wasn't me. So not everyone accepted your call to go to a private lesson for a certain amount of money. By the way, after you mentioned private lessons, we had 20 people, now it's 10. Albert, hi, how are you? Is the picture and sound good? I think Pasha's ping is 20 seconds.

You won't be able to play Counter-Strike. Two. One.

Do it again. Two. One. Two. Is it fast? Like Road Runner.

One. Two. Hi.

Nice. Everything is great. We got it from the mobile Internet. That's exactly what we have. So, friends, hello everyone. Greetings to everyone on the CatCode channel.

Today we have two legendary guests in the CatCode studio. Pavel Buchnev and Alexey Gagarin from the company Fartime Inc. Today our stream will be dedicated to the announcement of Laravel Road Runner Bridge, which Pasha wrote, gave him a second life. In general, where do the legs grow from? There is such a thing in Laravel as Octane. But there are some drawbacks. For example, if we talk about Road Runner, only HTTP is used.

All other goodies from Road Runner are not used. And Taylor doesn't want to use them. He cuts off and closes all pull requests.

He doesn't want to talk. He doesn't want to hear or see anything about it at all. And for a long time, the guys from Fartime only hated this topic.

They said how bad everything is. But they didn't do anything. Until recently, Pasha sat down and made a bridge, which gives us the opportunity now to take and run Laravel together with additional plugins, gRPC, Temporal. Hey, stop talking about our words. I've already told you everything. There's no point in continuing.

So, let's say hello. Hello everyone. Yes, let's say hello.

You can say hello. So, Pasha, what do we have there? Our Octane is still in dependence, right? Because a lot of global stage needs to be abandoned. I would like to start a little earlier. Why do we need something, but not Octane? First, let's start with the fact that we used to have Laravel Bridge. It was in the organization of RoadrunnerPHP on GitHub for a very long time.

And we closed it at some point because Octane appeared. And, accordingly, Octane took part of the logic from there. Well, you can even look at it somehow. There are listeners who used to be with us.

Well, in general, they took the best and brought it to them. But how did they do it? They took... Well, there's Muli, there's Frankin, there's Roadrunner, and they all have HTTP plugins. Or rather, Frankin was originally designed for HTTP as a web server. And with Muli, we have some kind of application server, which can do both HTTP, gRPC, WebSockets, and all that.

And there's Roadrunner, which also has WebSockets, gRPC, TCP, HTTP, and so on. But Octane was made so that it can only do HTTP. Because they didn't want to bother. And maybe they didn't even try to delve into this topic, and just did what they managed to do. Or managed to understand, I don't know. And we, in turn, first supported Larvel Bridge and gave almost all the features that can only be given to Roadrunner, except Temporal, because at that time we hadn't done such a close integration yet.

And then Octane came out. We transferred everything to it and put it in this package and put it in the archive. It turned out that Octane did shit.

Well, not shit, but they made a very, very weak support for the features of real application servers, which are Roadrunner. And I think that... Alexey, are you shaking your hand? Yes. Yes. We can't say the word shit. We can't hate anything right now. Larvel is a great team.

They did at least something. Yes. Please continue. Yes. Come on, Danya, a little word for you. So far, you've said the same thing I've said, just a little longer.

Well, you've opened the topic. Now I can say the same thing. Let's not. Come on. What do you think? I think that perhaps it is necessary to reveal a little about what an application server is, so that the audience understands why it is not worth treating Roadrunner as an ordinary HTTP server. As everyone is used to, there is Nginx, there is Apache, there is some Franken, there is Spool.

Why do we need Roadrunner at all? By the way, I wonder if you can tell us now, since you've touched on the topic, that in general you can use both Franken and Roadrunner. Well, actually, yes. As we all know, Franken runs its own PHP process, and, accordingly, we can run all the HTTP traffic on it.

So, Roadrunner runs the process, well, PHP workers, who route or send requests to them. Or rather, they don't even send them, but the workers from Roadrunner read the requests, the same WebSockets, Temporal, Queues, HTTP, gRPC, well, in short, any of those workers that Roadrunner provides. And the point is that we can use part of the plugins from Franken, well, Franken has only one plugin, it's HTTP, and part, the same Temporal, on Roadrunner. We don't mind doing this at all, and Franken will use Octane, and Roadrunner will also use Octane. And here we can smoothly move on to what we're actually doing. Why am I making a chat on YouTube if we have our own private chat? Do you want to show the screen? Wait, I want to connect to the chat so that I can write something in the chat.

You can write right from the premiere. Yes, yes, yes, that's what I'm doing. Okay, let's continue. Yes, but I suggest you or I will move the screen, but it's better if you open your Bridge and look at it.

Okay, and here I want to say right away, don't forget to ask questions in the chat, because they are important to us today. That's it, you're moving my screen, can I talk now? Yes, you can ask questions in the process, especially if something is unclear and we haven't told you something somewhere, because we can omit something from our bell, some simple things, if it's not clear, don't hesitate to ask. So, Pasha, I turned off the screen.

Okay, I hope everyone can see my screen. So, where did I want to start? First of all, this package... Let's show the repo first. Well, let's try now how it's done.

So, this is DigitalOcean. I'll close everything here. I think someone has seen it, it was recently in the archive. Yes, we have a PHP organization rootrunner, where we collect all the plugins for the PHP implementation. And we always had Larvel Bridge in it. I reviewed it conceptually.

So, I'll close myself, Telegram. I reviewed it conceptually, because earlier the rootrunner was Larvel Bridge. Well, I added a rhythm here, here you can see how to use it.

We'll quickly go through this thing now, to make it clear what's going on here. What does it do? First of all, this is, let's say, an overhaul of Octane. If you look at its composer, you can see...

Now I'll open the composer. You can see that it mainly has plugins, well, integration packages for working with the rootrunner, and Octane is at the heart of it. I didn't reinvent the wheel, because Octane already has a lot of packages that add their own listeners to it, for cleaning the state, all sorts of livewires, and so on, so on, so on. Then there's no point in saying, we've released another package, let's make listeners for it now. So I took Octane, took its part, which deals with resetting the state of the container, their application, let's say, and just reused it.

Everything else they came up with, let's say, integration with Franken, integration with Asvuli, with the rootrunner, that's all for nothing. Just one feature that resets the state. By the way, it would be a good idea to write a separate state reset for Laravel, so as not to drag the whole Octane. In addition to this task, there are many others when it is needed. In general, I think, yes, that architecturally, when they made Octane, because they took it and began to integrate it for everyone, they got some mess there. In fact, it would be cool if there was a separate package that resets the state of the entire application, gives flexible features with all the listeners, that is, to divide it into several packages and put it all together.

Otherwise, it turns out such a mess. Imagine, we also had a framework before, which included the rootrunner, all packages were included in the spiral, and the same CycleUrm and CycleDatabase became part of the framework. And at some point we faced such a problem that a new version comes out, a new release of CycleUrm, for example, and, accordingly, in order for this release to work, you need to make a release of the framework. And it turns out that the more such a single package includes services, the same Franken, Svuli, Rootrunner, and it turns out that for each of them each of them introduces some changes and even minor and major octane releases can come out at once. Well, this, of course, is not a very good move, but we did it and we did it. So, why do we need a bridge? Why couldn't we take octane and reuse it? The first reason, I don't know, can I show it somehow? Well, okay, in my opinion, there is this thing in the bridge, I'll show it quickly now to make it clear right away.

So, what is the whole problem? It is that, first of all, they made such things, sewed them up, in my opinion, here, well, in general, the launch of workers is somewhere here, in their bin folder, and, accordingly, when Rootrunner starts, the console team starts, and this console team inside launches the configuration of Rootrunner, it launches this worker, and they sewed it all up so hard here that in order to run an additional worker in Rootrunner, well, for another plugin, let's say, with gRPC, you just need to sit here, reverse engineer all this, and right here somewhere in Laravel in the application, sew it up very hard. At one time, I did it with aggregator, it used to be double in Laravel for some time, and for some time I tried to use Octane. With Octane, I was very burned out with this thing, because I sewed up there, you can't imagine how much shit, so I gave up on this idea, and then we met TerramPamPam with his Laravel Bridge package. Yes, we didn't write the first version ourselves, but it was TerramPamPam, and he supported it, and it was easier for me to reach an agreement with him, we agreed to make such a config, I'll show you, a config in which we say, let's add this shit with you, in which we say that we are routing to such a worker, jobs to such a worker, and so on.

New plugins with new worker pools appear in Roadrunner, we, accordingly, say, here we add something, let's say some TCP, we will process such a worker. And how does it work? In general, when Roadrunner, if someone has never used it, did not configure it, then Roadrunner has such a thing as plugins. That is, we say that we have an HTTP connection in the config, where we say, here is such an address, all requests that will come, they are HTTP requests. There are jobs that raise their worker pool, Temporal raises its worker pool, there is also a GRPC plugin, which has its own things. And, accordingly, when we run our server, we specify a PHP script that it must run.

And when the server starts, it starts all these plugins, each plugin that requires workers, well, they have a pool, a setting, it starts raising a PHP process for each of them. How does it do it? It takes this thing, a command, by default, this is how it happens, it takes this command and exposes an nf-variable in it, which is called rrmod. And depending on the plugin, it is HTTP, well, accordingly, with the name of the plugin. That is, there will be such things transmitted. And when this nf-variable comes to us, we, accordingly, look in the workers section which nf-variable came, here it is listed here, and we, accordingly, add the required worker at that moment. And, accordingly, in this worker we are already launching communication with Roadrunner.

For those who do not know how the interaction of Roadrunner with our PHP application happens, Roadrunner is raised as a separate server. Now I will even try to draw quickly. Damn, we are already laying down for 20 minutes, I am already finishing my report. So, here it is, new drawing. Alexey, I'm sorry. How does Roadrunner work? Here we have Roadrunner and any, well, Franken works a little differently, of course, here is our application.

And Roadrunner launches, let's say, HTTP, here we have some kind of gRPC, here we have Temporal, and each plugin, for each plugin PHP processes are launched. As many processes as is indicated in the settings of the number of workers. Let's say here for HTTP we raise one PHP process, for jobs we raise two, for Temporal, let's say, we raise eight and so on. Accordingly, Temporal will raise here eight PHP workers, so eight, and Envy Temporal will come to them. Next, gRPC comes to our application and raises, let's say, two more workers here. Another HTTP comes, another worker is raised, and we get that Roadrunner has launched, it works on its own, but manages our PHP processes launched.

And our process, if we look at any worker, let's say, HTTP, what does the PHP process do? It launches such a loop and every tick in the loop goes to Roadrunner on pipes, there are different channels of interaction, by default it's pipes, it asks Roadrunner if there is any task for it. An HTTP request comes here from a client, there is a client, it sends an HTTP request to us, to Roadrunner. Accordingly, here Roadrunner accumulates these requests, which every second, well, millisecond, it doesn't matter, in short, every tick comes to Roadrunner and asks if there is a task for me. And we have a queue for Temporal, there is a queue, let's say, for HTTP, there is a queue for gRPC, and every plugin, when it comes to Roadrunner, asks if there is a task for Temporal.

HTTP comes, Roadrunner looks, there is a task, and it gives it to the worker, it answers, here are the tasks for processing. Accordingly, the worker receives a request here, it processes it here, and, well, here it processes it and just answers, where does it answer? In short, somewhere here, inside the worker, it answers the request, and inside the worker, what happens? We take our container, Laravel, and put it in the current application, well, clone it, put it in the current application, perform our request, make a response, and go in the opposite direction, in the current application, remove what was cloned, replace it with a real one, and, accordingly, what is a state reset within Laravel? This is to remove from the container everything that could have accumulated there in the process of various auto-wiring, someone could bind something in the container, something else, we cancel it all back. This is called a state reset. And there are some third-party packages that additionally require a state reset, and therefore there are some events here, which this handler at some point calls here, well, let's say, I don't even know, somewhere here they are, I don't know where they are, well, in short, they are called, and someone signs them. And Octane has, well, in short, this worker is the only thing I use, because it has a method handle, a regular request, it handles when we have an HTTP request, and there is also a second handle task, this is simpler, which simply performs the task that came here as a callback, that is, we pass some kind of closure here, it performs it, before that, once an event, after it an event, and, in fact, that's it, the application returns it. And these workers just use, the Roadrunner workers, they just use this handle task and handle method.

Thus, we can safely, this package turned out to be quite small, the only thing that can be done in the future is to distribute here, it turns out, there is a separate package that will only work with Temporal, because Temporal has different bundles that can simply be taken out. And so, for those who do not know, I remind you that for those who know, I remind you, there is Roadrunner, by the way, I have a picture somewhere, it is here, for those who do not understand what Roadrunner is, here is a picture, I always show it everywhere, that Roadrunner is an application server, it means that it is not a web server, He is a web server request handler, but he also knows how to process TCP requests, he knows how to be a WebSocket server. Moreover, he works in bidirectional mode, when we can give him tasks from our WebSocket application. And the client can also contact our WebSocket server, and this will also happen through a root-runner with Centrifuge, one of the best WebSocket servers.

He processes the queues, and he does, well, if someone wants, we will return to this topic, and I will tell you what is the difference between what Larvel does and any PHP frameworks, they work on the same principle. He has his own cache server, and these queues can be in-memory in the root-runner, and you don't need Redis or anything like that if you just need to test on the dev-server. He has metrics for the same Kubernetes to do healthchecks, he has a file server, he has a built-in logger that immediately brings logs to the logger in the pods, and you don't need a monologue or anything else. gRPC, well, a lot of things, and Temporal is not here, by the way. In short, he does a lot of tasks.

This picture is a little out of date, there are no logs yet. Yes, I'll finish streaming for now. Oh, there are so many, I'm even afraid to read.

Don't delete this picture at all, add it to the rhythm. Yes, oh. Everything was very beautiful there. Yes.

Alexey, you were pulling your hand there. No, no, I already said. By the way, Kirill Lobanov sent an interesting question here.

The question is, won't Rodrunner and Pykhy send it? No, they won't send it, because... Well, why won't Alexey send it? Maybe you know? I've already commented on it, I wrote that they are waiting for it. That is, Rodrunner and Pykhy work on the principle of one sent, the other sent in response. That is, you sent a request and are always waiting for an answer.

And until the answer comes, well, you're waiting. Maybe the answer will come that, okay, I got it, and then you throw the next request. That is, we don't hammer like Lark has these radius mechanisms and other jobs, when they just hammer, hammer. Like seagulls. No, it doesn't work that way.

These requests are not heavy, so they do not affect the system in any way. These are not requests. It's like... Well, it's not a request. There is nothing superfluous. No, there is nothing superfluous.

A client request comes from Rodrunner, and a response from Pykhy goes to the client. And there is no such thing. I remembered, we wanted to raise Temporal. Let's do it.

Who will raise Temporal? Danya, you or me? I can raise it. Integration. So I will also raise it. But it will be necessary to write Workflow Activity at the same time. In general, let's write everything from scratch.

Let's do it. God. Well, come on, you can do it on Moonshine.

I liked how you polished me a little today. Then I will stop my screen. What did he polish there? Will this deploy complicate through Laravel Forge? But in general, how will it complicate? You have all this at the level of packages. But if you say about Temporal...

What should be complicated? Can you explain? But in general, is this package compatible with Forge? Just yes or no? What's the difference with Forge? The composer installs the package. So it's compatible. To raise Roadrunner.

Forge already has these shell scripts, which we can run in the process. If I remember correctly. And there is nothing to download Roadrunner. Just Octane. By the way, no one canceled the Octane console command. You can also run it to download the same Roadrunner.

Just don't run Roadrunner itself with Octane. After all, Roadrunner should be launched. Well, you can also implement the console command, of course. If it is needed. But I probably wouldn't.

An important question about downloading Roadrunner. Tell me, what are you doing? So you tell me what to do. Look, I have it launched. Let's open the UI. Let's talk a little bit about Temporal. What is Temporal? This is a thing that rises as a separate server, which has its own database.

Well, if you want to run some kind of production ready. There is a local one that runs as a binary, and it works in one thread. Am I right, Alexey? I remember. In one thread? Or how? Well, a local binary for Temporal development. Do you mean a test Temporal server? Yes.

There are two test Temporal servers. One works so that... I've already lost track. Yes, one works in one thread. Well, not in one thread, but it doesn't matter. It's so light.

It's written in Java SDK, and you can test it with a time skip. So you write a workflow in which you have to wait for a year. It imitates the time after a year and tells your workflow that a year has passed, and you have to wait for the next event. And there is a full-fledged one, the same Temporal that runs in Docker, but packed in one binary with the UI, database, telemetry, and other stuff.

Does it work well? It works well, but it's not for production. Because it can say, at any moment, screw up the error log and some kind of crap happens. Yes, it's a full-fledged Temporal tested for local development so as not to drag everything that Danya has just rolled out in Docker. I'm rolling it out too.

You can do it in Docker too. By the way, let's do something else. Those who are watching us now can do the same thing right away, or later, to make it easier for you to reproduce. What do you need? You need to go to Temporal, right? I think they have it.

I usually download Temporal Docker Compose. I would recommend you to download DLOD, install DLOD right into the project and write DLOD GET TEMPORAL. And Temporal is downloaded, and you don't even need to install Docker. Come on.

Well, a super simple solution is to go here, copy Docker Compose. Why? They have it. Oh, well, okay. It's super simple. You can watch it right now and do it. Temporal will come up.

Here you will have Containers UI, Elasticsearch base, Postgres too. But this is not so necessary. Sorry, I'll interrupt you.

Elastic is not needed at all. We only run Temporal and Postgres Elastic is needed if you need to search by history. In short, if you need very advanced functionality, in most cases it is not needed. So I wouldn't bother.

There is another question. You will get it, for example, in that SDK, in that Compose, which you will clone by default. Although there are several of them, you can choose. Oh, there are five options.

Well, the most default and everything works. Here you will have UI on the port you specify. Next, go to this package, Laravel Bridge, RoadrunnerPHP.

First of all, put an asterisk, and then move below and install the bridge itself. Wait, is there a link to the PHP SDK Temporal below? Because you also need to go there and put an asterisk. By the way, no.

Damn, we need to add. Alexey, my oversight. Sorry.

By the way, it is on the official website. Probably. Below, next. These are samples. For those who are interested, there are samples in the doc, which can be… By the way, Kirill Nesmeyanov also wrote these samples, so you can put an asterisk for Kirill for this. Yes.

Put an asterisk here. By the way, the asterisk is smaller than Laravel Bridge. So, here we have a command. I didn't put it? This is just another account. And here it is. So.

After that, friends, put this command. That is, you don't need to put Octane. Octane will already be here, so just Bridge. And that's it. After that, publish the configs. You will have a config where you always have Laravel Roadrunner.

And in my opinion, it was like this by default, and I didn't touch anything here, right? Immediately move to your env and add a temporal address with a container, or if you run it natively, then specify the localhost accordingly. So, what's next? Next, if we go through this doc, what did I do here? You must make sure to do it. Yes, you definitely need to do it.

I did it, but I have it inside, everything is done in the docker. It will be necessary to do it. It's just that VendorBinaryRarget downloads the Roadrunner binary, but you need to rewrite it to the download today. That is, I don't have it, but you can do it locally. I will do it accordingly.

Here. No, Octane doesn't run Roadrunner.cli. No, I added Roadrunner.cli myself. So, we're on temporal today, right? Yes. SDK is already there, you don't need to install anything with Bridge.

And it turns out that everything is ready for interaction. There is Roadrunner.config by default. Remove GRPC from here, please. Why do we need GRPC? We don't need it. I played here, and in my opinion, except for GRPC, there is nothing new here. Can I quickly insert here that when we have plugins with workers, like HTTP Temporal, by default, if you don't specify the number of workers in the plugin section, we specify numWorkers. If you don't specify it,

it will take as many cores as there are workers. Therefore, if you are at the time of development, or in general, it is better to follow these parameters. Or, as Asanovar pointed out, Roadrunner has autoscaling of workers, you can also use it.

It will scale the workers from the load itself. But it should not work on Temporal, if anything. It is unlikely that it will work on the Temporal plugin. Yes, it is better to be careful with Temporal.

Okay, let's move on now. Now it's time for Temporal. Our app is up, Temporal is up, Bridge is up, what do we do next? What is Temporal? How do we create Workflow Activity? I will do something according to your dictation. Quickly, for those who hear the word Temporal for the first time, Kirill Lobanov asked that Temporal is something like a command. Yes, but it has a slightly different approach.

Here we have a workflow as a code. We describe it. We can describe our workflow in different languages as a code that will ... What will it do, Alexey? It will do a client call on the Temporal server.

Probably you can say so. Can you describe our workflow? I got distracted by an important question. It's not about the stream, I'm just kidding. Here in Rostov-on-Don there are very important questions about gender and whether you should use true or false to men or to women when you run a database. It's directly related to the app and not because we all run databases.

Okay, I'll put it another way. What do we not like the most when we work with queues? First of all, we can't understand why our queue is dropping, how many tasks have been processed, if something is dropping and we want to understand what the problem is. Let's say we have a chain of jobs that run each other somewhere inside and we have such a distributed network of launches. I think many of you have done it. Delayed jobs, a whole business process that needs to be described and it should be in the background somewhere there.

Accordingly, the biggest problem is to monitor all this and somehow understand what has already started, what has not started, whether it has started, whether it has worked, where in our chain of these calls a problem could occur. We start to put some logs there. We need to set up a retry policy if the job has fallen, and it is not the first in the chain. Let's say we have 10 jobs in a row to start and one should run the other. If something has fallen in the fifth job, what should we do? Should we run it all over again or should we retry the fifth job? And if it does not retry, we need to fix something in the code. What should we do? I think that many of you have asked and cried during the production, tried to log and debug all this.

I myself had a lot of this before. Temporal has appeared, which gives us, first of all, it gives us a UI in which it is an orchestrator of the launched jobs, so to speak, that is, the units that it controls and performs some actions are jobs, so to speak, and there is some workflow, Temporal has two units that it operates with. This workflow is our business process that we describe. It can have dozens of calls of different jobs, they can return an answer, we can pass some values, there can be timers and the workflow can perform up to a year. Let's imagine that a user is registered and he was sent a verification letter, and we want to give him half a year to press the button in the letter.

We give half a year, and the workflow is what will wait half a year until a person presses the button. If he hasn't pressed it in half a year, it should either delete it or put it in the archive. And the very action of pressing the button will be our job.

Sending a letter to a client will be a job that the workflow can run. And in this case, it will monitor all retries, it will show the state, what has already started, what data has been sent, what has returned. Let's start with this moment. Now Danya will create a workflow. We will have a very simple workflow.

I propose to make my favorite option. This is monthly withdrawing money from the client's account. Let's show it. I'll quickly comment on this more observability and other useful benefits of Temporal. We talked about it on the submarine.

I sent a link to the submarine to our report. Maybe it will look there and penetrate. How to call it? Windrow? Or how is it right? Subscription workflow. What is a workflow? A workflow is an ordinary PHP class.

It must have an attribute workflow interface. Let's put it. Workflow always has one entry point. Workflow is a method in which the starting arguments will come. If we make a subscription, then it's probably a subscribe. Some attribute hanging on the method? Yes, we always have one input method and this is a workflow method attribute.

Name? I don't think it's necessary. Yes, it is. This is the name with which it is registered in the workflow. And if not displayed, how is it formed? By the name of the class, if I remember correctly.

So far, so good. Yes, look. The workflow method should not specify the return type, because it will be a generator, and on the other hand, Alexey, can it be something else? Well, maybe. It can, of course, be without any actions, but what's the point then? Usually it's always a generator. Okay.

And then let's write... We don't specify this type, by the way. Yes, it's better not to specify and... Yes, and nothing but the generator should not be specified here. There can be no lines, no int, nothing. Well, if we specify the generator, then it's fine, right? We need to try.

I don't want to. This is the only place in the workflow where we try to at least specify these methods. Now Taylor is looking at us and says, I told you not to specify anything. That's the place. Let's write step by step what a subscription is. First, a client is registered to us, and he has an identifier.

Accordingly, we must send him a letter with a notification that... Or at the end... Okay, let's write that the subscription is registered. Write in Russian. Who are you writing to? To reduce the layout... Yes, to switch the layout.

Yes, yes, yes. Do you want to implement all of this right now? We will do it with blocks to show the principle. The second thing is to write the money from the account every month. Every 30 days. Every 30 days. The next action will be the material period.

The first 30 days or 15 days will be the material period after which we write the first money. Here it turns out, right? To save money. No, no, no. If it's a real period, notify that the real period is over. Okay.

Then this prompt will start gpt. Yes. And then after each writing of money, we will show that the money is written. And the last action that we will do is to cancel the subscription.

Even if you paid normally. Yes. And also a random writing of one ruble. We won't have a random writing. Okay.

Now let's think. Don't forget to like the video while you remember about it. Danya is very worried when there are very few likes. Especially if there are no subscriptions.

I'm already worried. Yes, and now let's explain a little bit on the fingers what a workflow is. Let's imagine that our workflow does not process all users at the same time. One workflow is launched on one client. That is, any business process, let's say, a subscription with notifications, with the registration of funds, it is distributed to one specific actor.

In this case, the user with whom we register the money. Therefore, in the subscribe method, we must transfer his identifier. Let's transfer the user ID to this method. By the way, the producer center in the chat tells us that we said something important about who the creator of Temporal is. Otherwise, the guys will think that someone unknown did it.

If not Taylor, then you can already leave the stream and slam the door. Alexey, will you tell us who the authors are? Who would know? Well, I don't know everyone by name. At least the companies that are associated with it. The guys are from Uber, Amazon, who have been doing scaling processes and other stuff for years.

They decided that it was possible. Temporal is a continuation of the internal project of the internal workflow manager. It's called Cadence in Uber. The guys from Uber went out and made Temporal.

That is, they made it more flexible, cool, and so on. That is, cool guys have been doing this for 20 years on a professional basis. And here we have what we have. Yes, and it is important to add that Temporal is not about PHP. Temporal supports work in 8 languages.

In short, it is a language agnostic. That is, you can write part of the workflow in one language, part of the workflow in another, the workflow in one language, run activities on Go, for example, it doesn't matter what language they are written in, it is important that they are registered in Temporal. By the way, this person is asking, this commando on Java is not a commando, but the principle is partially similar, that is, the goal is the same. We have already talked about it, you probably read it in another chat. Let's start writing our workflow.

Our monthly schedule is some kind of endless, endless task that must be repeated every 30 days. What do we do when we have an endless task? We write a while true cycle. And inside it, the first thing we do, imagine that we have a trial period, we can now write a while true, create a variable, declare a new one, or even before the cycle, let's move it before the cycle, please.

And inside we write, our schedule should take place first after 15 days. We put the conditions here, if the trial is a real period, then, accordingly, here we write a workflow, double quotes, timer, timer, I already forgot, timer, and here we put a carbon interval, here you can use a carbon interval in order to flexibly specify, and we wait 15 days. And as we said, this method is a generator, so all the actions that we describe, all sorts of timers, launches, activities, other workflows, and so on, they must be generators, that is, we always put yield. Or if we don't put yield, then all these methods from the workflow class return a promise, and someone has to, what do they do with promises, Alexey? Resolve. Someone has to resolve these promises.

At some point, this yield should appear. That is, theoretically, we can continue with the timer, say zen, when the timer has worked, do something there. This can be done, we will not do it now, let's forget about this thing. So, 15 days have passed, we set the trial to be false.

We send a notification that our trial period is over. The notification will already be an activity, an activity is our job, that is, let's create an activity that will be some kind of email, subscription activity, I don't know. The fan has a great question, why does Spiral without Roadrunner display the base template for so long, unlike Laravel? It turns out that Spiral is not intended to work in Neo-GRPC. Spiral is displayed quite quickly, but as you noticed, it is sharpened for long-running, and, accordingly, we allow ourselves long bootstrapping, because bootstrapping is only once in the application, and Spiral positions itself as a framework for long-running. There are very few cases, and we don't even consider them, but they work in a completely different mode.

Pasha commented there, most likely the debug mode is turned on, because it is turned on by default. Not the debug mode, but the tokenizer is turned on, you mean. Not only tokenizer, everything is not used by default if the debug mode is turned on, everything will fly faster than normal. By the way, everyone should have an activity attribute, an activity interface. In general, it seems to me, in the framework of Laravel, this is a pointless activity, or not, okay, let it be, and then we should add… What do you mean? Why is it pointless in the framework of Laravel? I just don't remember, they are used inside SDK, right, Alexey? Yes. And we also have methods running in Activity, so let's add the method sendEmail, or trial, what is there, expired, I don't know, or finished.

Or we can just write email sent. Okay. Alexey, are you here? Yes, yes, yes, I'm here. Here we can use any typing, here, maybe, void method, result, and inside, let's just make some dump, or write vlog, I don't know how to write vlog in Laravel, let's write vlogger here, that the material period is over.

Great, awesome. And over all Activity methods we have to put ActivityMethod attribute. I'll be boring here again, basically no. Okay, ActivityMethod? For Activity, you announced Activity class, all public methods will automatically become ActivityMethods.

But it's better to specify, so it's clearer, in general. That's all. That's a good comment. Yes, in Activity we can always use auto-wiring in the constructor, dependency injection, and so on, so you can insert any of your repositories here, if you have them, and so on. Well, let's go back to our subscription workflow, and here we have to call the NewActivityStub workflow. Yes, look, here I'm inserting this class, and I'm calling the method finishTrial, or whatever we called it.

Right here, yes, write it. Oh, and don't forget... Did you cut off the side? No, it works without it for now. Okay, don't forget to put yields, everything that should be done now, we put yields. Pasha, I would say that we need to wait when we write our code, we want to wait for this action in this line. In fact, we are writing await here for those who came from JS, and promises for you, as we are used to with warnings.

Yes, and to finish our trial period, we have to go to the next cycle, because we don't need to write the money here. By the way, will there be a problem here that we indicated void? No. I can explain a little how it works. We call a timer inside the workflow that should wait for something, or some activity.

In fact, we don't call a specific activity, and we don't do any slips here. All these workflow double-point methods do is, let's say, client calls to the Temporal server, which at this moment will see if there is such a timer, with such an index. If not, it will run it. If there is, it will just go further.

The same with the activity. It will go to Temporal, see if we launched email subscription activity, method trial finished. If not, it will tell Temporal to run such a method.

Temporal in the list of registered workers, as it can be called. In general, every application that connects to Temporal and it can perform workflow or activity, it registers all the activity and workflow that it can perform. And accordingly, when someone says that he will perform email subscription activity, Temporal looks who announced when connecting to Temporal that he can perform them, this launch will pass and will pass all the arguments that we pass into this method. If we say start the timer for 15 days, Temporal will receive a request that this workflow should wait 15 days. After 15 days, it will continue to go below the stack. Am I right, Alexey? So some stack is remembered and it constantly runs and looks where it stopped, whether to go further.

Not a single worker who will live there for 15 days. No, this workflow can be restarted dozens of times, hundreds of times and it will always go from the beginning of the call of this method, that is, it will go again, make a trial through, go to the timer, if we launched this timer, it will just return the state of this timer. The same with Activity, it will see if we have already launched it, in the database there is a record that it was launched in the history, then it will just get all this information from the history and it will go from top to bottom with each restart. So this is already a solution to the problem that you mentioned in the Laravel queue, when somewhere on the invested jobs we can't restart later because it is not clear where all this has fallen and we have to come up with something. Here Kirill asks if there will be a cross-name Activity if there are several timers. No, there won't be.

First of all, Temporal has such a concept as task queue, it's like a queue, you can register different workflows in different queues. Danya can demonstrate, there is an attribute Assign Worker that can be hung on the workflow or Activity where we can declare in which task queue to run it. Temporal has a task queue, a queue in which tasks and workflows will run. Accordingly, we can register the same names in different task queues, in my opinion, and so on. Well, in general, there will be no problems. Okay, let's move on.

In order to finish the material period, we need to put a continue. We switch to a new iteration. So, what are we doing here? We put a continue. Well, look, if we now go up and down this cycle, we see that while we have a variable trial true, we enter the conditions, run the timer, wait 15 days, put a false and, accordingly, send a notification. We switch to a new cycle.

And now we are waiting for 30 days. Or wait. Well, yes, we are waiting for 30 days. Let's wait for 30 days. So, this is after the trial? Yes, yes.

We switched to a new cycle after 15 days, and now our task is to wait for the next 30 days so that we can wait for a month, I don't know, it doesn't matter. And then we write the money. We run a new activity to write the money. And while Danya is doing it, I'll tell you what Activity is.

Everything we register in Temporal, we don't register the classes themselves. We register the name of the activity, the name of the workflow. They form certain keys. If PHP does it itself, it usually takes the name of the class, the name of the activity, the name of the class, the point of the name of the method, and, accordingly, when we register this activity, it will register it as withdrawActivity. Write the money.

Withdraw, for example. And it can be in any other service. That is, it can be in another city and this service is running. And it just cuts into this Temporal, registers what it can do.

And in Workflow, we can call this activity not by the name of the class, but by the name of the activity that Temporal sees. Because when we call by the name of the class here, then, in fact, the SDK on PHP will build this withdrawActivity.withdraw method under the box.

Let's write here $100, for example, int amount, how much to write from the client. By the way, this is a good idea in the chat. Let's try to run it at this step. Let's write it now and run it. Okay.

I like another idea from the chat. Logging. Send the data of the card and the PIN code to this chat. By the way, yes, we need to test on real data. PIN codes, cards, everything we've accumulated, guys, send it. We'll make a draw.

We'll try the first card. You can just send the PIN codes and card numbers. Let's create our Activity. The last thing we'll discuss on it now.

When we create ActivityStub, we can always pass options with the second argument. We'll take a quick look at the options and run it. And this will be ActivityOptions.

It's called class. Yes, it has a new method. And when we pass ActivityOptions, we specify all the settings such as retry policy.

We can control, we can set how many times to run, what coefficient to set, how much Activity can be executed in time, how long, how long can we wait in the queue, if it hasn't been processed in time. In short, here you can play around, look at the documentation, everything is there. Let's leave it empty for now.

And we start running this thing. So, we've written the day. It turns out that we've already done almost the entire workflow. Look, guys, if you try to do this yourself, you'll need a cron that will have to run it, I don't know, if it's 15 days, once a day, how many times a day it has to run. Come on, Danya, to run all this, where are we going to run it? In the console team? I usually run it in the console team. Let's do it in Moonshine, so that I have it.

Come on, you did it cool then. This thing. You were happy. What's with the lock-in? So, no, of course, they ask, are you sure you won't cheat? This swindler is now a streamer.

Send. Send. No cheating. So, what's with the lock-in? This is probably within Temporal.

I don't know what's with the lock-in, but it will run in Activity. Workflow is such a thing that should not make a call to the database, it should only orchestrate Activity calls with timers or something like that. No more. What are you doing here, Danya? You tell me, I want to throw a client. Workflow client, or what is it called? Yes, client interface.

I forgot the interface. Look, to run Workflow, you need Workflow client interface. It is already binged into the container, so you can safely just take it to the DI where you like it. And in it we create our client stub, new workflow stub, new activity stub. We conventionally declare a certain client object, create a proxy.

Here we have a subscription workflow. In it, too, you can specify the second argument of the option. You can specify retry policy, bucket coefficients, give your own identifiers for Workflow if we want to contact them later. Because if our workflow works, someone asked a question how to cancel Workflow.

To cancel it, you need to know the Workflow identifier and just refer to it by identifier. We'll show you how to do it now. It's quite simple.

We created an object, it needs a WF, but a new workflow stub. Let's change it. What's there, start? Look, there are two ways to run it. The first one is when we take the proxy itself and call the method subscribe. In this case, it will wait for an answer. It can hang here for 30 days.

The second option is we take this workflow client and call the start method and pass this workflow to it. Let's do it this way, 30 days is a long time. All right. Wait, wait. The start and the second argument need to be passed to the user identifier. Let's say, 1.

We'll run it later in the cycle. We'll show you how it works in the cycle. I think that's all.

Vlad asks what's with the container's state. We showed at the beginning that it's being thrown by Octane. Not us. With the other connections, the activity is killed after each execution, so it shouldn't be there forever. I think it's still...

Well, the activity... When the activity is killed, the connection is also killed. I think it was solved at the Octane level. The problem with reconnecting to BD. Let's try it. No, come on.

It's interesting. Danya made a button. Go to Temporal. Oh, here it is. Can you enlarge it a bit? We'll see what's going on right now. Go to the workflow, we can see that the workflow for the client with the identifier 1 has started.

Go to it and let's see. Here is its type, Subscription Workflow. Yes. We go in and see that we don't have any workers here who can work on the Subscription Workflow. That's because we didn't register them in Roadrunner.php. Let's go and register them.

Temporal is not processing anything right now. Here in Roadrunner.php there is a section in Temporal, Declarations, and here we hand-write the workflows and activities that this application will process. Here we should probably do some kind of auto-discovery. It's not convenient. We should do it, I haven't bothered with it yet.

It's already a whistle-blower, let's put it that way. So, how do we do it? Activity and workflow, right? What is it called? Ah, all of them. So, Kirill asked, by the way, how to connect to BD when using the workflow.

What's the difference there? We have a global Finalizer Interface, and when we connect the Cycle to the Spiral, the URM has, I forgot if it's an interceptor or a bootloader, how does it work? There are two different ways to do it. First, the Cycle can reconnect itself, so when sending a request, if the connection is lost, it will reconnect. And the second thing is that usually, if there's nothing blocking the connection, then an additional bootloader is created, where the resetter is registered after each request, so the connection is disconnected. So, Danya has already launched the workflow. Danya, please make the whole screen. And there's a little sun on the bottom left.

Look, we've launched two workflows now, and Danya, the workflow that didn't launch, as soon as we did everything right, it started working, and now they're both waiting, as we can see, for 15 days. The waiting process has started. Let's change it to 15 seconds or something. Here you can already see how interesting and cool it looks, that you can track everything in real-time. Yes, and you can scroll down below, and there's a history of our call. Look, we launched, well, this is classic event sourcing, where we have all the states, how we launched the workflow, when we launched it, and it went up, the timer started for 15 days.

Now let's change all this to seconds, so that we don't have to wait 15 days. And we also do 30 seconds of writing money. Well, that's it, then we need to restart. Don't forget that in order for all this to work, you need to reboot, as we remember.

Why are you rebooting everything? You only need to reboot PHP. Wait a second. Okay, let's run the workflow. Let it hang.

Can I cancel it? Not necessarily, why? Let it hang. By the way, you can cancel or terminate them from here. I say right away that there are two options. The first one is cancel. It will send an exception to our workflow. ActivityCanceledException or something like that.

I forgot what it's called. So you can process it inside, right? Yes. And there's terminate, which kills it without any notifications. It's like sickKill or something like that. It just kills it and that's it.

There's also Reset. Reset is to restart, right? To reset to a previous state. It's better not to do this. These are rare cases. Here we indicate how far we can reset.

But we usually don't suffer from this. We'll talk about the signals a little later. Let's run the new workflow. We're waiting for 15 seconds now. We have a little time left.

Yes, we can. We have a drop here because of the attribute. We didn't add anything there. Did you add the activity lifetime? Because it's a necessary thing. Then let's... Yes, we need to add the activity options.

We can add an empty one, right? No, not an empty one. We need to specify the time, the time limit for this activity. You can specify the year, of course.

What do we have here? Any of the timeouts. Write timeout and there will be 3 timeouts. Which one? Start to close, come on.

And here specify 5 seconds. Carbon interval 10 seconds. You can just write a number and it will be equal to a second. Yes, 10 seconds. And wait, in the second email subscription activity, too. No, workflow.

You see, there is no such option. So, that's it. Let's reboot and go. I rebooted everything with your permission. The server too, or rather the computer. The computer can be rebooted.

I don't know, this activity probably won't run. By the way, check it. It won't run. Oh, it has this, right? By the way, try to reset here. Let's see if the reset works here.

I don't think it should work. So, workflow task completed. Come on, roll back to 4. And reboot the page. Let's see what happens. I've never done this before.

Okay, forget it then. Oh, it started. Now it wrote that the tutorial period is over. Activity started, as we can see. And now it's been waiting for 30 days.

Well, we didn't pass anything here. Now it's been waiting for 30 days to write off the money. Let's wait 30 days while we go to the logger. Is there? Yes, let's see. Where did I write it? Yes, this storage logs, or how is it there? I could write it in std. I could write it in std.

What the hell is this? That's it with logs. Parser logs in Laravel works. Alexey, tell me about the tests. How to test it all? Come on, delete it.

It's already there. I'm stuck. About the tests.

PHP SDK has a test engine in the initial state, so to speak, when we can lock the workflow, or something like that, lock the activity. Well, we run this temporal test server, which is small, which can skip time. And with the skipping of time, it's all tested well. Okay, let's go back to the workflow. Let's look at it. Look, we have it.

The trial is over. Then we waited 30 days, wrote the money. And now go reload our docker. You can do both temporal and docker at once. Let's do both temporal and this one. Here we have a problem.

Everything fell everywhere. Connections are all gone. Well, let's start again. That is, at this moment all the activity workers have fallen. Well, that's it. I broke it.

Why did you do it? Did I ask you to turn it off? No, now it will start. Oh, that's it. It stopped because temporal was down. And now PHP has definitely started.

By the way, I think you didn't hurt it. Well, let's see. Our workflow continues to work. And we see that it continues to write. Yes, the timer is a little bit proffocated because everything fell. But the timers went as if nothing had happened.

And now go to activity and set exception. By the way, there are tests in samples, either a folder or something. There are examples of testing on some Simple Workflow. Come and see. Are you waiting for him to tell you the options? I'm sitting too. I can sit for half an hour.

Three days. I basically don't write. My internal temporal started for three days. You just need to reboot PHP now.

Well, let's drop our activity now. But we really don't have retry policy. Here we see that our activity has fallen.

Retry attempts have gone. And we can click on it now, on this sausage of ours. And we see that our message exception has fallen and shows where it fell. I say right away that exceptions come here in the form of a line because temporal works with all languages.

And he can't disassemble an exception from Go. Go and change this exception back. Remove it. Wait a second, Pasha.

Can you stop for a second and rewind stack trace below? Here. Let's do it. I'm just wondering if temporal got here from DK. I didn't test this feature. I added a feature so that stack trace is not as long as before.

By the way, it's good that you removed it. I add all features to temporal PHP SDK without tests. I add and users test. Yes, Kirill, you wrote that temporal has fallen and we cancel the subscription.

You can't cancel the subscription because your servers don't work. You understand that until your signal is delivered, it's better not to cancel the subscription. Let's go back to our workflow. We need to do the last thing. Kirill reminded us about the subscription. Let's cancel it.

Write a new method, call it cancel. Let's make two methods. The first one will call...

Okay, let's just cancel. We don't need to do anything else. Temporal has a thing called Query and Signals.

Query is when we can get the state of a property from within temporal. And Signals is when we can send something to the workflow in our identifier. Here we have a cancel.

Let's hang the attribute signal method on it. Kirill says that the client will make a mistake that the server couldn't complete the request. The 500th mistake.

He won't get a cancel. It's convenient. It's very convenient to do it with unsubscribe method.

Yes. Look, you have a cancel and you write this.cancel="true". Here? Yes. Maybe not cancel, but stop? Or something like that? I would write cancel to make it clear. Okay. this.cancel="true".

And now I declare in the class property canceled boolean false. And now we'll get to know Eridonli. Please remove it from the class, otherwise it won't let us do it.

Temporal has this thing go to the 30-day timer and Temporal has one more type of timers await with timer. Let's change this timer to await with timer. Ah, with timeout. The first one is timeout.

We specify how long it should wait. And the second one is callbacks. If they return true, it leaves the await. And the second callback is this.cancel.

fn and this.cancel. So while this.cancel="false", canceled property.

While this.cancel="false", the await works. As soon as this.cancel="true", we leave this await. And now I assign a variable here.

I assign a variable to this await. IsTimeout, I think. If I remember correctly, when...

You can check this.cancel="false"> this.cancel="false"> add this if.

No, wait, how do you... You need a timer to get out. How do you check if... No, go back, Da

2025-05-14 09:54

Show Video

Other news

What will it take to create the quantum internet of the future? 2025-05-14 22:35
What the Internet Was Like in 1995 2025-05-15 00:42
Browser Fingerprinting Masterclass: How It Works & How To Protect Yourself 2025-05-11 01:10