Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Event-driven application made easy with Watermill
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
840
Wprowadzenie do Watermill - Gophers Silesia
roblaszczak
1
78
Wprowadzenie do Watermill GoCracow
roblaszczak
0
39
Tactical DDD patterns in Go
roblaszczak
0
110
Other Decks in Technology
See All in Technology
二歩目のフロントエンド・ウェブアクセシビリティ
camiha
2
570
AI駆動で「他アプリの音を加工できる」アプリを作った
satomasahiro2005
0
150
事業成長とデータ活用を止めないためのマネジメントへ挑戦
hiracky16
0
390
Browser Spec: Testing Hotwire with Ruby
takahashim
1
120
Claude の新機能を、 出た日に触って社内に届けるまで
rfdnxbro
1
170
Classmethod Company Deck
satoshi7
1
680
触りたくないデータに、どう触るか AI時代のRBACアクセスガバナンス
ivry_presentationmaterials
0
260
Tofu-Controller lightning talk @ Containerday 2026
gionn
0
110
"優秀な誰か"の下位互換にならないために
yut0naga1_fa
1
1.8k
SAENM ~Jupyterで残すPoCの試行錯誤、未来の自分と仲間を助ける記録!~
terapyon
0
110
QA4AI 2026-Oct 生成AI時代の品質保証
tokuty
1
860
WasmをRustにする
sansantech
PRO
0
250
Featured
See All Featured
Lightning talk: Run Django tests with GitHub Actions
sabderemane
0
300
Deep Space Network (abreviated)
tonyrice
0
370
Git: the NoSQL Database
bkeepers
PRO
433
67k
Designing for Performance
lara
612
70k
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
990
エンジニアに許された特別な時間の終わり
watany
109
250k
Building an army of robots
kneath
307
47k
Design in an AI World
tapps
1
350
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
VelocityConf: Rendering Performance Case Studies
addyosmani
331
25k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.9k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.7k
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!