при работе с базами данных SQL в GO, обеспечение атома и управления откатами во время многоэтапных транзакций может быть сложной задачей. В этой статье я направляю вас через создание надежной, многоразовой и тестируемой структуры для выполнения транзакций SQL в Go, используя Generics для гибкости.
]мы создадим утилиту SQLWriteExec для выполнения нескольких зависимых операций базы данных в рамках транзакции. Он поддерживает как без сохранения состояния, так и государственные операции, обеспечивая сложные рабочие процессы, такие как вставка связанных объектов при плавном управлении зависимостями.
]в реальных приложениях операции базы данных редко изолированы. Рассмотрим эти сценарии:
] вставка пользователя и обновлять их инвентарь атомно.
]
Создание заказа и обработка своего платежа, обеспечивая согласованность.
]
С учетом нескольких шагов управление откатами во время сбоев становится решающим для обеспечения целостности данных.
Если вы пишете базу данных txn, может быть несколько котловых пластин, которые вам может потребоваться рассмотреть перед написанием основной логики. В то время как это управление TXN управляется Spring Boot в Java, и вы никогда не беспокоились о них во время написания кода на Java, но это не так в Голанге. Простой пример приведен ниже
]
func basicTxn(db *sql.DB) error { // start a transaction tx, err := db.Begin() if err != nil { return err } defer func() { if r := recover(); r != nil { tx.Rollback() } else if err != nil { tx.Rollback() } else { tx.Commit() } }() // insert data into the orders table _, err = tx.Exec("INSERT INTO orders (id, customer_name, order_date) VALUES (1, 'John Doe', '2022-01-01')") if err != nil { return err } return nil }]
мы не можем рассчитывать на повторить код Rollback/Commit для каждой функции. У нас есть два варианта здесь либо создать класс, который предоставит функцию в качестве типа возврата, который при выполнении в Defer будет совершать/откат TXN, либо создаст класс обертки, который объединит все функции TXN вместе и выполняется за один раз.
] я пошел с более поздним выбором, и изменение кода можно увидеть ниже.
]
func TestSqlWriteExec_CreateOrderTxn(t *testing.T) { db := setupDatabase() // create a new SQL Write Executor err := dbutils.NewSqlTxnExec[OrderRequest, OrderProcessingResponse](context.TODO(), db, nil, &OrderRequest{CustomerName: "CustomerA", ProductID: 1, Quantity: 10}). StatefulExec(InsertOrder). StatefulExec(UpdateInventory). StatefulExec(InsertShipment). Commit() // check if the transaction was committed successfully if err != nil { t.Fatal(err) return } verifyTransactionSuccessful(t, db) t.Cleanup( func() { cleanup(db) db.Close() }, ) }]
func InsertOrder(ctx context.Context, txn *sql.Tx, order *OrderRequest, orderProcessing *OrderProcessingResponse) error { // Insert Order result, err := txn.Exec("INSERT INTO orders (customer_name, product_id, quantity) VALUES ($1, $2, $3)", order.CustomerName, order.ProductID, order.Quantity) if err != nil { return err } // Get the inserted Order ID orderProcessing.OrderID, err = result.LastInsertId() return err } func UpdateInventory(ctx context.Context, txn *sql.Tx, order *OrderRequest, orderProcessing *OrderProcessingResponse) error { // Update Inventory if it exists and the quantity is greater than the quantity check if it exists result, err := txn.Exec("UPDATE inventory SET product_quantity = product_quantity - $1 WHERE id = $2 AND product_quantity >= $1", order.Quantity, order.ProductID) if err != nil { return err } // Get the number of rows affected rowsAffected, err := result.RowsAffected() if rowsAffected == 0 { return errors.New("Insufficient inventory") } return err } func InsertShipment(ctx context.Context, txn *sql.Tx, order *OrderRequest, orderProcessing *OrderProcessingResponse) error { // Insert Shipment result, err := txn.Exec("INSERT INTO shipping_info (customer_name, shipping_address) VALUES ($1, 'Shipping Address')", order.CustomerName) if err != nil { return err } // Get the inserted Shipping ID orderProcessing.ShippingID, err = result.LastInsertId() return err }]
этот код будет намного более точным и кратким.
] Идея состоит в том, чтобы изолировать TXN на одну структуру GO так, чтобы она мог принять несколько TXN. Под TXN я имею в виду функции, которые будут действовать с TXN, который мы создали для класса.
type TxnFn[T any] func(ctx context.Context, txn *sql.Tx, processingReq *T) error type StatefulTxnFn[T any, R any] func(ctx context.Context, txn *sql.Tx, processingReq *T, processedRes *R) error]
эти два - типы функций, которые займут TXN для чего -то обработать. Теперь в уровне данных внедряет функцию создать такую функцию и передайте ее классу исполнителя, который заботится о инъекции ARG и выполнении функции.
]
// SQL Write Executor is responsible when executing write operations // For dependent writes you may need to add the dependent data to processReq and proceed to the next function call type SqlTxnExec[T any, R any] struct { db *sql.DB txn *sql.Tx txnFns []TxnFn[T] statefulTxnFns []StatefulTxnFn[T, R] processingReq *T processedRes *R ctx context.Context err error }]
Здесь мы храним все данные txn_fn, и у нас будет метод Commit (), чтобы попробовать совершить TXN.
]
func (s *SqlTxnExec[T, R]) Commit() (err error) { defer func() { if p := recover(); p != nil { s.txn.Rollback() panic(p) } else if err != nil { err = errors.Join(err, s.txn.Rollback()) } else { err = errors.Join(err, s.txn.Commit()) } return }() for _, writeFn := range s.txnFns { if err = writeFn(s.ctx, s.txn, s.processingReq); err != nil { return } } for _, statefulWriteFn := range s.statefulTxnFns { if err = statefulWriteFn(s.ctx, s.txn, s.processingReq, s.processedRes); err != nil { return } } return }]
вы можете найти больше примеров и тестов в репо -
]
https://github.com/mahadev-k/go-utils/tree/main/examples=&&&&]]
]
дайте мне знать, если кто -нибудь хочет внести свой вклад и построить сверху !!]
Спасибо, что прочитали это далеко !!
]
https://in.linkedin.com/in/mahadev-k-934520223=&&&&]]
https://x.com/mahadev_k_ts&&&&]
]
]
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3