Привет всем энтузиастам-разработчикам! Сегодня окунемся в увлекательную область программной разработки, где шаблоны подобны путеводным звездам на бескрайних просторах кода, а конкретно шаблон Saga — это наш компас на пути создания надежных, масштабируемых систем Go.
Этим шаблоном — убийцей распределенной транзакции — каждой транзакции на всех этапах бизнес-процесса предоставляется соответствующая транзакция компенсации. Так что, когда появляется условие ошибки, из-за которого продолжение бизнес-процесса становится невозможным, эти транзакции компенсации выполняются для уже завершенных этапов. Проделанная к этому моменту работа фактически отменяется, чем обеспечивается согласованность системы.
Управление продолжительными транзакциями
Шаблон Saga — важный инструмент для управления продолжительными транзакциями в распределенных системах. Сложные транзакции разбиваются им на маленькие, управляемые этапы, так обеспечиваются согласованность и надежность данных.
В шаблоне Saga каждый этап транзакции сопровождается действием компенсации, необходимым для корректного восстановления в случае сбоя. Так система остается отказоустойчивой и согласованной.
Когда применяется Saga?
Шаблон приходится кстати в сценариях управления длительными или распределенными транзакциями в службах или компонентах микросервисной архитектуры, сложными бизнес-процессами, транзакциями компенсации, а в конечном итоге согласованностью.
Вот реальные примеры таких сценариев.
При онлайн-покупке системе нужно зарезервировать товар, обработать платеж, отправить товар. Если какой-либо этап не пройден, то предыдущие системой отменяются для поддержания согласованности.
При бронировании перелета или гостиницы системе нужно подтвердить бронь, обработать платеж, оформить билеты. Если какой-либо этап не пройден, предыдущие системой отменяются во избежание двойного бронирования или некорректных начислений.
При доставке еды системой принимается заказ, готовится еда, назначается водитель доставки, обрабатывается платеж. Если какой-либо этап не пройден, предыдущие системой отменяются во избежание некорректной обработки заказа.
При финансовых транзакциях, денежных переводах или онлайн-заявках на кредит системой подтверждается запрос, обрабатывается транзакция, обновляется счет. Если какой-либо этап не пройден, предыдущие системой откатываются во избежание некорректных транзакций или несоответствия данных.
Переходим сразу к коду
В этом примере сделаем акцент на двух сценариях обработки: заказа и оплаты. В шаблоне Saga функции MakeOrder
и CancelOrder
выполняются как локальные транзакции. Если при заказе проблем нет, выполняется функция ProcessPayment
, то есть этап оплаты. Если проблема на этапе оплаты, выполняется функция CancelPayment
— это действие компенсации.
type Order struct {
Id uint
Product string
Quantity int
Total float64
}
func ProcessOrder(workflow *wf.Workflow) {
order := &Order{
Id: 123,
Product: "Laptop",
Quantity: 1,
Total: 1000.00,
}
workflow.AddStep("make_order", wf.SagaStep{Transaction: order.MakeOrder, Compensate: order.CancelOrder})
workflow.AddStep("process_payment", wf.SagaStep{Transaction: order.ProcessPayment, Compensate: order.CancelPayment})
}
func (o *Order) MakeOrder() error {
log.Printf("Order created: %v\n", o)
return nil
}
func (o *Order) CancelOrder() error {
log.Printf("Order cancelled: %d\n", o.Id)
return nil
}
func (o *Order) ProcessPayment() error {
return errors.New("payment failed")
}
func (o *Order) CancelPayment() error {
log.Printf("Payment cancelled: %v\n", o.Total)
return nil
}
В этом коде обработки заказа намеренно спровоцированы ошибки функции ProcessPayment
, так продемонстрируем откат всех предыдущих транзакций.
Вот код рабочих процессов:
type LocalTransaction func() error
type CompensatingAction func() error
type Workflow struct {
Steps map[string]SagaStep
}
type SagaStep struct {
Transaction LocalTransaction
Compensate CompensatingAction
}
func NewWorkflow() *Workflow {
return &Workflow{
Steps: make(map[string]SagaStep),
}
}
func (w *Workflow) AddStep(name string, step SagaStep) {
w.Steps[name] = step
}
func (w *Workflow) Execute() error {
for stepName, stepFunc := range w.Steps {
log.Printf("[Executing step] %s", stepName)
if err := stepFunc.Transaction(); err != nil {
log.Printf("[Error executing step] %s: %s", stepName, err)
for n, f := range w.Steps {
log.Printf("[Compensating step] %s", n)
f.Compensate()
if stepName == n {
break
}
}
}
}
return nil
}
package main
import (
order "go-saga-workflow/internal"
wf "go-saga-workflow/pkg"
"log"
)
func main() {
workflow := wf.NewWorkflow()
order.ProcessOrder(workflow)
err := workflow.Execute()
if err != nil {
log.Printf("saga execution failed: %+v\n", err)
} else {
log.Printf("saga executed successfully\n")
}
}
Подводя итоги
Шаблон Saga — важный инструмент в арсенале разработчика. Эффективно используя его возможности, разработчики создают отказоустойчивые, масштабируемые, легко сопровождаемые системы. Благодаря Saga транзакции, которыми охватывается несколько служб или компонентов, становятся более управляемыми, этим обеспечиваются согласованность и надежность данных даже в случае сбоев.
Поэтому, когда в следующий раз встретите сценарий со сложными транзакциями из области программной разработки, вспомните о шаблоне Saga — надежном инструменте для разбора нюансов распределенных систем.
В этом простом примере шаблон Saga на Go описан посредством моделирования этапов заказа и оплаты. Можете расширить его, усложнив рабочие процессы и применяя различные шаблоны. Подробнее код проекта — на GitHub.
Читайте также:
- Что такое шаблон SAGA и какую проблему он решает в микросервисной архитектуре
- Реализация шаблона Saga в микросервисах с помощью Node.js
- Node.js быстрее, чем Go
Читайте нас в Telegram, VK и Дзен
Перевод статьи Cem Bideci: Implementing the Saga Pattern in Go: A Hands-On Approach