From b827956b124cba7e72d802e9f307947d7f9c8ce6 Mon Sep 17 00:00:00 2001 From: Giselle van Dongen Date: Tue, 7 Jan 2025 16:36:22 +0100 Subject: [PATCH] Add go callbacks example --- go/patterns-use-cases/README.md | 16 +++++++++++++ .../src/dataupload/client/client.go | 4 ++-- .../src/webhookcallbacks/callbackrouter.go | 19 +++++++++++++++ .../src/webhookcallbacks/stubs.go | 23 +++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 go/patterns-use-cases/src/webhookcallbacks/callbackrouter.go create mode 100644 go/patterns-use-cases/src/webhookcallbacks/stubs.go diff --git a/go/patterns-use-cases/README.md b/go/patterns-use-cases/README.md index d612b672..c062be0b 100644 --- a/go/patterns-use-cases/README.md +++ b/go/patterns-use-cases/README.md @@ -114,6 +114,22 @@ Have a look at the logs to see the cancellations of the flight and car booking i ``` +## Durable Webhook Event Processing + +This example processes webhook callbacks from a payment provider. +Restate handlers can be used as the target for webhook callbacks. +This turns handlers into durable event processors that ensure the event is processed exactly once. +You don't need to do anything special! + +## Scheduling Tasks +This example processes failed payment events from a payment provider. +The service reminds the customer for 3 days to update their payment details, and otherwise escalates to support. + +To schedule the reminders, the handler uses Restate's durable timers and delayed calls. +The handler calls itself three times in a row after a delay of one day, and then stops the loop and calls another handler. + +Restate tracks the timer across failures, and triggers execution. + ## Stateful Actors This example implements a State Machine with a Virtual Object. diff --git a/go/patterns-use-cases/src/dataupload/client/client.go b/go/patterns-use-cases/src/dataupload/client/client.go index 39915571..4171b96c 100644 --- a/go/patterns-use-cases/src/dataupload/client/client.go +++ b/go/patterns-use-cases/src/dataupload/client/client.go @@ -20,7 +20,7 @@ const RESTATE_URL = "http://localhost:8080" // workflow to send the upload url via email instead. func upload(id string, email string) error { - slog.Info(fmt.Sprintf("Start upload for %s", id)) + slog.Info("Start upload for " + id) client := &http.Client{Timeout: 5 * time.Second} @@ -50,7 +50,7 @@ func upload(id string, email string) error { } // ... process result directly ... - slog.Info(fmt.Sprintf("Fast upload: URL was %s", string(body))) + slog.Info("Fast upload: URL was " + string(body)) return nil } diff --git a/go/patterns-use-cases/src/webhookcallbacks/callbackrouter.go b/go/patterns-use-cases/src/webhookcallbacks/callbackrouter.go new file mode 100644 index 00000000..d43b6899 --- /dev/null +++ b/go/patterns-use-cases/src/webhookcallbacks/callbackrouter.go @@ -0,0 +1,19 @@ +package main + +import restate "github.com/restatedev/sdk-go" + +type WebhookCallbackRouter struct{} + +// Any handler can be a durable webhook processor that never loses events +// You don't need to do anything special for this. Just point your webhook to the handler endpoint. + +func (WebhookCallbackRouter) OnStripeEvent(ctx restate.Context, event StripeEvent) error { + if event.Type == "invoice.payment_failed" { + restate.ObjectSend(ctx, "PaymentTracker", "onPaymentFailed", event.Data.Object.ID). + Send(event) + } else if event.Type == "invoice.payment_succeeded" { + restate.ObjectSend(ctx, "PaymentTracker", "onPaymentSuccess", event.Data.Object.ID). + Send(event) + } + return nil +} diff --git a/go/patterns-use-cases/src/webhookcallbacks/stubs.go b/go/patterns-use-cases/src/webhookcallbacks/stubs.go new file mode 100644 index 00000000..4b86113e --- /dev/null +++ b/go/patterns-use-cases/src/webhookcallbacks/stubs.go @@ -0,0 +1,23 @@ +package main + +import restate "github.com/restatedev/sdk-go" + +type StripeEvent struct { + ID string `json:"id"` + Type string `json:"type"` + Created int64 `json:"created"` + Data struct { + Object struct { + ID string `json:"id"` + Customer string `json:"customer"` + } `json:"object"` + } `json:"data"` +} + +// Have a look at the scheduling tasks example (../schedulingtasks/paymentreminders.go) +// to see a full implementation of this + +type PaymentTracker struct{} + +func (PaymentTracker) OnPaymentFailed(ctx restate.Context, event StripeEvent) error { return nil } +func (PaymentTracker) OnPaymentSuccess(ctx restate.Context, event StripeEvent) error { return nil }