namespace Library { using Automatonymous; using MassTransit; using MassTransit.EntityFrameworkCoreIntegration; using MassTransit.EntityFrameworkCoreIntegration.Mappings; using MassTransit.ExtensionsDependencyInjectionIntegration; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Threading.Tasks; public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(host => host.UseStartup<Startup>()); } public class Startup { public IConfiguration Configuration { get; } public Startup(IConfiguration configuration) => Configuration = configuration; public void ConfigureServices(IServiceCollection svcs) { svcs.AddDbContext<LibraryContext>(opts => opts .UseSqlServer(Configuration.GetConnectionString("Library"))); svcs.AddMassTransit(MassTransitConfigure); svcs.AddMassTransitHostedService(); svcs.AddControllers(); } public void Configure(IApplicationBuilder budr) { budr.UseRouting(); budr.UseEndpoints(rute => rute.MapControllers()); } public void MassTransitConfigure(IServiceCollectionBusConfigurator cfgr) { cfgr.AddSagaStateMachine<BookStateMachine, Book>() .EntityFrameworkRepository(cfg => cfg.ExistingDbContext<LibraryContext>()); cfgr.AddSagaStateMachine<ReserveStateMachine, Reserve>() .EntityFrameworkRepository(cfg => cfg.ExistingDbContext<LibraryContext>()); cfgr.UsingRabbitMq((ctx, cfg) => { cfg.Host("localhost", "/", hst => { hst.Username("guest"); hst.Password("guest"); }); cfg.ConfigureEndpoints(ctx); }); } } public class LibraryContext : SagaDbContext { public LibraryContext(DbContextOptions<LibraryContext> options) : base(options) { } public DbSet<Book> Books { get; set; } public DbSet<Reserve> Reserves { get; set; } protected override IEnumerable<ISagaClassMap> Configurations { get { yield return new BookMap(); yield return new ReserveMap(); } } } public class Book : SagaStateMachineInstance { public Guid CorrelationId { get; set; } public int State { get; set; } public DateTime Timestamp { get; set; } } public class BookMap : SagaClassMap<Book> { protected override void Configure(EntityTypeBuilder<Book> entity, ModelBuilder model) { base.Configure(entity, model); entity.ToTable(nameof(Book)); } } public class BookStateMachine : MassTransitStateMachine<Book> { public ILogger<BookStateMachine> Logger { get; } public BookStateMachine(ILogger<BookStateMachine> logger) { Logger = logger; InstanceState(ins => ins.State, Created, Reserved); Event(() => BookCreated, cfg => cfg.CorrelateById(ctx => ctx.Message.BookId)); Event(() => ReserveCreated, cfg => cfg.CorrelateById(ctx => ctx.Message.BookId)); Initially( When(BookCreated) .Then(ctx => { ctx.Instance.Timestamp = ctx.Data.Timestamp; }) .TransitionTo(Created)); During(Created, When(ReserveCreated) .TransitionTo(Reserved) .PublishAsync(ctx => ctx.Init<BookReserved>(new { ReserveId = ctx.Data.ReserveId, Timestamp = ctx.Data.Timestamp, BookId = ctx.Data.BookId // ctx.Instance.CorrelationId }))); } public State Created { get; } public State Reserved { get; } public Event<BookCreated> BookCreated { get; } public Event<ReserveCreated> ReserveCreated { get; } } public class BookController : ControllerBase { public ILogger<BookController> Logger { get; } public IPublishEndpoint PublishEndpoint { get; } public BookController( ILogger<BookController> logger, IPublishEndpoint publishEndpoint) { Logger = logger; PublishEndpoint = publishEndpoint; } [HttpGet, Route("api/books/create/{bookId}")] public async Task<IActionResult> CreateAsync(Guid bookId) { await PublishEndpoint.Publish<BookCreated>(new { BookId = bookId, Timestamp = InVar.Timestamp }); return Ok(bookId); } } public interface BookCreated { Guid BookId { get; } DateTime Timestamp { get; } } public interface ReserveCreated { Guid ReserveId { get; } DateTime Timestamp { get; } Guid BookId { get; } } public interface BookReserved { Guid ReserveId { get; } DateTime Timestamp { get; } Guid BookId { get; } } public class Reserve : SagaStateMachineInstance { public Guid CorrelationId { get; set; } public int State { get; set; } public DateTime Timestamp { get; set; } public Guid BookId { get; set; } } public class ReserveMap : SagaClassMap<Reserve> { protected override void Configure(EntityTypeBuilder<Reserve> entity, ModelBuilder model) { base.Configure(entity, model); entity.ToTable(nameof(Reserve)); } } public class ReserveStateMachine : MassTransitStateMachine<Reserve> { public ILogger<ReserveStateMachine> Logger { get; } public ReserveStateMachine(ILogger<ReserveStateMachine> logger) { Logger = logger; InstanceState(ins => ins.State, Created, Reserved); Event(() => ReserveCreated, cfg => cfg.CorrelateById(ctx => ctx.Message.ReserveId)); Event(() => BookReserved, cfg => cfg.CorrelateById(ctx => ctx.Message.ReserveId)); Initially( When(ReserveCreated) .Then(ctx => { ctx.Instance.Timestamp = ctx.Data.Timestamp; ctx.Instance.BookId = ctx.Data.BookId; }) .TransitionTo(Created)); During(Created, When(BookReserved) .TransitionTo(Reserved)); } public State Created { get; } public State Reserved { get; } public Event<ReserveCreated> ReserveCreated { get; } public Event<BookReserved> BookReserved { get; } } public class ReserveController : ControllerBase { public ILogger<ReserveController> Logger { get; } public IPublishEndpoint PublishEndpoint { get; } public ReserveController( ILogger<ReserveController> logger, IPublishEndpoint publishEndpoint) { Logger = logger; PublishEndpoint = publishEndpoint; } [HttpGet, Route("api/reserves/create/{reserveId}/{bookId}")] public async Task<IActionResult> Create(Guid reserveId, Guid bookId) { await PublishEndpoint.Publish<ReserveCreated>(new { ReserveId = reserveId, Timestamp = InVar.Timestamp, BookId = bookId }); return Ok(reserveId); } } }