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
Message-driven application made easy with Water...
Search
GopherCon Russia
April 13, 2019
Programming
70
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Message-driven application made easy with Watermill – Robert Laszczak
GopherCon Russia
April 13, 2019
More Decks by GopherCon Russia
See All by GopherCon Russia
Go Profiling from Bottom Up - Felix Geisendörfer
gopherconrussia
0
260
Learning Unsung Gotchas of Go - Rashmi Nagpal
gopherconrussia
1
300
Прозрачный gRPC-proxy один-ко-многим - Андрей Смирнов
gopherconrussia
0
180
Из Python в Go и обратно - Андрей Минкин
gopherconrussia
0
190
Оптимизация работы с PostgreSQL в Go: от 50 до 5000 RPS - Иван Осадчий
gopherconrussia
0
220
Пакет embed: распаковка знаний - Илья Данилкин
gopherconrussia
0
290
За пару мгновений до main() - Олег Ковалев
gopherconrussia
0
170
Тестирование в Go c Ginkgo и Gomega - Александр Егурнов
gopherconrussia
0
160
Building an Autoscaling HTTP Proxy for Kubernetes - Aaron Schlesinger
gopherconrussia
0
160
Other Decks in Programming
See All in Programming
How I Won Prize Money at a Hackathon Using Codex and Symphony Alpha
yasei_no_otoko
0
100
AI時代のPHPer生存戦略 ~「言語、もうなんでもよくない?」に本気で向き合う~
vivion
0
770
Go言語とトイモデルで学ぶTransformerの気持ち / fukuokago23-transformer
monochromegane
0
180
【やさしく解説 設計編・中級 #6】良いアーキテクチャとは ~ 一本の登り道の、行き先 ~
panda728
PRO
0
220
php-fpmのプロセスが枯渇した日-調査・対処・そして本当にやるべきだったこと-
shibuchaaaan
0
300
楽しそうなつよつよエンジニアと目が死んでる僕/A brilliant engineer having a blast, and dead-eyed me.
3l4l5
2
190
言葉の格闘技のススメ~紙とペンと言葉から始める、キャリアの描き方~
progresscicada
2
150
AIを紡ぐPMのお話
swdtkuy
0
110
Claude CodeとAgentCore Gatewayを繋ぐ際の認証認可 / Authentication and authorization when connecting Claude Code with AgentCore Gateway
har1101
2
290
いまどきの Codex で開発する visionOS アプリの開発スタイルについて
karad
0
150
「人を評価する AI」の設計と実装
ryoyanara
0
210
琵琶湖の水は止められてもNet--HTTPのリトライは止められない / You might be able to stop the water flow of Lake Biwa but you can't stop Net::HTTP retries
luccafort
PRO
0
750
Featured
See All Featured
Bootstrapping a Software Product
garrettdimon
PRO
307
120k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
240
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
400
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
210
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.5k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
490
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
790
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
380
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
Music & Morning Musume
bryan
47
7.3k
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!