عند العمل مع قواعد بيانات SQL في GO ، فإن ضمان الذرات وإدارة التراجعات خلال المعاملات متعددة الخطوات يمكن أن يكون تحديًا. في هذه المقالة ، سأرشدك من خلال إنشاء إطار عمل قوي وقابل لإعادة الاستخدام وقابل للاختبار لتنفيذ معاملات SQL في GO ، وذلك باستخدام مرونة للمرونة.
]سنقوم ببناء أداة SQLWriteExec لتنفيذ عمليات قاعدة بيانات تعتمد متعددة داخل المعاملة. وهو يدعم العمليات العديدة للثانية والدولة ، مما يتيح سير العمل المتطورة مثل إدراج الكيانات ذات الصلة مع إدارة التبعيات بسلاسة.
في تطبيقات العالم الحقيقي ، نادراً ما يتم عزل عمليات قاعدة البيانات. النظر في هذه السيناريوهات:
إدخال مستخدم وتحديث مخزونه ذريًا.
إنشاء طلب ومعالجة مدفوعاته ، وضمان الاتساق.
مع وجود خطوات متعددة ، تصبح إدارة التراجع أثناء الفشل أمرًا بالغ الأهمية لضمان سلامة البيانات.
إذا كنت تكتب قاعدة بيانات TXN ، فقد يكون هناك العديد من لوحات الغلايات التي قد تحتاج إلى مراعاتها قبل كتابة المنطق الأساسي. على الرغم من أن إدارة TXN هذه تدار بواسطة Spring Boot في Java ولم تزعجك أبدًا كثيرًا أثناء كتابة التعليمات البرمجية في Java ، لكن هذا ليس هو الحال في Golang. تم توفير مثال بسيط أدناه
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 }
لا يمكننا أن نتوقع تكرار رمز التراجع/الالتزام بكل وظيفة. لدينا خياران هنا إما إنشاء فئة ستوفر وظيفة كنوع إرجاع يتم تنفيذها عند تنفيذها في التأجيل/ترتيب TXN أو إنشاء فئة غلاف من شأنها أن تلتف جميع Funcs 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 واحدة بحيث يمكن أن تقبل TXNs متعددة. بقلم 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 لمعالجة شيء ما. الآن في طبقة البيانات التي تنفذ دالة مثل هذه ونقلها إلى فئة المنفذ التي تهتم بحقن args وتنفيذ الوظيفة.
// 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 وسنكون لدينا طريقة ملتزمة () لمحاولة ارتكاب 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 }
يمكنك العثور على المزيد من الأمثلة والاختبارات في repo -
https://github.com/mahadev-k/go-tils/tree/main/examples budapples&&&]
اسمحوا لي أن أعرف ما إذا كان أي شخص يرغب في المساهمة والبناء على رأس هذا !!
شكرا لقراءة هذا بعيدا !!
https://in.linkedin.com/in/mahadev-k-934520223 Budap ،
https://x.com/mahadev_k_ Budaps&&&]
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3