Lorsque vous travaillez avec les bases de données SQL dans GO, assurer l'atomicité et gérer les reculs pendant les transactions en plusieurs étapes peut être difficile. Dans cet article, je vous guiderai à travers la création d'un cadre robuste, réutilisable et testable pour exécuter des transactions SQL dans Go, en utilisant des génériques pour la flexibilité.
Nous allons créer un utilitaire SQLWriteExec pour exécuter plusieurs opérations de base de données dépendantes dans une transaction. Il prend en charge les opérations sans état et avec état, permettant des flux de travail sophistiqués comme l'insertion d'entités associées tout en gérant les dépendances de manière transparente.
Dans les applications du monde réel, les opérations de base de données sont rarement isolées. Considérez ces scénarios:
Insertion d'un utilisateur et mise à jour de son inventaire atomiquement.
Création d'une commande et traitement de son paiement, garantissant la cohérence.
Avec plusieurs étapes impliquées, la gestion des reculs pendant les échecs devient cruciale pour assurer l'intégrité des données.
Si vous écrivez une base de données TXN, il pourrait y avoir plusieurs plaques de chaudière que vous pourriez avoir besoin de considérer avant d'écrire la logique principale. Bien que cette gestion TXN soit gérée par Spring Boot en Java et que vous ne vous en souciez jamais beaucoup lors de l'écriture de code en Java, mais ce n'est pas le cas à Golang. Un exemple simple est fourni ci-dessous
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 }
Nous ne pouvons pas nous attendre à répéter le code Rollback / Commit pour chaque fonction. Nous avons deux options ici, créez une classe qui fournira une fonction en tant que type de retour qui, lorsqu'elle est exécutée dans le repère, commettra / rollback TXN ou créera une classe de wrapper qui enveloppera toutes les funcs TXN et exécutera en une seule fois.
Je suis allé avec le choix ultérieur et le changement de code peut être vu ci-dessous.
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 }
Ce code sera beaucoup plus précis et concis.
L'idée est d'isoler le TXN en une seule structure GO de telle sorte qu'elle peut accepter plusieurs TXN. Par txn je veux dire des fonctions qui feront l'action avec le txn que nous avons créé pour la classe.
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
Ces deux sont des types de fonction qui prendront un TXN pour traiter quelque chose. Maintenant, dans la couche de données implémentant une création d'une fonction comme celle-ci et passez-la à la classe d'exécuteur qui s'occupe de l'injection des args et de l'exécution de la fonction.
// 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 }
C'est là que nous stockons tous les détails TXN_FN et nous aurons la méthode commit () pour essayer de commettre le 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 }
Vous pouvez trouver plus d'exemples et de tests dans le repo -
https://github.com/mahadev-k/go-utils/tree/main/examples fichiers
Faites-moi savoir si quelqu'un souhaite contribuer et construire en haut de cela !!
Merci d'avoir lu aussi loin !!
https://in.linkedin.com/in/mahadev-k-934520223
https://x.com/mahadev_k_ ·&&&]
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3