Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Event-driven application made easy with Watermill
Search
Robert Laszczak
April 13, 2019
Technology
250
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Event-driven application made easy with Watermill
Robert Laszczak
April 13, 2019
More Decks by Robert Laszczak
See All by Robert Laszczak
Let's build event-driven application in 15 minutes - introduction to Watermill
roblaszczak
0
830
Wprowadzenie do Watermill - Gophers Silesia
roblaszczak
1
73
Wprowadzenie do Watermill GoCracow
roblaszczak
0
37
Tactical DDD patterns in Go
roblaszczak
0
110
Other Decks in Technology
See All in Technology
AI驚き屋発見器
yama3133
1
390
MCPをつなげて作る組織横断のAIエージェント基盤
tsubakimoto_s
0
420
クラウドを使う側から、作る側へ / 大吉祥寺.pm 2026前夜祭
fujiwara3
8
1.9k
歴史から理解するクラウドインフラのしくみ
kizawa2020
0
190
AIツールを導入しても生産性はあがらない? カオナビが直面した 3つの壁と乗り越え方。/ Overcoming 3 Barriers to AI-Driven Productivity at kaonavi
kaonavi
0
470
toio・myCobotでフィジカルAIっぽいことを行うための検討(とりあえず調査) / フィジカルAI LT(IoTLTによる開催)
you
PRO
0
130
基調講演:人とAIをつなぐIoTの今と未来 ー 「フィジカル」と「デジタル」が出会うその先へ【SORACOM Discovery 2026】
soracom
PRO
0
360
1台から試せる!Edge IoTを使った位置情報の活用設計【SORACOM Discovery 2026】
soracom
PRO
0
110
VPCセキュリティ対応の最新事情
nagisa53
2
350
大 AI 時代におけるC# の事情 ~ぶっちゃけトークを交えながら~
nenonaninu
1
560
Retriever と Reranker、結局どうする?
kazuaki
1
510
ウォーターフォール開発案件のPMとしてAI活用を模索している話
hatahata021
2
220
Featured
See All Featured
Automating Front-end Workflow
addyosmani
1370
210k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.6k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Test your architecture with Archunit
thirion
1
2.3k
Technical Leadership for Architectural Decision Making
baasie
3
450
4 Signs Your Business is Dying
shpigford
187
22k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2.1k
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
880
AI Search: Where Are We & What Can We Do About It?
aleyda
0
7.7k
The SEO Collaboration Effect
kristinabergwall1
1
510
Utilizing Notion as your number one productivity tool
mfonobong
4
490
Transcript
roblaszczak MESSAGE-DRIVEN APPLICATION MADE EASY WITH WATERMILL
roblaszczak MESSAGE-DRIVEN ARCHITECTURE
roblaszczak SCALEABLE RESILIENT LOWER COUPLING
roblaszczak HARDER TO IMPLEMENT HARDER TO DEBUG
roblaszczak BUT… CAN BE BUILDING MESSAGE-DRIVEN APPLICATIONS EASIER?
roblaszczak PROVEN PATTERNS
roblaszczak WATERMILL
roblaszczak COMPANY X ROOM BOOKING PORTAL
roblaszczak
roblaszczak
roblaszczak r.Post("/book-room", func(w http.ResponseWriter, r *http.Request) { // ... if
err := bookingRepo.AddBooking(booking); err != nil { panic(err) } err := paymentsInitializer.InitializePayment(p) if err != nil { panic(err) } })
roblaszczak
roblaszczak
roblaszczak HOW TO FIX IT?
roblaszczak MAYBE MESSAGE-DRIVEN?
roblaszczak WE DIDN’T DONE THAT BEFORE
roblaszczak LET’S EXPERIMENT! LET’S USE WATERMILL
roblaszczak
roblaszczak
roblaszczak err := bookingRepo.AddBooking(booking) //... event, err := json.Marshal(RoomBooked{ BookingUUID:
booking.ID(), RoomID: req.RoomID, Price: booking.Price(), PaymentChannel: req.PaymentChannel, }) //... err := publisher.Publish( "room_bookings", message.NewMessage(watermill.NewUUID(), event), ) //...
roblaszczak import ( "github.com/ThreeDotsLabs/watermill/message/infrastructure/kafka" ) publisher, err := kafka.NewPublisher( []string{"localhost:9092"},
kafka.DefaultMarshaler{}, nil, // Sarama config watermillLogger, )
roblaszczak subscriber, err := kafka.NewSubscriber( kafka.SubscriberConfig{ Brokers: []string{"localhost:9092"}, ConsumerGroup: "some_consumer_group",
}, nil, kafka.DefaultMarshaler{}, watermillLogger, )
roblaszczak watermillRouter, err := message.NewRouter( message.RouterConfig{}, watermillLogger, ) if err
!= nil { panic(err) } watermillRouter.AddMiddleware(watermillMiddleware.Recoverer)
roblaszczak type HandlerFunc func(msg *Message) ([]*Message, error) type HandlerMiddleware func(h
HandlerFunc) HandlerFunc
roblaszczak watermillRouter.AddNoPublisherHandler( "book_room_handler", // handler name "room_bookings", //subscribe topic subscriber,
func(msg *message.Message) ([]*message.Message, error) { event := RoomBooked{} err := json.Unmarshal(msg.Payload, &event) // ... p, err := payment.NewPayment(event.Price, event.Channel) // ... err := paymentsInitializer.InitializePayment(p) // ... return nil, nil }, )
roblaszczak
roblaszczak watermillRouter.AddMiddleware( watermillMiddleware.Recoverer, watermillMiddleware.Retry{ MaxRetries: 3, WaitTime: time.Second*10, }.Middleware, )
roblaszczak
roblaszczak
roblaszczak ANALOGY 400 Bad Request
roblaszczak POISON QUEUE
roblaszczak
roblaszczak pq, err := watermillMiddleware.NewPoisonQueue( publisher, "poison_queue" ) // ...
watermillRouter.AddMiddleware( pq.Middleware, watermillMiddleware.Retry{ MaxRetries: 1, Logger: watermillLogger, }.Middleware, )
roblaszczak pq, err := watermillMiddleware.NewPoisonQueueWithFilter( publisher, "poison_queue", func(err error) bool
{ switch errors.Cause(err).(type) { case *json.InvalidUnmarshalError: return true Default: return false } }, )
roblaszczak FEATURE REQUEST: BOOKINGS COUNTER
roblaszczak JUST A SQL QUERY?
roblaszczak
roblaszczak
roblaszczak
roblaszczak CQRS COMPONENT
roblaszczak func (BookingsCounterGenerator) NewEvent() interface{} { return &event.RoomBooked{} } func
(b BookingsCounterGenerator) Handle(e interface{}) error { event := e.(*event.RoomBooked) return b.i.IncrementBookingCounter(event.BookingUUID) }
roblaszczak FEATURE REQUEST: FULL TEXT SEARCH
roblaszczak
roblaszczak POLYGLOT PERSISTENCE
roblaszczak CONTRACT IS IMPORTANT
roblaszczak
roblaszczak DATA IS NOT UP-TO DATE
roblaszczak EVENTUAL CONSISTENCY
roblaszczak
roblaszczak IN MOST CASES EVENTUAL CONSISTENCY IS ACCEPTABLE
roblaszczak DEBUGGING
roblaszczak CORRELATION ID watermillRouter.AddMiddleware( watermillMiddleware.CorrelationIDWithAutogenerate( func() string { return watermill.NewShortUUID()
}), )
roblaszczak
roblaszczak METRICS prometheusRegistry, closeMetricsServer := metrics.CreateRegistryAndServeHTTP(":8081") defer closeMetricsServer() metricsBuilder :=
metrics.NewPrometheusMetricsBuilder(prometheusRegistry, "", "") metricsBuilder.AddPrometheusRouterMetrics(watermillRouter)
roblaszczak
roblaszczak TRACING IN PROGRESS :)
roblaszczak
roblaszczak
roblaszczak DUPLICATED BOOKINGS
roblaszczak 23:09:35 [INFO] Booking 01D6P9VAFXEGXHAF6MW4ZRZD8V done 23:09:37 [INFO] Booking 01D6P9STY5CCN8RJT4YNTHFZRH
done 23:09:37 [ERROR] Lost Kafka connection 23:09:38 [INFO] Connecting to Kafka 23:09:41 [INFO] Booking 01D6P9STY5CCN8RJT4YNTHFZRH done 23:09:35 [INFO] Booking 01D6P9VTE0QNCESP805MVH9Z1K done
roblaszczak
roblaszczak
roblaszczak
roblaszczak AT-LEAST-ONCE DELIVERY
roblaszczak 23:09:35 [INFO] Booking 01D6P9VAFXEGXHAF6MW4ZRZD8V done 23:09:37 [INFO] Booking 01D6P9STY5CCN8RJT4YNTHFZRH
done 23:09:37 [ERROR] Lost Kafka connection 23:09:38 [INFO] Connecting to Kafka 23:09:41 [INFO] Booking 01D6P9STY5CCN8RJT4YNTHFZRH done 23:09:35 [INFO] Booking 01D6P9VTE0QNCESP805MVH9Z1K done
roblaszczak SOLUTION: DEDUPLICATION
roblaszczak func (m *MemoryBookings) IncrementBookingCounter(bookingUUID string) error { m.lock.Lock() defer
m.lock.Unlock() if m.bookings == nil { m.bookings = map[string]struct{}{} } if _, ok := m.bookings[bookingUUID]; ok { // deduplicated return nil } m.bookings[bookingUUID] = struct{}{} m.count += 1 return nil }
roblaszczak WHAT’S NEXT?
roblaszczak DON’T BE AFRAID TO EXPERIMENT
roblaszczak roblaszczak https://watermill.io/ github.com/ThreeDotsLabs/watermill github.com/roblaszczak/reservation-system
roblaszczak roblaszczak https://watermill.io/ github.com/ThreeDotsLabs/watermill github.com/roblaszczak/reservation-system THANKS!