Skip to content

Instantly share code, notes, and snippets.

@havarnov
Created January 9, 2025 10:19
Show Gist options
  • Save havarnov/bcac50eedcab04b5dfac50ecb082b919 to your computer and use it in GitHub Desktop.
Save havarnov/bcac50eedcab04b5dfac50ecb082b919 to your computer and use it in GitHub Desktop.
A minimal example of how nested transaction works in Orleans.
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Orleans.Concurrency;
using Orleans.Transactions.Abstractions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOrleans(siloBuilder =>
{
siloBuilder.UseTransactions();
siloBuilder.UseLocalhostClustering();
siloBuilder.AddMemoryGrainStorageAsDefault();
});
var app = builder.Build();
app.MapGet("/", async ([FromServices] IClusterClient client, [FromQuery, Required]string value) =>
{
var a = client.GetGrain<IGrainA>("A");
var b = client.GetGrain<IGrainB>("A");
await a.SetValue(value);
return new { A = await a.GetValue(), B = await b.GetValue() };
});
app.MapGet("/fetch", async ([FromServices] IClusterClient client) =>
{
var a = client.GetGrain<IGrainA>("A");
var b = client.GetGrain<IGrainB>("A");
return new { A = await a.GetValue(), B = await b.GetValue() };
});
app.Run();
internal interface IGrainA : IGrainWithStringKey
{
[Transaction(TransactionOption.CreateOrJoin)]
public Task<string?> GetValue();
[Transaction(TransactionOption.CreateOrJoin)]
public Task SetValue(string value);
}
internal interface IGrainB : IGrainWithStringKey
{
[Transaction(TransactionOption.CreateOrJoin)]
public Task<string?> GetValue();
[Transaction(TransactionOption.Join)]
public Task SetValue(string value);
}
[GenerateSerializer]
internal class State
{
public string? Value { get; set; }
}
[Reentrant]
internal class GrainA([TransactionalState("state")] ITransactionalState<State> state)
: Grain, IGrainA
{
public async Task<string?> GetValue()
{
return await state.PerformRead(s => s.Value);
}
public async Task SetValue(string value)
{
await state.PerformUpdate(s => s.Value = value);
await GrainFactory.GetGrain<IGrainB>(this.GetPrimaryKeyString()).SetValue(value);
}
}
[Reentrant]
internal class GrainB([TransactionalState("state")] ITransactionalState<State> state)
: Grain, IGrainB
{
public async Task<string?> GetValue()
{
return await state.PerformRead(s => s.Value);
}
public async Task SetValue(string value)
{
await state.PerformUpdate(s =>
{
if (value == "foobar")
{
throw new InvalidOperationException("'foobar' is not allowed");
}
return s.Value = value;
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment