-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
119 lines (103 loc) · 3.25 KB
/
Copy pathmain.go
File metadata and controls
119 lines (103 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Package main runs a fan-out / fan-in workflow: dispatch a message to N
// parallel channels, await every delivery, aggregate the result.
package main
import (
"context"
"flag"
"fmt"
"log"
"strings"
resonate "github.com/resonatehq/resonate-sdk-go"
)
type FanoutArgs struct {
Channels []string `json:"channels"`
Message string `json:"message"`
}
type FanoutResult struct {
Delivered []Delivery `json:"delivered"`
}
type SendArgs struct {
Channel string `json:"channel"`
Message string `json:"message"`
}
type Delivery struct {
Channel string `json:"channel"`
OK bool `json:"ok"`
Reason string `json:"reason,omitempty"`
}
// fanout dispatches one ctx.RPC call per channel without awaiting in between,
// then awaits all of them. The dispatches happen in series in user code but
// run concurrently on the server side; the Awaits block until every child
// promise settles.
func fanout(ctx *resonate.Context, args FanoutArgs) (FanoutResult, error) {
futures := make([]*resonate.Future, 0, len(args.Channels))
for _, ch := range args.Channels {
f, err := ctx.RPC("send", SendArgs{Channel: ch, Message: args.Message})
if err != nil {
return FanoutResult{}, err
}
futures = append(futures, f)
}
out := FanoutResult{Delivered: make([]Delivery, 0, len(futures))}
for i, f := range futures {
var d Delivery
if err := f.Await(&d); err != nil {
out.Delivered = append(out.Delivered, Delivery{
Channel: args.Channels[i],
OK: false,
Reason: err.Error(),
})
continue
}
out.Delivered = append(out.Delivered, d)
}
return out, nil
}
// send is the child function that "delivers" a message to one channel. In a
// real system this would call a provider API (Twilio, SES, Slack, ...). Here
// it just prints and returns a Delivery record.
func send(_ *resonate.Context, args SendArgs) (Delivery, error) {
fmt.Printf(" [send] %-8s <- %q\n", args.Channel, args.Message)
return Delivery{Channel: args.Channel, OK: true}, nil
}
func main() {
channels := flag.String("channels", "email,sms,slack,push", "comma-separated channel names")
message := flag.String("message", "Hello from Resonate", "message to deliver")
id := flag.String("id", "fanout-1", "promise ID; rerun with the same ID to get the cached result (idempotency demo)")
flag.Parse()
chs := strings.Split(*channels, ",")
for i := range chs {
chs[i] = strings.TrimSpace(chs[i])
}
r, err := resonate.New(resonate.Config{URL: "http://localhost:8001"})
if err != nil {
log.Fatalf("resonate.New: %v", err)
}
defer func() { _ = r.Stop() }()
fanoutFn, err := resonate.Register(r, "fanout", fanout)
if err != nil {
log.Fatalf("Register fanout: %v", err)
}
if _, err := resonate.Register(r, "send", send); err != nil {
log.Fatalf("Register send: %v", err)
}
ctx := context.Background()
args := FanoutArgs{Channels: chs, Message: *message}
fmt.Printf("[fanout] starting workflow id=%s channels=%v\n", *id, args.Channels)
h, err := fanoutFn.Run(ctx, *id, args)
if err != nil {
log.Fatalf("Run: %v", err)
}
out, err := h.Result(ctx)
if err != nil {
log.Fatalf("Result: %v", err)
}
fmt.Println("[fanout] done")
for _, d := range out.Delivered {
status := "OK"
if !d.OK {
status = "FAIL: " + d.Reason
}
fmt.Printf(" %-8s %s\n", d.Channel, status)
}
}