221 lines
5.3 KiB
Go
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"`
|
|
}
|