Skip to content

Commit f3e3d72

Browse files
authored
Add go cron example (#292)
* Add go cron job and turn cancel into shared handler * Add to readmes * Revert cancellation split and address feedback * Small style improvements
1 parent 14104ac commit f3e3d72

File tree

12 files changed

+264
-14
lines changed

12 files changed

+264
-14
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Or have a look at the general catalog below:
5858
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
5959
| <a id="durable-rpc">Durable RPC, Idempotency & Concurrency</a> | [<img src="https://skillicons.dev/icons?i=ts" width="24" height="24">](typescript/patterns-use-cases/README.md#durable-rpc-idempotency--concurrency) [<img src="https://skillicons.dev/icons?i=go" width="24" height="24">](go/patterns-use-cases/README.md#durable-rpc-idempotency--concurrency) [<img src="https://skillicons.dev/icons?i=python&theme=light" width="24" height="24">](python/patterns-use-cases/README.md#durable-rpc-idempotency--concurrency) [<img src="https://skillicons.dev/icons?i=java&theme=light" width="24" height="24">](java/patterns-use-cases/README.md#durable-rpc-idempotency--concurrency) |
6060
| <a id="message-queue">\(Delayed\) Message Queue</a> | [<img src="https://skillicons.dev/icons?i=ts" width="24" height="24">](typescript/patterns-use-cases/README.md#delayed-message-queue) [<img src="https://skillicons.dev/icons?i=go" width="24" height="24">](go/patterns-use-cases/README.md#delayed-message-queue) [<img src="https://skillicons.dev/icons?i=python&theme=light" width="24" height="24">](python/patterns-use-cases/README.md#delayed-message-queue) [<img src="https://skillicons.dev/icons?i=java&theme=light" width="24" height="24">](java/patterns-use-cases/README.md#delayed-message-queue) [<img src="https://skillicons.dev/icons?i=kotlin&theme=light" width="24" height="24">](kotlin/patterns-use-cases/README.md#delayed-message-queue) |
61-
| <a id="cron">Cron Jobs</a> | [<img src="https://skillicons.dev/icons?i=ts" width="24" height="24">](typescript/patterns-use-cases/README.md#cron-jobs) [<img src="https://skillicons.dev/icons?i=java&theme=light" width="24" height="24">](java/patterns-use-cases/README.md#cron-jobs) |
61+
| <a id="cron">Cron Jobs</a> | [<img src="https://skillicons.dev/icons?i=ts" width="24" height="24">](typescript/patterns-use-cases/README.md#cron-jobs) [<img src="https://skillicons.dev/icons?i=go" width="24" height="24">](go/patterns-use-cases/README.md#cron-jobs) [<img src="https://skillicons.dev/icons?i=java&theme=light" width="24" height="24">](java/patterns-use-cases/README.md#cron-jobs) |
6262
| <a id="webhook-callbacks">Webhook Callbacks</a> | [<img src="https://skillicons.dev/icons?i=ts" width="24" height="24">](typescript/patterns-use-cases/README.md#webhook-callbacks) [<img src="https://skillicons.dev/icons?i=go" width="24" height="24">](go/patterns-use-cases/README.md#webhook-callbacks) |
6363
| <a id="database-interaction">Database Interaction Patterns</a> | [<img src="https://skillicons.dev/icons?i=ts" width="24" height="24">](typescript/patterns-use-cases/README.md#database-interaction-patterns) |
6464
| <a id="sync-to-async">Convert Sync Tasks to Async</a> | [<img src="https://skillicons.dev/icons?i=ts" width="24" height="24">](typescript/patterns-use-cases/README.md#convert-sync-tasks-to-async) [<img src="https://skillicons.dev/icons?i=go" width="24" height="24">](go/patterns-use-cases/README.md#convert-sync-tasks-to-async) [<img src="https://skillicons.dev/icons?i=python&theme=light" width="24" height="24">](python/patterns-use-cases/README.md#convert-sync-tasks-to-async) [<img src="https://skillicons.dev/icons?i=java&theme=light" width="24" height="24">](java/patterns-use-cases/README.md#convert-sync-tasks-to-async) |

go/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- **[Stateful Actors and State Machines](patterns-use-cases/README.md#stateful-actors-and-state-machines)**: Stateful Actor representing a machine in our factory. Track state transitions with automatic state persistence. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](patterns-use-cases/src/statefulactors/machineoperator.go)
2323

2424
#### Scheduling
25+
- **[Cron Jobs](patterns-use-cases/README.md#cron-jobs)**: Implement a cron service that executes tasks based on a cron expression. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](patterns-use-cases/src/cron/cron.go)
2526
- **[Scheduling Tasks](patterns-use-cases/README.md#scheduling-tasks)**: Restate as scheduler: Schedule tasks for later and ensure the task is triggered and executed. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](patterns-use-cases/src/schedulingtasks/paymentreminders.go)
2627
- **[Parallelizing Work](patterns-use-cases/README.md#parallelizing-work)**: Execute a list of tasks in parallel and then gather their result. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](patterns-use-cases/src/parallelizework/fanoutworker.go)
2728

go/patterns-use-cases/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Common tasks and patterns implemented with Restate:
1313
- **[Stateful Actors and State Machines](README.md#stateful-actors-and-state-machines)**: Stateful Actor representing a machine in our factory. Track state transitions with automatic state persistence. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](src/statefulactors/machineoperator.go)
1414

1515
#### Scheduling
16+
- **[Cron Jobs](README.md#cron-jobs)**: Implement a cron service that executes tasks based on a cron expression. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](src/cron/cron.go)
1617
- **[Scheduling Tasks](README.md#scheduling-tasks)**: Restate as scheduler: Schedule tasks for later and ensure the task is triggered and executed. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](src/schedulingtasks/paymentreminders.go)
1718
- **[Parallelizing Work](README.md#parallelizing-work)**: Execute a list of tasks in parallel and then gather their result. [<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/play-button.svg" width="16" height="16">](src/parallelizework/fanoutworker.go)
1819

@@ -189,6 +190,82 @@ Have a look at the logs to see the cancellations of the flight and car booking i
189190
</details>
190191
</details>
191192

193+
194+
## Cron Jobs
195+
[<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/show-code.svg">](src/cron/cron.go)
196+
[<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/read-guide.svg">](https://docs.restate.dev/guides/cron)
197+
198+
Restate has no built-in functionality for cron jobs.
199+
But Restate's durable building blocks make it easy to implement a service that does this for us.
200+
And uses the guarantees Restate gives to make sure tasks get executed reliably.
201+
202+
We use the following Restate features to implement the cron service:
203+
- **Durable timers**: Restate allows the schedule tasks to run at a specific time in the future. Restate ensures execution.
204+
- **Task control**: Restate allows starting and cancelling tasks.
205+
- **K/V state**: We store the details of the cron jobs in Restate, so we can retrieve them later.
206+
207+
The cron service schedules tasks based on a cron expression, lets you cancel jobs and retrieve information about them.
208+
209+
For example, we create two cron jobs. One executes every minute, and the other one executes at midnight.
210+
We then see the following in the UI:
211+
<img src="img/cron_service_schedule.png" width="1200px" alt="Cron Service UI">
212+
213+
<img src="img/cron_state_ui.png" width="1200px" alt="Cron Job State UI">
214+
215+
Note that this implementation is fully resilient, but you might need to make some adjustments to make this fit your use case:
216+
- Take into account time zones.
217+
- Adjust how you want to handle tasks that fail until the next task gets scheduled. Right now, you would have concurrent executions of the same cron job (one retrying and the other starting up).
218+
- ...
219+
220+
<details>
221+
<summary><strong>Running the example</strong></summary>
222+
223+
1. [Start the Restate Server](https://docs.restate.dev/develop/local_dev) in a separate shell: `restate-server`
224+
2. Start the cron service and the task service:
225+
```shell
226+
go run ./src/cron
227+
```
228+
3. Register the services (with `--force` to override the endpoint during **development**): `restate -y deployments register --force localhost:9080`
229+
230+
Send a request to create a cron job that runs every minute:
231+
232+
```shell
233+
curl localhost:8080/CronJobInitiator/Create --json '{
234+
"cronExpression": "* * * * *",
235+
"service": "TaskService",
236+
"method": "executeTask",
237+
"payload": "Hello new minute!"
238+
}'
239+
```
240+
241+
Or create a cron job that runs at midnight:
242+
243+
```shell
244+
curl localhost:8080/CronJobInitiator/Create --json '{
245+
"cronExpression": "0 0 * * *",
246+
"service": "TaskService",
247+
"method": "executeTask",
248+
"payload": "Hello midnight!"
249+
}'
250+
```
251+
252+
You can also use the cron service to execute handlers on Virtual Objects by specifying the Virtual Object key in the request.
253+
254+
255+
You will get back a response with the job ID.
256+
257+
Using the job ID, you can then get information about the job:
258+
```shell
259+
curl localhost:8080/CronJob/<myJobId>/GetInfo
260+
```
261+
262+
Or cancel the job later:
263+
```shell
264+
curl localhost:8080/CronJob/<myJobId>/Cancel
265+
```
266+
267+
</details>
268+
192269
## Stateful Actors and State Machines
193270
[<img src="https://raw.githubusercontent.com/restatedev/img/refs/heads/main/show-code.svg">](src/statefulactors/machineoperator.go)
194271

go/patterns-use-cases/go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.22.5
55
require (
66
github.com/google/uuid v1.6.0
77
github.com/restatedev/sdk-go v0.17.0
8+
github.com/robfig/cron/v3 v3.0.1
89
)
910

1011
require (
@@ -14,7 +15,7 @@ require (
1415
github.com/go-logr/stdr v1.2.2 // indirect
1516
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
1617
github.com/invopop/jsonschema v0.13.0 // indirect
17-
github.com/mailru/easyjson v0.7.7 // indirect
18+
github.com/mailru/easyjson v0.9.0 // indirect
1819
github.com/mr-tron/base58 v1.2.0 // indirect
1920
github.com/tetratelabs/wazero v1.9.0 // indirect
2021
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
@@ -23,6 +24,6 @@ require (
2324
go.opentelemetry.io/otel/trace v1.28.0 // indirect
2425
golang.org/x/net v0.23.0 // indirect
2526
golang.org/x/text v0.14.0 // indirect
26-
google.golang.org/protobuf v1.36.5 // indirect
27+
google.golang.org/protobuf v1.36.6 // indirect
2728
gopkg.in/yaml.v3 v3.0.1 // indirect
2829
)

go/patterns-use-cases/go.sum

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1717
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1818
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
1919
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
20-
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
21-
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
22-
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
20+
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
21+
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
2322
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
2423
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
2524
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2625
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
2726
github.com/restatedev/sdk-go v0.17.0 h1:15LRmolKkXFim5tUBrWYLm8geKTdrhHrFdOZHpBZX68=
2827
github.com/restatedev/sdk-go v0.17.0/go.mod h1:TTonnUrH4zxFr7xNnkZxN+Sr3myq/FvZc3q+j195XiQ=
28+
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
29+
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
2930
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
3031
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
3132
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
@@ -42,8 +43,8 @@ golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
4243
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
4344
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
4445
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
45-
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
46-
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
46+
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
47+
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
4748
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4849
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4950
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
38.7 KB
Loading
50.4 KB
Loading

0 commit comments

Comments
 (0)