Files
upstream-watermill/_examples/real-world-examples/delayed-messages/main.go
2026-09-15 21:27:46 +05:00

221 lines
5.3 KiB
Go

package main
import (
"context"
stdSQL "database/sql"
"fmt"
"strings"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/google/uuid"
_ "github.com/lib/pq"
"github.com/redis/go-redis/v9"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill-redisstream/pkg/redisstream"
"github.com/ThreeDotsLabs/watermill-sql/v4/pkg/sql"
"github.com/ThreeDotsLabs/watermill/components/cqrs"
"github.com/ThreeDotsLabs/watermill/components/delay"
"github.com/ThreeDotsLabs/watermill/components/forwarder"
"github.com/ThreeDotsLabs/watermill/message"
)
func main() {
db, err := stdSQL.Open("postgres", "postgres://watermill:password@postgres:5432/watermill?sslmode=disable")
if err != nil {
panic(err)
}
logger := watermill.NewStdLogger(false, false)
redisClient := redis.NewClient(&redis.Options{Addr: "redis:6379"})
marshaler := cqrs.JSONMarshaler{
GenerateName: cqrs.StructName,
}
redisPublisher, err := redisstream.NewPublisher(redisstream.PublisherConfig{
Client: redisClient,
}, logger)
if err != nil {
panic(err)
}
var sqlPublisher message.Publisher
sqlPublisher, err = sql.NewDelayedPostgreSQLPublisher(sql.BeginnerFromStdSQL(db), sql.DelayedPostgreSQLPublisherConfig{
DelayPublisherConfig: delay.PublisherConfig{},
Logger: logger,
})
if err != nil {
panic(err)
}
sqlPublisher = forwarder.NewPublisher(sqlPublisher, forwarder.PublisherConfig{
ForwarderTopic: "forwarder",
})
eventBus, err := cqrs.NewEventBusWithConfig(redisPublisher, cqrs.EventBusConfig{
GeneratePublishTopic: func(params cqrs.GenerateEventPublishTopicParams) (string, error) {
return params.EventName, nil
},
Marshaler: marshaler,
Logger: logger,
})
if err != nil {
panic(err)
}
commandBus, err := cqrs.NewCommandBusWithConfig(sqlPublisher, cqrs.CommandBusConfig{
GeneratePublishTopic: func(params cqrs.CommandBusGeneratePublishTopicParams) (string, error) {
return params.CommandName, nil
},
Marshaler: marshaler,
Logger: logger,
})
if err != nil {
panic(err)
}
router := message.NewDefaultRouter(logger)
eventProcessor, err := cqrs.NewEventProcessorWithConfig(router, cqrs.EventProcessorConfig{
GenerateSubscribeTopic: func(params cqrs.EventProcessorGenerateSubscribeTopicParams) (string, error) {
return params.EventName, nil
},
SubscriberConstructor: func(params cqrs.EventProcessorSubscriberConstructorParams) (message.Subscriber, error) {
return redisstream.NewSubscriber(redisstream.SubscriberConfig{
Client: redisClient,
ConsumerGroup: params.HandlerName,
}, logger)
},
Marshaler: marshaler,
Logger: logger,
})
if err != nil {
panic(err)
}
commandProcessor, err := cqrs.NewCommandProcessorWithConfig(router, cqrs.CommandProcessorConfig{
GenerateSubscribeTopic: func(params cqrs.CommandProcessorGenerateSubscribeTopicParams) (string, error) {
return params.CommandName, nil
},
SubscriberConstructor: func(params cqrs.CommandProcessorSubscriberConstructorParams) (message.Subscriber, error) {
return redisstream.NewSubscriber(redisstream.SubscriberConfig{
Client: redisClient,
ConsumerGroup: params.HandlerName,
}, logger)
},
Marshaler: marshaler,
Logger: logger,
})
if err != nil {
panic(err)
}
err = eventProcessor.AddHandlers(
cqrs.NewEventHandler(
"OnOrderPlacedHandler",
func(ctx context.Context, event *OrderPlaced) error {
fmt.Printf("💰 Received order from %v <%v>\n", event.Customer.Name, event.Customer.Email)
cmd := SendFeedbackForm{
To: event.Customer.Email,
Name: event.Customer.Name,
}
// In a real world scenario, we would delay the command by a few days
ctx = delay.WithContext(ctx, delay.For(8*time.Second))
err := commandBus.Send(ctx, cmd)
if err != nil {
return err
}
return nil
},
),
)
if err != nil {
panic(err)
}
err = commandProcessor.AddHandlers(
cqrs.NewCommandHandler(
"OnSendFeedbackForm",
func(ctx context.Context, cmd *SendFeedbackForm) error {
fmt.Printf("📧 Sending feedback form to %v <%v>\n", cmd.Name, cmd.To)
// In a real world scenario, we would send an email to the customer here
return nil
},
),
)
if err != nil {
panic(err)
}
sqlSubscriber, err := sql.NewDelayedPostgreSQLSubscriber(sql.BeginnerFromStdSQL(db), sql.DelayedPostgreSQLSubscriberConfig{
DeleteOnAck: true,
Logger: logger,
})
if err != nil {
panic(err)
}
_, err = forwarder.NewForwarder(
sqlSubscriber,
redisPublisher,
logger,
forwarder.Config{
ForwarderTopic: "forwarder",
Router: router,
},
)
if err != nil {
panic(err)
}
go func() {
err = router.Run(context.Background())
if err != nil {
panic(err)
}
}()
<-router.Running()
for {
name := gofakeit.FirstName()
e := OrderPlaced{
OrderID: uuid.NewString(),
Customer: Customer{
Name: name,
Email: fmt.Sprintf("%v@%v", strings.ToLower(name), gofakeit.DomainName()),
},
}
err = eventBus.Publish(context.Background(), e)
if err != nil {
panic(err)
}
time.Sleep(5 * time.Second)
}
}
type Customer struct {
Name string `json:"name"`
Email string `json:"email"`
}
type OrderPlaced struct {
OrderID string `json:"order_id"`
Customer Customer `json:"customer"`
}
type SendFeedbackForm struct {
To string `json:"to"`
Name string `json:"name"`
}