En esta clase aprenderemos cómo gestionar transacciones en Orleans, asegurando consistencia de datos en Granos cuando hay múltiples operaciones que deben ejecutarse de manera atómica.
Objetivos de la Clase
- Comprender cómo Orleans maneja las transacciones distribuidas.
- Implementar Granos con transacciones en Orleans.
- Configurar persistencia con soporte transaccional.
- Probar la ejecución de transacciones Orleans con múltiples Granos.
¿Qué son las Transacciones en Orleans?
Las transacciones en Orleans permiten ejecutar múltiples operaciones en diferentes Granos de forma atómica. Esto significa que:
- Todas las operaciones se completan o ninguna se aplica.
- Orleans maneja automáticamente los bloqueos y la concurrencia.
- Se pueden distribuir entre múltiples Granos, sin necesidad de base de datos transaccional.
Casos de Uso
- Cuentas bancarias: Transferencias entre cuentas sin pérdida de dinero.
- Carritos de compras: Asegurar que los productos se descuentan correctamente del inventario.
- Juegos multijugador: Mantener sincronizados los cambios en el estado de los jugadores.
Configurar Orleans para Transacciones
1. Instalar Dependencias
Ejecuta en OrleansDemo:
dotnet add package Microsoft.Orleans.Transactions
2. Configurar Transacciones en Program.cs
Edita Program.cs
en OrleansDemo y habilita soporte transaccional:
builder.UseOrleans(siloBuilder =>
{
siloBuilder.UseLocalhostClustering();
siloBuilder.AddMemoryGrainStorage("transactionStore");
siloBuilder.UseTransactions();
});
Explicación del Código
AddMemoryGrainStorage("transactionStore")
: Habilita almacenamiento en memoria para transacciones.UseTransactions()
: Activa el soporte transaccional en Orleans.
Implementar Granos con Transacciones
1. Definir un Grano de Cuenta Bancaria
Crea ICuentaGrain.cs
en Granos:
using System.Threading.Tasks;
using Orleans;
using Orleans.Transactions.Abstractions;
public interface ICuentaGrain : IGrainWithStringKey
{
Task Depositar(decimal monto);
Task Retirar(decimal monto);
Task<decimal> ObtenerSaldo();
}
2. Implementar el Grano con Soporte Transaccional
Crea CuentaGrain.cs
en Granos:
using System.Threading.Tasks;
using Orleans;
using Orleans.Transactions.Abstractions;
public class CuentaGrain : Grain, ICuentaGrain
{
private readonly ITransactionalState<CuentaState> _estado;
public CuentaGrain([TransactionalState("cuenta", "transactionStore")] ITransactionalState<CuentaState> estado)
{
_estado = estado;
}
public async Task Depositar(decimal monto)
{
await _estado.PerformUpdate(state => state.Saldo += monto);
}
public async Task Retirar(decimal monto)
{
await _estado.PerformUpdate(state =>
{
if (state.Saldo >= monto)
state.Saldo -= monto;
else
throw new System.Exception("Saldo insuficiente");
});
}
public Task<decimal> ObtenerSaldo()
{
return _estado.PerformRead(state => state.Saldo);
}
}
[GenerateSerializer]
public class CuentaState
{
[Id(0)]
public decimal Saldo { get; set; } = 0;
}
Explicación del Código
ITransactionalState<CuentaState>
: Mantiene el estado del Grano con soporte transaccional.PerformUpdate()
: Ejecuta cambios en el estado de forma segura.PerformRead()
: Recupera datos sin riesgo de inconsistencias.
Implementar una Transferencia entre Cuentas
1. Definir un Grano Coordinador
Crea ITransferenciaGrain.cs
en Granos:
using System.Threading.Tasks;
using Orleans;
public interface ITransferenciaGrain : IGrainWithStringKey
{
Task Transferir(string cuentaOrigen, string cuentaDestino, decimal monto);
}
2. Implementar el Grano Coordinador
Crea TransferenciaGrain.cs
en Granos:
using System.Threading.Tasks;
using Orleans;
using Orleans.Transactions.Abstractions;
public class TransferenciaGrain : Grain, ITransferenciaGrain
{
private readonly ITransactionCoordinator _coordinator;
public TransferenciaGrain(ITransactionCoordinator coordinator)
{
_coordinator = coordinator;
}
public async Task Transferir(string cuentaOrigen, string cuentaDestino, decimal monto)
{
var origen = GrainFactory.GetGrain<ICuentaGrain>(cuentaOrigen);
var destino = GrainFactory.GetGrain<ICuentaGrain>(cuentaDestino);
await _coordinator.RunTransaction(async () =>
{
await origen.Retirar(monto);
await destino.Depositar(monto);
});
}
}
Explicación del Código
RunTransaction(async () => {...})
: Garantiza que la transferencia sea atómica.- Si una operación falla, Orleans revierte los cambios automáticamente.
Probar Transacciones en Orleans
1. Ejecutar el Silo Orleans
Ejecuta en la terminal:
dotnet run
2. Probar la Transferencia con un Cliente Orleans
Edita Program.cs
en OrleansClient:
var cliente = new ClientBuilder()
.UseLocalhostClustering()
.Build();
await cliente.Connect();
var cuenta1 = cliente.GetGrain<ICuentaGrain>("cuenta1");
var cuenta2 = cliente.GetGrain<ICuentaGrain>("cuenta2");
await cuenta1.Depositar(100);
await cuenta2.Depositar(50);
Console.WriteLine($"Saldo cuenta 1: {await cuenta1.ObtenerSaldo()}");
Console.WriteLine($"Saldo cuenta 2: {await cuenta2.ObtenerSaldo()}");
var transferencia = cliente.GetGrain<ITransferenciaGrain>("transaccion1");
await transferencia.Transferir("cuenta1", "cuenta2", 30);
Console.WriteLine($"Saldo cuenta 1 después de transferir: {await cuenta1.ObtenerSaldo()}");
Console.WriteLine($"Saldo cuenta 2 después de recibir: {await cuenta2.ObtenerSaldo()}");
Salida esperada
Saldo cuenta 1: 100
Saldo cuenta 2: 50
Saldo cuenta 1 después de transferir: 70
Saldo cuenta 2 después de recibir: 80
Si se intenta transferir más dinero del saldo disponible, Orleans revertirá la operación automáticamente.
Cuestionario de Autoevaluación
- ¿Qué son las transacciones en Orleans y por qué son importantes?
- ¿Cómo Orleans garantiza que una transacción sea atómica?
- ¿Qué hace el método
RunTransaction()
en un Grano Orleans? - ¿Qué sucede si un Grano intenta modificar datos en una transacción pero otra operación falla?
- ¿En qué escenarios sería útil usar transacciones en Orleans?
Resumen de la Clase
- Orleans permite ejecutar transacciones distribuidas en Granos.
- Implementamos un sistema bancario simple con transacciones Orleans.
- Orleans garantiza la consistencia revirtiendo operaciones fallidas.
- Probamos transacciones con múltiples Granos y verificamos la consistencia de los datos.
Próxima Clase: Event Sourced Grains
En la siguiente clase, aprenderemos cómo usar Event Sourcing en Orleans para registrar eventos de cambios en los Granos y reconstruir su estado desde un historial de eventos.