تركيب المعاملات
اجمع التأثيرات بأمان
تركيب المعاملات درس مجاني في Scala for Backend Engineering & Functional Programming على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Scala for Backend Engineering & Functional Programming، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Scala for Backend Engineering & Functional Programming 4 دروس في المجموع.
ConnectionIO هي Monad
تكمن القوة الحقيقية لـ Doobie في أن ConnectionIO هي monad. يمكنك دمج عبارات متعددة في برنامج أكبر باستخدام flatMap أو for-comprehension.
كل ما ترتبه بهذه الطريقة يعمل على الاتصال نفسه داخل معاملة واحدة.
import doobie.implicits._
val program: ConnectionIO[Long] =
for {
id <- insertUser("Ada")
_ <- insertProfile(id)
} yield idمعاملة واحدة لكل transact
بغض النظر عن عدد العبارات التي تدمجها، تتحول ConnectionIO بأكملها إلى معاملة واحدة عند استدعاء .transact.
إذا فشلت أي خطوة، تتراجع استراتيجية Transactor عن كل شيء؛ وإذا نجحت جميع الخطوات، تُجرى عملية الالتزام مرة واحدة في النهاية.
val io: IO[Long] = program.transact(xa)عمليات الكتابة الذرية متعددة العبارات
لأن العبارات المدمجة تشترك في معاملة واحدة، تحصل على الذرية تلقائيًا. فعملية تحويل الأموال التي تخصم من حساب وتضيف إلى آخر إما أن تتم بالكامل أو لا تتم إطلاقًا.
لا توجد حالة جزئية إذا طرحت عملية التحديث الثانية استثناءً.
def transfer(from: Long, to: Long, cents: Int) =
for {
_ <- sql"update acct set bal = bal - $cents where id = $from".update.run
_ <- sql"update acct set bal = bal + $cents where id = $to".update.run
} yield ()رفع الأخطاء ومعالجتها
تملك ConnectionIO مثيلًا من MonadError، لذا يمكنك استخدام raiseError لإجهاض المعاملة وتفعيل التراجع.
يمكنك أيضًا استخدام handleErrorWith للتعافي، لكن لاحظ أن التعافي لا يلغي العبارات السابقة بمفرده ما لم تتراجع المعاملة.
import cats.syntax.all._
import doobie.free.connection.{raiseError, pure}
def debit(id: Long, c: Int): ConnectionIO[Unit] =
sql"update acct set bal = bal - $c where id = $id".update.run.flatMap {
case 1 => pure(())
case _ => raiseError(new RuntimeException("no such account"))
}المنطق الشرطي داخل المعاملة
بما أنها مجرد monad، يعمل تدفق التحكم العادي في Scala. يمكنك قراءة صف، واتخاذ فرع بناءً على قيمته، ثم الكتابة وفقًا لذلك، وكل ذلك بصورة ذرية.
يحافظ هذا على قواعد العمل والتخزين في وحدة متسقة واحدة.
for {
bal <- sql"select bal from acct where id = $id".query[Int].unique
_ <- if (bal >= amt)
sql"update acct set bal = bal - $amt where id = $id".update.run
else raiseError(new RuntimeException("insufficient funds"))
} yield ()نقاط الحفظ للتراجع الجزئي
لتحكم أدق، يتيح Doobie نقاط حفظ JDBC المسماة Savepoint عبر جبر FC (اتصال حر). يمكنك التراجع إلى نقطة حفظ من دون إجهاض المعاملة بأكملها.
يتيح ذلك أنماط المحاولة/البديل داخل معاملة خارجية واحدة.
import doobie.free.{connection => FC}
val withSp =
for {
sp <- FC.setSavepoint
_ <- riskyWrite.handleErrorWith(_ => FC.rollback(sp))
} yield ()دمج الاستعلامات وعمليات الكتابة
يمكن مزج عمليات القراءة والكتابة بحرية في البرنامج نفسه. فقد تحدد الحالة الحالية، وتحسب تغييرًا في Scala، ثم تحفظه، وكل ذلك عبر اتصال واحد.
ترى القراءة التغييرات غير المُلتزَم بها التي أُجريت في وقت سابق ضمن المعاملة نفسها.
for {
v <- sql"select stock from items where id = $id".query[Int].unique
_ <- sql"update items set stock = ${v - 1} where id = $id".update.run
n <- sql"select stock from items where id = $id".query[Int].unique
} yield nإعادة استخدام البرامج باستخدام traverse
لأن البرامج قيم، يمكنك تشغيل البرنامج نفسه على قائمة باستخدام traverse من cats، مع ترتيب تنفيذها ضمن معاملة واحدة.
إما أن تُلتزم جميع عمليات الإدراج معًا أو لا تُلتزم أيٌّ منها.
import cats.syntax.all._
def saveAll(names: List[String]): ConnectionIO[List[Long]] =
names.traverse(insertUser)مستويات العزل
يمكنك ضبط مستوى عزل المعاملة داخل البرنامج باستخدام جبر الاتصال قبل تنفيذ العمل.
تمنع المستويات الأكثر صرامة مثل SERIALIZABLE حالات الشذوذ، لكنها قد تجعل قاعدة البيانات تلغي المعاملات المتعارضة، وعندها ينبغي إعادة المحاولة.
import doobie.free.{connection => FC}
import java.sql.Connection
val serializable =
FC.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) *> programإعادة المحاولة عند فشل التسلسل
تنتمي عمليات إعادة المحاولة إلى طبقة التأثير، لا إلى المعاملة. بعد .transact تحصل على IO، ويمكنك إعادة محاولة المعاملة بأكملها عند حدوث خطأ في التسلسل.
كل إعادة محاولة هي معاملة جديدة ومستقلة.
def runWithRetry(io: IO[Unit], n: Int): IO[Unit] =
io.handleErrorWith {
case _ if n > 0 => runWithRetry(io, n - 1)
case e => IO.raiseError(e)
}أبقِ المعاملات قصيرة
لا تدمج في ConnectionIO واحدة إلا العمل الذي يجب أن يكون ذريًا. لا تنفذ استدعاءات خارجية (HTTP أو عمليات CPU بطيئة) داخل معاملة؛ فهذا يشغل اتصالًا وأقفالًا.
اقرأ واحسب خارج المعاملة إن أمكن، ثم اكتب ضمن معاملة قصيرة ومركزة.
// good: gather input first, then one short transactional write
val write: ConnectionIO[Int] =
sql"update users set name = $name where id = $id".update.runتحقق سريع
حلّل حدود المعاملات.
مراجعة
ConnectionIO هي monad، لذا تدمج for-comprehensions العبارات في معاملة ذرية واحدة لكل .transact. تؤدي الأخطاء إلى التراجع عن كل شيء، بينما يجهض raiseError المعاملة عمدًا.
استخدم نقاط الحفظ للتراجع الجزئي، واضبط مستويات العزل داخل البرنامج، وأعد محاولة حالات فشل التسلسل في طبقة التأثير، وأبقِ المعاملات قصيرة.
الأسئلة الشائعة
هل درس «تركيب المعاملات» مجاني؟
نعم — نص درس «تركيب المعاملات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Scala for Backend Engineering & Functional Programming، انتقل إلى CoddyKit PRO. تتضمن دورة Scala for Backend Engineering & Functional Programming 4 دروس في المجموع.
ماذا ستتعلم في «تركيب المعاملات»؟
اجمع التأثيرات بأمان تتمرن على Scala for Backend Engineering & Functional Programming مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Scala for Backend Engineering & Functional Programming؟
لا تُشترط خبرة سابقة. Scala for Backend Engineering & Functional Programming على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «تركيب المعاملات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Scala for Backend Engineering & Functional Programming هذا؟
نعم. كل درس في Scala for Backend Engineering & Functional Programming يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.