"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > بناء تنفيذ معاملات SQL قوية في GO مع إطار عام

بناء تنفيذ معاملات SQL قوية في GO مع إطار عام

نشر في 2025-03-23
تصفح:758

Building Robust SQL Transaction Execution in Go with a Generic Framework

عند العمل مع قواعد بيانات SQL في GO ، فإن ضمان الذرات وإدارة التراجعات خلال المعاملات متعددة الخطوات يمكن أن يكون تحديًا. في هذه المقالة ، سأرشدك من خلال إنشاء إطار عمل قوي وقابل لإعادة الاستخدام وقابل للاختبار لتنفيذ معاملات SQL في GO ، وذلك باستخدام مرونة للمرونة.

]

سنقوم ببناء أداة SQLWriteExec لتنفيذ عمليات قاعدة بيانات تعتمد متعددة داخل المعاملة. وهو يدعم العمليات العديدة للثانية والدولة ، مما يتيح سير العمل المتطورة مثل إدراج الكيانات ذات الصلة مع إدارة التبعيات بسلاسة.

لماذا نحتاج إلى إطار لمعاملات SQL؟

في تطبيقات العالم الحقيقي ، نادراً ما يتم عزل عمليات قاعدة البيانات. النظر في هذه السيناريوهات:

إدخال مستخدم وتحديث مخزونه ذريًا.
إنشاء طلب ومعالجة مدفوعاته ، وضمان الاتساق.
مع وجود خطوات متعددة ، تصبح إدارة التراجع أثناء الفشل أمرًا بالغ الأهمية لضمان سلامة البيانات.

العمل مع GO في إدارة TXN.

إذا كنت تكتب قاعدة بيانات 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&&&]

على الرغم من أننا نتحيز تجاه الأنظمة الموزعة وبروتوكول الإجماع في الوقت الحاضر ، إلا أننا ما زلنا نستخدم SQL وما زالت موجودة.

اسمحوا لي أن أعرف ما إذا كان أي شخص يرغب في المساهمة والبناء على رأس هذا !!

شكرا لقراءة هذا بعيدا !!
https://in.linkedin.com/in/mahadev-k-934520223 Budap ، https://x.com/mahadev_k_ Budaps&&&]

بيان الافراج يتم استنساخ هذه المقالة على: https://dev.to/mahadev_k/building-robust-sql-transaction-execution-in-go-with-a-generic-framework-4j0f؟1 إذا كان هناك أي انتهاك ، يرجى الاتصال [email protected] لحذفه.
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3