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
66
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
250
Learning Unsung Gotchas of Go - Rashmi Nagpal
gopherconrussia
1
300
Прозрачный gRPC-proxy один-ко-многим - Андрей Смирнов
gopherconrussia
0
170
Из Python в Go и обратно - Андрей Минкин
gopherconrussia
0
180
Оптимизация работы с PostgreSQL в Go: от 50 до 5000 RPS - Иван Осадчий
gopherconrussia
0
210
Пакет embed: распаковка знаний - Илья Данилкин
gopherconrussia
0
280
За пару мгновений до main() - Олег Ковалев
gopherconrussia
0
160
Тестирование в Go c Ginkgo и Gomega - Александр Егурнов
gopherconrussia
0
150
Building an Autoscaling HTTP Proxy for Kubernetes - Aaron Schlesinger
gopherconrussia
0
160
Other Decks in Programming
See All in Programming
権限チェックの一貫性を型で守る TypeScript による多層防御
mnch
4
1.1k
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
2
220
「AIで開発し、AIを届ける」をEvalでつなぐ 〜AIネイティブに始めるプロダクト開発の実践〜 / Connecting "Develop with AI, deliver AI" with Eval
rkaga
4
4.3k
キャリア迷子上等 ─ "ない道"は自分で作ればいい
16bitidol
3
1.7k
AI時代の仕事技芸論 — ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ
kuranuki
1
620
気づいたらRubyで100作品 ー クリエイティブコーディングが生活の一部になるまで / 100 Ruby Sketches Later: How Creative Coding Became Part of My Life
chobishiba
3
550
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
120
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
190
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
250
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
310
ふつうのFeature Flag実践入門
irof
7
3.6k
Why Laravel apps break—Mastering the fundamentals to keep them maintainable
kentaroutakeda
1
340
Featured
See All Featured
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
210
Testing 201, or: Great Expectations
jmmastey
46
8.2k
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
200
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
160
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.2k
Paper Plane
katiecoart
PRO
1
51k
Bash Introduction
62gerente
615
210k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.3k
Optimising Largest Contentful Paint
csswizardry
37
3.7k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
46
2.8k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
360
We Are The Robots
honzajavorek
0
240
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!