From a0c8638a35972444d57dc7c2696abe4e37d025cf Mon Sep 17 00:00:00 2001 From: dingsongjie Date: Thu, 5 Mar 2020 18:09:17 +0800 Subject: [PATCH] 添加核心代码 并且 添加 核心代码的测试 --- samples/apis/SagasTest.Api/Activities/Transaction1OkActivity.cs | 39 +++++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Activities/Transaction1ReturnFalseActivity.cs | 38 ++++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Activities/Transaction2OkActivity.cs | 39 +++++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Activities/Transaction2ReturnFalseActivity.cs | 37 +++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Activities/Transaction3HasResultActivity.cs | 43 +++++++++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Activities/Transaction3ReturnFalseActivity.cs | 29 +++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Controllers/OutGoingMockController.cs | 39 +++++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Controllers/SagasTestController.cs | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Controllers/ValuesController.cs | 45 --------------------------------------------- samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/Backet.cs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/BacketItem.cs | 16 ++++++++++++++++ samples/apis/SagasTest.Api/Infrastructure/BacketDbContext.cs | 27 +++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketEntityTypeConfiguration.cs | 27 +++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketItemEntityTypeConfiguration.cs | 29 +++++++++++++++++++++++++++++ samples/apis/SagasTest.Api/Properties/launchSettings.json | 4 ++-- samples/apis/SagasTest.Api/SagasTest.Api.csproj | 7 ++----- samples/apis/SagasTest.Api/Startup.cs | 15 ++++++++++++++- samples/apis/SagasTest.Api/appsettings.json | 11 ++++++++++- src/Pole.Sagas/Core/Abstraction/IEventSender.cs | 2 +- src/Pole.Sagas/Core/ActivityCompensateResult.cs | 15 --------------- src/Pole.Sagas/Core/ActivityExecuteResult.cs | 7 ++++++- src/Pole.Sagas/Core/ActivityFinder.cs | 3 ++- src/Pole.Sagas/Core/ActivityWapper.cs | 5 +++-- src/Pole.Sagas/Core/EventSender.cs | 2 +- src/Pole.Sagas/Core/Exceptions/ActivityImplementIrregularException.cs | 14 ++++++++++++++ src/Pole.Sagas/Core/ISaga.cs | 2 +- src/Pole.Sagas/Core/Saga.cs | 30 ++++++++++++++++++++---------- src/Pole.Sagas/Core/SagaFactory.cs | 6 ++++-- src/Pole.Sagas/Core/SagasCollection.cs | 25 ------------------------- src/Pole.Sagas/PoleSagaServiceCollectionExtensions.cs | 24 ++++++++++++++++-------- 30 files changed, 598 insertions(+), 121 deletions(-) create mode 100644 samples/apis/SagasTest.Api/Activities/Transaction1OkActivity.cs create mode 100644 samples/apis/SagasTest.Api/Activities/Transaction1ReturnFalseActivity.cs create mode 100644 samples/apis/SagasTest.Api/Activities/Transaction2OkActivity.cs create mode 100644 samples/apis/SagasTest.Api/Activities/Transaction2ReturnFalseActivity.cs create mode 100644 samples/apis/SagasTest.Api/Activities/Transaction3HasResultActivity.cs create mode 100644 samples/apis/SagasTest.Api/Activities/Transaction3ReturnFalseActivity.cs create mode 100644 samples/apis/SagasTest.Api/Controllers/OutGoingMockController.cs create mode 100644 samples/apis/SagasTest.Api/Controllers/SagasTestController.cs delete mode 100644 samples/apis/SagasTest.Api/Controllers/ValuesController.cs create mode 100644 samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/Backet.cs create mode 100644 samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/BacketItem.cs create mode 100644 samples/apis/SagasTest.Api/Infrastructure/BacketDbContext.cs create mode 100644 samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketEntityTypeConfiguration.cs create mode 100644 samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketItemEntityTypeConfiguration.cs delete mode 100644 src/Pole.Sagas/Core/ActivityCompensateResult.cs create mode 100644 src/Pole.Sagas/Core/Exceptions/ActivityImplementIrregularException.cs delete mode 100644 src/Pole.Sagas/Core/SagasCollection.cs diff --git a/samples/apis/SagasTest.Api/Activities/Transaction1OkActivity.cs b/samples/apis/SagasTest.Api/Activities/Transaction1OkActivity.cs new file mode 100644 index 0000000..d701ac2 --- /dev/null +++ b/samples/apis/SagasTest.Api/Activities/Transaction1OkActivity.cs @@ -0,0 +1,39 @@ +using Pole.Sagas.Core; +using Pole.Sagas.Core.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace SagasTest.Api.Activities +{ + public class Transaction1OkActivity : IActivity + { + private readonly IHttpClientFactory httpClientFactory; + public Transaction1OkActivity(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + + } + public async Task Compensate(Transaction1Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction1RollBack"); + } + + public async Task Execute(Transaction1Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction1Ok"); + return ActivityExecuteResult.Success; + } + } + public class Transaction1Dto + { + public string Name { get; set; } + public int Id { get; set; } + } +} diff --git a/samples/apis/SagasTest.Api/Activities/Transaction1ReturnFalseActivity.cs b/samples/apis/SagasTest.Api/Activities/Transaction1ReturnFalseActivity.cs new file mode 100644 index 0000000..4758b41 --- /dev/null +++ b/samples/apis/SagasTest.Api/Activities/Transaction1ReturnFalseActivity.cs @@ -0,0 +1,38 @@ +using Pole.Sagas.Core; +using Pole.Sagas.Core.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace SagasTest.Api.Activities +{ + public class Transaction1ReturnFalseActivity : IActivity + { + private readonly IHttpClientFactory httpClientFactory; + public Transaction1ReturnFalseActivity(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + + } + public async Task Compensate(Transaction1Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction1RollBack"); + } + + public async Task Execute(Transaction1Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction1Ok"); + return new ActivityExecuteResult + { + IsSuccess = false, + Result = "库存不足" + }; + } + } +} diff --git a/samples/apis/SagasTest.Api/Activities/Transaction2OkActivity.cs b/samples/apis/SagasTest.Api/Activities/Transaction2OkActivity.cs new file mode 100644 index 0000000..90c24fd --- /dev/null +++ b/samples/apis/SagasTest.Api/Activities/Transaction2OkActivity.cs @@ -0,0 +1,39 @@ +using Pole.Sagas.Core; +using Pole.Sagas.Core.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace SagasTest.Api.Activities +{ + public class Transaction2OkActivity : IActivity + { + private readonly IHttpClientFactory httpClientFactory; + public Transaction2OkActivity(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + + } + public async Task Compensate(Transaction2Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction2RollBack"); + } + + public async Task Execute(Transaction2Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction2Ok"); + return ActivityExecuteResult.Success; + } + } + public class Transaction2Dto + { + public long Price { get; set; } + public string Message { get; set; } + } +} diff --git a/samples/apis/SagasTest.Api/Activities/Transaction2ReturnFalseActivity.cs b/samples/apis/SagasTest.Api/Activities/Transaction2ReturnFalseActivity.cs new file mode 100644 index 0000000..0e86cf7 --- /dev/null +++ b/samples/apis/SagasTest.Api/Activities/Transaction2ReturnFalseActivity.cs @@ -0,0 +1,37 @@ +using Pole.Sagas.Core; +using Pole.Sagas.Core.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace SagasTest.Api.Activities +{ + public class Transaction2ReturnFalseActivity : IActivity + { + private readonly IHttpClientFactory httpClientFactory; + public Transaction2ReturnFalseActivity(IHttpClientFactory httpClientFactory) + { + this.httpClientFactory = httpClientFactory; + + } + public async Task Compensate(Transaction2Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction2RollBack"); + } + + public async Task Execute(Transaction2Dto data) + { + var httpclient = httpClientFactory.CreateClient(); + httpclient.BaseAddress = new Uri("http://localhost:5000"); + var result = await httpclient.GetAsync("api/OutGoingMock/Transaction2Ok"); + return new ActivityExecuteResult { + IsSuccess=false, + Result="用户余额不足" + }; + } + } +} diff --git a/samples/apis/SagasTest.Api/Activities/Transaction3HasResultActivity.cs b/samples/apis/SagasTest.Api/Activities/Transaction3HasResultActivity.cs new file mode 100644 index 0000000..0ae948f --- /dev/null +++ b/samples/apis/SagasTest.Api/Activities/Transaction3HasResultActivity.cs @@ -0,0 +1,43 @@ +using Pole.Sagas.Core; +using Pole.Sagas.Core.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SagasTest.Api.Activities +{ + public class Transaction3HasResultActivity : IActivity + { + public Task Compensate(Transaction3Dto data) + { + Console.WriteLine("Transaction3 Rollback"); + return Task.CompletedTask; + } + + public Task Execute(Transaction3Dto data) + { + Console.WriteLine("Transaction3 commit"); + var result = new ActivityExecuteResult + { + IsSuccess = true, + Result = new Transaction3DtoResult + { + OrderId = 112, + UserName = "ccc" + } + }; + return Task.FromResult(result); + } + } + public class Transaction3Dto + { + public string Name { get; set; } + public int Age { get; set; } + } + public class Transaction3DtoResult + { + public int OrderId { get; set; } + public string UserName { get; set; } + } +} diff --git a/samples/apis/SagasTest.Api/Activities/Transaction3ReturnFalseActivity.cs b/samples/apis/SagasTest.Api/Activities/Transaction3ReturnFalseActivity.cs new file mode 100644 index 0000000..d5b7d4e --- /dev/null +++ b/samples/apis/SagasTest.Api/Activities/Transaction3ReturnFalseActivity.cs @@ -0,0 +1,29 @@ +using Pole.Sagas.Core; +using Pole.Sagas.Core.Abstraction; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SagasTest.Api.Activities +{ + public class Transaction3ReturnFalseActivity : IActivity + { + public Task Compensate(Transaction3Dto data) + { + Console.WriteLine("Transaction3 Rollback"); + return Task.CompletedTask; + } + + public Task Execute(Transaction3Dto data) + { + Console.WriteLine("Transaction3 commit"); + var result = new ActivityExecuteResult + { + IsSuccess = false, + Result = "创建订单失败" + }; + return Task.FromResult(result); + } + } +} diff --git a/samples/apis/SagasTest.Api/Controllers/OutGoingMockController.cs b/samples/apis/SagasTest.Api/Controllers/OutGoingMockController.cs new file mode 100644 index 0000000..83bf9cd --- /dev/null +++ b/samples/apis/SagasTest.Api/Controllers/OutGoingMockController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SagasTest.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class OutGoingMockController : ControllerBase + { + [HttpGet("Transaction1Ok")] + public Task Transaction1Ok() + { + return Task.FromResult("Transaction1 Ok"); + } + [HttpGet("Transaction1RollBack")] + public Task Transaction1RollBack() + { + return Task.FromResult("Transaction1 RollBack"); + } + [HttpGet("Transaction2Ok")] + public Task Transaction2Ok() + { + return Task.FromResult("Transaction1 Ok"); + } + [HttpGet("Transaction2RollBack")] + public Task Transaction2RollBack() + { + return Task.FromResult("Transaction2 RollBack"); + } + [HttpGet("Transaction3Ok")] + public Task Transaction3Ok() + { + return Task.FromResult("Transaction1 Ok"); + } + } +} diff --git a/samples/apis/SagasTest.Api/Controllers/SagasTestController.cs b/samples/apis/SagasTest.Api/Controllers/SagasTestController.cs new file mode 100644 index 0000000..0040a1b --- /dev/null +++ b/samples/apis/SagasTest.Api/Controllers/SagasTestController.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Pole.Sagas.Core.Abstraction; +using SagasTest.Api.Activities; + +namespace SagasTest.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class SagasTestController : ControllerBase + { + private readonly ISagaFactory sagaFactory; + public SagasTestController(ISagaFactory sagaFactory) + { + this.sagaFactory = sagaFactory; + } + // GET api/values + [HttpGet("NormalCall")] + public async Task NormalCall() + { + var sagas = sagaFactory.CreateSaga(); + + sagas.AddActivity("Transaction1Ok", new Transaction1Dto { Id = 1, Name = "22" }); + sagas.AddActivity("Transaction2Ok", new Transaction2Dto { Price = 1, Message = "我们" }); + sagas.AddActivity("Transaction3HasResult", new Transaction3Dto { Age = 1, Name = "333" }); + + var result = await sagas.GetResult(); + } + [HttpGet("Transaction3ReturnFalse")] + public async Task Transaction3ReturnFalse() + { + var sagas = sagaFactory.CreateSaga(); + + sagas.AddActivity("Transaction1Ok", new Transaction1Dto { Id = 1, Name = "22" }); + sagas.AddActivity("Transaction2Ok", new Transaction2Dto { Price = 1, Message = "我们" }); + sagas.AddActivity("Transaction3ReturnFalse", new Transaction3Dto { Age = 1, Name = "333" }); + + var result = await sagas.GetResult(); + } + [HttpGet("Transaction2ReturnFalse")] + public async Task Transaction2ReturnFalse() + { + var sagas = sagaFactory.CreateSaga(); + + sagas.AddActivity("Transaction1Ok", new Transaction1Dto { Id = 1, Name = "22" }); + sagas.AddActivity("Transaction2ReturnFalse", new Transaction2Dto { Price = 1, Message = "我们" }); + sagas.AddActivity("Transaction3HasResult", new Transaction3Dto { Age = 1, Name = "333" }); + + var result = await sagas.GetResult(); + } + [HttpGet("Transaction1ReturnFalse")] + public async Task Transaction1ReturnFalse() + { + var sagas = sagaFactory.CreateSaga(); + + sagas.AddActivity("Transaction1ReturnFalse", new Transaction1Dto { Id = 1, Name = "22" }); + sagas.AddActivity("Transaction2Ok", new Transaction2Dto { Price = 1, Message = "我们" }); + sagas.AddActivity("Transaction3HasResult", new Transaction3Dto { Age = 1, Name = "333" }); + + var result = await sagas.GetResult(); + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/samples/apis/SagasTest.Api/Controllers/ValuesController.cs b/samples/apis/SagasTest.Api/Controllers/ValuesController.cs deleted file mode 100644 index 0e31cf8..0000000 --- a/samples/apis/SagasTest.Api/Controllers/ValuesController.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; - -namespace SagasTest.Api.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class ValuesController : ControllerBase - { - // GET api/values - [HttpGet] - public ActionResult> Get() - { - return new string[] { "value1", "value2" }; - } - - // GET api/values/5 - [HttpGet("{id}")] - public ActionResult Get(int id) - { - return "value"; - } - - // POST api/values - [HttpPost] - public void Post([FromBody] string value) - { - } - - // PUT api/values/5 - [HttpPut("{id}")] - public void Put(int id, [FromBody] string value) - { - } - - // DELETE api/values/5 - [HttpDelete("{id}")] - public void Delete(int id) - { - } - } -} diff --git a/samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/Backet.cs b/samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/Backet.cs new file mode 100644 index 0000000..b6596a6 --- /dev/null +++ b/samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/Backet.cs @@ -0,0 +1,48 @@ +using Pole.Core.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Backet.Api.Domain.AggregatesModel.BacketAggregate +{ + public class Backet : Entity, IAggregateRoot + { + public void AddBacketItem(string productId, string productName, long Price) + { + BacketItem backetItem = new BacketItem() + { + Id = Guid.NewGuid().ToString("N"), + Price = Price, + ProductId = productId, + ProductName = productName + }; + BacketItems.Add(backetItem); + SetBacketTotalPrice(); + } + public void ModifyItemProductId(string productId) + { + BacketItems.ForEach(m => m.ProductId = productId); + } + private void SetBacketTotalPrice() + { + foreach (var item in BacketItems) + { + TotalPrice = BacketItems.Sum(m=>m.Price); + } + } + public string UserId { get; set; } + public List BacketItems { get; private set; } = new List(); + public long TotalPrice { get; set; } + + internal void RemoveFirstItem() + { + var first = BacketItems.FirstOrDefault(); + if (first != null) + { + BacketItems.Remove(first); + SetBacketTotalPrice(); + } + } + } +} diff --git a/samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/BacketItem.cs b/samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/BacketItem.cs new file mode 100644 index 0000000..d86a6fb --- /dev/null +++ b/samples/apis/SagasTest.Api/Domain/AggregatesModels/BacketAggregate/BacketItem.cs @@ -0,0 +1,16 @@ +using Pole.Core.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Backet.Api.Domain.AggregatesModel.BacketAggregate +{ + public class BacketItem : Entity + { + public string ProductId { get; set; } + public string ProductName { get; set; } + public long Price { get; set; } + public string BacketId { get; set; } + } +} diff --git a/samples/apis/SagasTest.Api/Infrastructure/BacketDbContext.cs b/samples/apis/SagasTest.Api/Infrastructure/BacketDbContext.cs new file mode 100644 index 0000000..c492348 --- /dev/null +++ b/samples/apis/SagasTest.Api/Infrastructure/BacketDbContext.cs @@ -0,0 +1,27 @@ +using Backet.Api.Domain.AggregatesModel.BacketAggregate; +using Backet.Api.Infrastructure.EntityConfigurations; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Backet.Api.Infrastructure +{ + public class BacketDbContext : DbContext + { + public BacketDbContext(DbContextOptions options) : base(options) + { + + } + public DbSet Backets { get; set; } + public DbSet BacketItems { get; set; } + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.ApplyConfiguration(new BacketItemEntityTypeConfiguration()); + builder.ApplyConfiguration(new BacketEntityTypeConfiguration()); + } + } +} diff --git a/samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketEntityTypeConfiguration.cs b/samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketEntityTypeConfiguration.cs new file mode 100644 index 0000000..8db1f30 --- /dev/null +++ b/samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketEntityTypeConfiguration.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Backet.Api.Infrastructure.EntityConfigurations +{ + public class BacketEntityTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(Backet.Api.Domain.AggregatesModel.BacketAggregate.Backet)); + + builder.Property(m => m.Id).HasMaxLength(32); + builder.Property(m => m.UserId).HasMaxLength(32).IsRequired(); + builder.HasMany(m => m.BacketItems).WithOne().IsRequired().OnDelete(DeleteBehavior.Cascade).HasForeignKey("BacketId"); + + builder.Ignore(m => m.DomainEvents); + builder.Ignore(m => m.IsPersisted); + + builder.HasKey(m => m.Id); + builder.HasIndex(m => m.UserId); + } + } +} diff --git a/samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketItemEntityTypeConfiguration.cs b/samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketItemEntityTypeConfiguration.cs new file mode 100644 index 0000000..0cce173 --- /dev/null +++ b/samples/apis/SagasTest.Api/Infrastructure/EntityConfigurations/BacketItemEntityTypeConfiguration.cs @@ -0,0 +1,29 @@ +using Backet.Api.Domain.AggregatesModel.BacketAggregate; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Backet.Api.Infrastructure.EntityConfigurations +{ + public class BacketItemEntityTypeConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable(nameof(BacketItem)); + + builder.Property(m => m.Id).HasMaxLength(32); + builder.Property(m => m.ProductId).HasMaxLength(32); + builder.Property(m => m.ProductName).HasMaxLength(256).IsRequired(); + builder.Property(m => m.BacketId).HasMaxLength(32).IsRequired(); + + builder.Ignore(m => m.DomainEvents); + builder.Ignore(m => m.IsPersisted); + + builder.HasKey(m => m.Id); + builder.HasIndex(m => m.ProductId); + } + } +} diff --git a/samples/apis/SagasTest.Api/Properties/launchSettings.json b/samples/apis/SagasTest.Api/Properties/launchSettings.json index 926fbc1..62d280c 100644 --- a/samples/apis/SagasTest.Api/Properties/launchSettings.json +++ b/samples/apis/SagasTest.Api/Properties/launchSettings.json @@ -19,9 +19,9 @@ }, "SagasTest.Api": { "commandName": "Project", - "launchBrowser": true, + "launchBrowser": false, "launchUrl": "api/values", - "applicationUrl": "https://localhost:5001;http://localhost:5000", + "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/samples/apis/SagasTest.Api/SagasTest.Api.csproj b/samples/apis/SagasTest.Api/SagasTest.Api.csproj index 6b5a7cc..4c62276 100644 --- a/samples/apis/SagasTest.Api/SagasTest.Api.csproj +++ b/samples/apis/SagasTest.Api/SagasTest.Api.csproj @@ -6,12 +6,9 @@ - - - - - + + diff --git a/samples/apis/SagasTest.Api/Startup.cs b/samples/apis/SagasTest.Api/Startup.cs index 1c1142f..c51f30a 100644 --- a/samples/apis/SagasTest.Api/Startup.cs +++ b/samples/apis/SagasTest.Api/Startup.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Backet.Api.Infrastructure; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -27,11 +29,22 @@ namespace SagasTest.Api // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddDbContextPool(options => options.UseNpgsql(Configuration["postgres:write"])); services.AddControllers(); services.AddPole(config => { - config.AddSagas(); + config.AddRabbitMQ(option => + { + option.Hosts = new string[1] { Configuration["RabbitmqConfig:HostAddress"] }; + option.Password = Configuration["RabbitmqConfig:HostPassword"]; + option.UserName = Configuration["RabbitmqConfig:HostUserName"]; + }); + config.AddEntityFrameworkEventStorage(); + config.AddSagas(option=> { + option.ServiceName = "SagasTest"; + }); }); + services.AddHttpClient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/samples/apis/SagasTest.Api/appsettings.json b/samples/apis/SagasTest.Api/appsettings.json index def9159..5c2011a 100644 --- a/samples/apis/SagasTest.Api/appsettings.json +++ b/samples/apis/SagasTest.Api/appsettings.json @@ -4,5 +4,14 @@ "Default": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "postgres": { + "write": "Server=192.168.0.248;Port=5432;Username=postgres;Password=comteck2020!@#;Database=Pole-Backet;Enlist=True;Timeout=0;Command Timeout=600;MinPoolSize=20;MaxPoolSize=500;" + }, + "ServiceName": "Backet", + "RabbitmqConfig": { + "HostAddress": "192.168.0.248", + "HostUserName": "comteck", + "HostPassword": "comteck3030" + } } diff --git a/src/Pole.Sagas/Core/Abstraction/IEventSender.cs b/src/Pole.Sagas/Core/Abstraction/IEventSender.cs index 85b9a70..7aaf7c8 100644 --- a/src/Pole.Sagas/Core/Abstraction/IEventSender.cs +++ b/src/Pole.Sagas/Core/Abstraction/IEventSender.cs @@ -7,7 +7,7 @@ namespace Pole.Sagas.Core.Abstraction { Task SagaStarted(string sagaId, string serviceName); Task SagaEnded(string sagaId,DateTime ExpiresAt); - Task ActivityStarted(string activityId, string sagaId, DateTime activityTimeoutTime, string parameterContent); + Task ActivityExecuteStarted(string activityId, string sagaId, DateTime activityTimeoutTime, string parameterContent,int order); Task ActivityRetried(string activityId, string status, int retries,string resultContent); Task ActivityExecuteAborted(string activityId,string resultContent,string errors); Task ActivityCompensateAborted(string activityId,string sagaId,string errors); diff --git a/src/Pole.Sagas/Core/ActivityCompensateResult.cs b/src/Pole.Sagas/Core/ActivityCompensateResult.cs deleted file mode 100644 index 611b60c..0000000 --- a/src/Pole.Sagas/Core/ActivityCompensateResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Pole.Sagas.Core -{ - public class ActivityCompensateResult - { - /// - /// If not success , this activity will be aborted , and current saga will compensate all previous activities - /// - public bool IsSuccess { get; set; } - public string Message { get; set; } - } -} diff --git a/src/Pole.Sagas/Core/ActivityExecuteResult.cs b/src/Pole.Sagas/Core/ActivityExecuteResult.cs index e758284..66ca394 100644 --- a/src/Pole.Sagas/Core/ActivityExecuteResult.cs +++ b/src/Pole.Sagas/Core/ActivityExecuteResult.cs @@ -9,16 +9,21 @@ namespace Pole.Sagas.Core public bool IsSuccess { get; set; } public object Result { get; set; } public string Errors { get; set; } + public static ActivityExecuteResult Success = new ActivityExecuteResult + { + IsSuccess = true + }; public static implicit operator SagaResult(ActivityExecuteResult activity) { return new SagaResult { IsSuccess = activity.IsSuccess, - Result = default(object), + Result = activity.Result, HasException = !string.IsNullOrEmpty(activity.Errors), ExceptionMessages = activity.Errors }; } + } } diff --git a/src/Pole.Sagas/Core/ActivityFinder.cs b/src/Pole.Sagas/Core/ActivityFinder.cs index 5ed6380..3e17b21 100644 --- a/src/Pole.Sagas/Core/ActivityFinder.cs +++ b/src/Pole.Sagas/Core/ActivityFinder.cs @@ -21,7 +21,8 @@ namespace Pole.Sagas.Core var baseActivityType = typeof(IActivity<>); foreach (var assembly in AssemblyHelper.GetAssemblies(this.logger)) { - foreach (var type in assembly.GetTypes().Where(m => m.IsGenericType && m.GetGenericTypeDefinition() == baseActivityType && !m.IsAbstract)) + + foreach (var type in assembly.GetTypes().Where(m => m.GetInterfaces().Any(i=>i.IsGenericType&& i.GetGenericTypeDefinition() == baseActivityType)&&m.IsClass&&!m.IsAbstract)) { if (!type.FullName.EndsWith("Activity")) { diff --git a/src/Pole.Sagas/Core/ActivityWapper.cs b/src/Pole.Sagas/Core/ActivityWapper.cs index c0fc712..a3dbbab 100644 --- a/src/Pole.Sagas/Core/ActivityWapper.cs +++ b/src/Pole.Sagas/Core/ActivityWapper.cs @@ -24,7 +24,7 @@ namespace Pole.Sagas.Core var dataObjParams = Expression.Parameter(typeof(object), "data"); var dataParams = Expression.Convert(dataObjParams, ActivityDataType); var method = ActivityType.GetMethod("Execute", new Type[] { ActivityDataType }); - var body = Expression.Call(activityObjParams, method, activityObjParams, dataParams); + var body = Expression.Call(activityParams, method, dataParams); var func = Expression.Lambda>>(body, activityObjParams, dataObjParams).Compile(); using (var scope = ServiceProvider.CreateScope()) @@ -40,8 +40,9 @@ namespace Pole.Sagas.Core var dataObjParams = Expression.Parameter(typeof(object), "data"); var dataParams = Expression.Convert(dataObjParams, ActivityDataType); var method = ActivityType.GetMethod("Compensate", new Type[] { ActivityDataType }); - var body = Expression.Call(activityObjParams, method, activityObjParams, dataParams); + var body = Expression.Call(activityParams, method, dataParams); var func = Expression.Lambda>(body, activityObjParams, dataObjParams).Compile(); + using (var scope = ServiceProvider.CreateScope()) { var activity = scope.ServiceProvider.GetRequiredService(ActivityType); diff --git a/src/Pole.Sagas/Core/EventSender.cs b/src/Pole.Sagas/Core/EventSender.cs index 6a6d401..f81602c 100644 --- a/src/Pole.Sagas/Core/EventSender.cs +++ b/src/Pole.Sagas/Core/EventSender.cs @@ -33,7 +33,7 @@ namespace Pole.Sagas.Core return Task.CompletedTask; } - public Task ActivityStarted(string activityId, string sagaId, DateTime activityTimeoutTime, string parameterContent) + public Task ActivityExecuteStarted(string activityId, string sagaId, DateTime activityTimeoutTime, string parameterContent, int order) { return Task.CompletedTask; } diff --git a/src/Pole.Sagas/Core/Exceptions/ActivityImplementIrregularException.cs b/src/Pole.Sagas/Core/Exceptions/ActivityImplementIrregularException.cs new file mode 100644 index 0000000..abc428b --- /dev/null +++ b/src/Pole.Sagas/Core/Exceptions/ActivityImplementIrregularException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Pole.Sagas.Core.Exceptions +{ + class ActivityImplementIrregularException: Exception + { + public ActivityImplementIrregularException(string name) : base($"Activity name :{name }must have and only inherit from IActivity<>") + { + + } + } +} diff --git a/src/Pole.Sagas/Core/ISaga.cs b/src/Pole.Sagas/Core/ISaga.cs index eac3fa1..47fd2ba 100644 --- a/src/Pole.Sagas/Core/ISaga.cs +++ b/src/Pole.Sagas/Core/ISaga.cs @@ -8,7 +8,7 @@ namespace Pole.Sagas.Core public interface ISaga { string Id { get; } - void AddActivity(string activityName, TData data); + void AddActivity(string activityName, object data); Task GetResult(); } } diff --git a/src/Pole.Sagas/Core/Saga.cs b/src/Pole.Sagas/Core/Saga.cs index 31f3d2e..72213c9 100644 --- a/src/Pole.Sagas/Core/Saga.cs +++ b/src/Pole.Sagas/Core/Saga.cs @@ -1,8 +1,10 @@ using Pole.Core.Serialization; using Pole.Core.Utils.Abstraction; +using Pole.Sagas.Core.Abstraction; using Pole.Sagas.Core.Exceptions; using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; @@ -14,6 +16,7 @@ namespace Pole.Sagas.Core private IServiceProvider serviceProvider; private IEventSender eventSender; private ISnowflakeIdGenerator snowflakeIdGenerator; + private IActivityFinder activityFinder; private PoleSagasOption poleSagasOption; private int _currentMaxOrder = 0; private int _currentExecuteOrder = 0; @@ -21,24 +24,33 @@ namespace Pole.Sagas.Core private ISerializer serializer; public string Id { get; } - public Saga(ISnowflakeIdGenerator snowflakeIdGenerator, IServiceProvider serviceProvider, IEventSender eventSender, PoleSagasOption poleSagasOption, ISerializer serializer) + public Saga(ISnowflakeIdGenerator snowflakeIdGenerator, IServiceProvider serviceProvider, IEventSender eventSender, PoleSagasOption poleSagasOption, ISerializer serializer, IActivityFinder activityFinder) { this.snowflakeIdGenerator = snowflakeIdGenerator; this.serviceProvider = serviceProvider; this.eventSender = eventSender; this.poleSagasOption = poleSagasOption; this.serializer = serializer; + this.activityFinder = activityFinder; Id = snowflakeIdGenerator.NextId(); } - public void AddActivity(string activityName, TData data) + public void AddActivity(string activityName, object data) { + var targetActivityType = activityFinder.FindType(activityName); + + var activityInterface = targetActivityType.GetInterfaces().FirstOrDefault(); + if (!activityInterface.IsGenericType) + { + throw new ActivityImplementIrregularException(activityName); + } + var dataType = activityInterface.GetGenericArguments()[0]; _currentMaxOrder++; ActivityWapper activityWapper = new ActivityWapper { - ActivityDataType = typeof(TData), + ActivityDataType = dataType, ActivityState = ActivityStatus.NotStarted, - ActivityType = data.GetType(), + ActivityType = targetActivityType, DataObj = data, Order = _currentMaxOrder, ServiceProvider = serviceProvider @@ -68,25 +80,23 @@ namespace Pole.Sagas.Core return null; } _currentExecuteOrder++; - return activities[_currentExecuteOrder]; + return activities[_currentExecuteOrder-1]; } private ActivityWapper GetNextCompensateActivity() { _currentCompensateOrder--; - if (_currentExecuteOrder == 0) + if (_currentCompensateOrder == 0) { return null; } - return activities[_currentCompensateOrder]; + return activities[_currentCompensateOrder-1]; } private async Task RecursiveCompensateActivity(ActivityWapper activityWapper) { var activityId = activityWapper.Id; try { - //var jsonContent = serializer.Serialize(activityWapper.DataObj, activityWapper.ActivityDataType); - //await eventSender.ActivityStarted(activityId, Id, activityWapper.TimeOut, jsonContent); await activityWapper.InvokeCompensate(); await eventSender.ActivityCompensated(activityId); var compensateActivity = GetNextCompensateActivity(); @@ -108,7 +118,7 @@ namespace Pole.Sagas.Core try { var jsonContent = serializer.Serialize(activityWapper.DataObj, activityWapper.ActivityDataType); - await eventSender.ActivityStarted(activityId, Id, activityWapper.TimeOut, jsonContent); + await eventSender.ActivityExecuteStarted(activityId, Id, activityWapper.TimeOut, jsonContent, activityWapper.Order); var result = await activityWapper.InvokeExecute(); if (!result.IsSuccess) { diff --git a/src/Pole.Sagas/Core/SagaFactory.cs b/src/Pole.Sagas/Core/SagaFactory.cs index 54fc460..ab6585c 100644 --- a/src/Pole.Sagas/Core/SagaFactory.cs +++ b/src/Pole.Sagas/Core/SagaFactory.cs @@ -15,18 +15,20 @@ namespace Pole.Sagas.Core private readonly IEventSender eventSender; private readonly PoleSagasOption poleSagasOption; private readonly ISerializer serializer; - public SagaFactory(ISnowflakeIdGenerator snowflakeIdGenerator, IServiceProvider serviceProvider, IEventSender eventSender, IOptions poleSagasOption, ISerializer serializer) + private readonly IActivityFinder activityFinder; + public SagaFactory(ISnowflakeIdGenerator snowflakeIdGenerator, IServiceProvider serviceProvider, IEventSender eventSender, IOptions poleSagasOption, ISerializer serializer, IActivityFinder activityFinder) { this.snowflakeIdGenerator = snowflakeIdGenerator; this.serviceProvider = serviceProvider; this.eventSender = eventSender; this.poleSagasOption = poleSagasOption.Value; this.serializer = serializer; + this.activityFinder = activityFinder; } public ISaga CreateSaga() { - return new Saga(snowflakeIdGenerator, serviceProvider, eventSender, poleSagasOption, serializer); + return new Saga(snowflakeIdGenerator, serviceProvider, eventSender, poleSagasOption, serializer, activityFinder); } } } diff --git a/src/Pole.Sagas/Core/SagasCollection.cs b/src/Pole.Sagas/Core/SagasCollection.cs deleted file mode 100644 index 70b5aad..0000000 --- a/src/Pole.Sagas/Core/SagasCollection.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Pole.Sagas.Core.Exceptions; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Pole.Sagas.Core -{ - public class SagasCollection : Dictionary - { - private static System.Collections.Concurrent.ConcurrentDictionary _sagas = new System.Collections.Concurrent.ConcurrentDictionary(); - public static ISaga Get(string name) - { - if (!_sagas.TryGetValue(name, out ISaga saga)) - { - throw new SagaNotFoundException(name); - } - return saga; - } - public static bool Add(ISaga saga) - { - var name = saga.GetType().FullName; - return _sagas.TryAdd(name, saga); - } - } -} diff --git a/src/Pole.Sagas/PoleSagaServiceCollectionExtensions.cs b/src/Pole.Sagas/PoleSagaServiceCollectionExtensions.cs index 26f2fb1..f543dc4 100644 --- a/src/Pole.Sagas/PoleSagaServiceCollectionExtensions.cs +++ b/src/Pole.Sagas/PoleSagaServiceCollectionExtensions.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using Microsoft.Extensions.DependencyInjection; using Pole.Core; +using Pole.Core.Utils; using Pole.Sagas.Core; using Pole.Sagas.Core.Abstraction; +using Pole.Sagas.Core.Exceptions; namespace Microsoft.Extensions.DependencyInjection { @@ -16,14 +19,19 @@ namespace Microsoft.Extensions.DependencyInjection startupOption.Services.AddSingleton(); startupOption.Services.AddSingleton(); startupOption.Services.AddSingleton(); - } - public static void AddSagas(this StartupConfig startupOption) - { - Action action = option => { }; - startupOption.Services.Configure(action); - startupOption.Services.AddSingleton(); - startupOption.Services.AddSingleton(); - startupOption.Services.AddSingleton(); + var baseActivityType = typeof(IActivity<>); + foreach (var assembly in AssemblyHelper.GetAssemblies()) + { + + foreach (var type in assembly.GetTypes().Where(m => m.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == baseActivityType) && m.IsClass && !m.IsAbstract)) + { + if (!type.FullName.EndsWith("Activity")) + { + throw new ActivityNameIrregularException(type); + } + startupOption.Services.AddScoped(type); + } + } } } } -- libgit2 0.25.0