Componer transacciones
Combine efectos de forma segura
Componer transacciones es una lección gratuita de Scala for Backend Engineering & Functional Programming en CoddyKit. Esta es la lección 4 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Scala for Backend Engineering & Functional Programming, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Scala for Backend Engineering & Functional Programming incluye 4 lecciones en total.
ConnectionIO es un mónada
La verdadera potencia de Doobie reside en que ConnectionIO es un mónada. Puede combinar varias sentencias en un programa más grande mediante flatMap o una comprensión for.
Todo lo que secuencie de esta manera se ejecuta en la misma conexión dentro de una única transacción.
import doobie.implicits._
val program: ConnectionIO[Long] =
for {
id <- insertUser("Ada")
_ <- insertProfile(id)
} yield idUna transacción por transact
Independientemente del número de sentencias que componga, todo el ConnectionIO se convierte en una única transacción al llamar a .transact.
Si falla algún paso, la estrategia de Transactor revierte todos los cambios; si todo funciona correctamente, confirma una sola vez al final.
val io: IO[Long] = program.transact(xa)Escrituras atómicas de varias sentencias
Como las sentencias compuestas comparten una transacción, obtiene atomicidad automáticamente. Una transferencia que descuenta dinero de una cuenta y lo abona en otra se realiza por completo o no se realiza.
No queda un estado parcial si la segunda actualización lanza una excepción.
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 ()Lanzar y gestionar errores
ConnectionIO tiene una instancia de MonadError, por lo que puede usar raiseError para cancelar una transacción y provocar una reversión.
También puede usar handleErrorWith para recuperarse, pero tenga en cuenta que recuperarse no deshace por sí solo las sentencias anteriores, a menos que la transacción se revierta.
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"))
}Lógica condicional en una transacción
Como no es más que un mónada, funciona el flujo de control habitual de Scala. Puede leer una fila, tomar una decisión según su valor y escribir en consecuencia, todo de forma atómica.
Esto mantiene las reglas de negocio y la persistencia en una única unidad coherente.
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 ()Puntos de guardado para reversiones parciales
Para un control más preciso, Doobie expone los Savepoint de JDBC mediante el álgebra FC (conexión libre). Puede revertir hasta un punto de guardado sin cancelar toda la transacción.
Esto permite aplicar patrones de intento y alternativa dentro de una única transacción externa.
import doobie.free.{connection => FC}
val withSp =
for {
sp <- FC.setSavepoint
_ <- riskyWrite.handleErrorWith(_ => FC.rollback(sp))
} yield ()Combinar consultas y operaciones de escritura
Las lecturas y escrituras se pueden mezclar libremente en el mismo programa. Puede seleccionar el estado actual, calcular un cambio en Scala y conservarlo, todo en una misma conexión.
La lectura ve los cambios no confirmados realizados anteriormente en la misma transacción.
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 nReutilizar programas con traverse
Como los programas son valores, puede ejecutar el mismo programa sobre una lista con traverse de cats, secuenciándolos en una única transacción.
Todas las inserciones se confirman juntas o no se confirma ninguna.
import cats.syntax.all._
def saveAll(names: List[String]): ConnectionIO[List[Long]] =
names.traverse(insertUser)Niveles de aislamiento
Puede establecer el nivel de aislamiento de la transacción dentro de un programa mediante el álgebra de conexión antes de realizar el trabajo.
Los niveles más estrictos, como SERIALIZABLE, evitan anomalías, pero pueden hacer que la base de datos cancele transacciones en conflicto, que deberá reintentar.
import doobie.free.{connection => FC}
import java.sql.Connection
val serializable =
FC.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) *> programReintentar ante fallos de serialización
Los reintentos pertenecen a la capa de efectos, no a la transacción. Después de .transact obtiene un IO y puede reintentar toda la transacción si se produce un error de serialización.
Cada reintento es una transacción nueva e independiente.
def runWithRetry(io: IO[Unit], n: Int): IO[Unit] =
io.handleErrorWith {
case _ if n > 0 => runWithRetry(io, n - 1)
case e => IO.raiseError(e)
}Mantener breves las transacciones
Componga en un único ConnectionIO solo el trabajo que deba ser atómico. No realice llamadas externas (HTTP o cálculos de CPU lentos) dentro de una transacción; mantendría ocupados una conexión y varios bloqueos.
Lea y calcule fuera de la transacción si es posible, y después escriba en una transacción breve.
// good: gather input first, then one short transactional write
val write: ConnectionIO[Int] =
sql"update users set name = $name where id = $id".update.runComprobación rápida
Razone sobre los límites de las transacciones.
Resumen
ConnectionIO es un mónada, por lo que las comprensiones for componen sentencias en una única transacción atómica por cada .transact. Los errores revierten todos los cambios; raiseError cancela deliberadamente la transacción.
Use puntos de guardado para reversiones parciales, establezca los niveles de aislamiento dentro del programa, reintente los fallos de serialización en la capa de efectos y mantenga breves las transacciones.
Preguntas frecuentes
¿La lección «Componer transacciones» es gratis?
Sí — el texto completo de «Componer transacciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Scala for Backend Engineering & Functional Programming, actualiza a CoddyKit PRO. El curso de Scala for Backend Engineering & Functional Programming incluye 4 lecciones en total.
¿Qué aprenderé en «Componer transacciones»?
Combine efectos de forma segura Practicas Scala for Backend Engineering & Functional Programming con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Scala for Backend Engineering & Functional Programming?
No se requiere experiencia previa. Scala for Backend Engineering & Functional Programming en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 4 de 4.
¿Cuánto tiempo toma la lección «Componer transacciones»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Scala for Backend Engineering & Functional Programming?
Sí. Cada lección de Scala for Backend Engineering & Functional Programming incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Conectar con un transactor
- Ejecutar consultas
- Inserciones y actualizaciones
- Componer transacciones