diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 42df38e020..6ea15635e9 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -387,6 +387,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.IdentityModel.Test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Threading.Tests", "test\Volo.Abp.Threading.Tests\Volo.Abp.Threading.Tests.csproj", "{7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EventBus.Boxes", "src\Volo.Abp.EventBus.Boxes\Volo.Abp.EventBus.Boxes.csproj", "{6E289F31-7924-418B-9DAC-62A7CFADF916}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.DistributedLocking", "src\Volo.Abp.DistributedLocking\Volo.Abp.DistributedLocking.csproj", "{9A7EEA08-15BE-476D-8168-53039867038E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Auditing.Contracts", "src\Volo.Abp.Auditing.Contracts\Volo.Abp.Auditing.Contracts.csproj", "{508B6355-AD28-4E60-8549-266D21DBF2CF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client.Web", "src\Volo.Abp.Http.Client.Web\Volo.Abp.Http.Client.Web.csproj", "{F7407459-8AFA-45E4-83E9-9BB01412CC08}" @@ -1157,6 +1160,14 @@ Global {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B}.Release|Any CPU.Build.0 = Release|Any CPU + {6E289F31-7924-418B-9DAC-62A7CFADF916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E289F31-7924-418B-9DAC-62A7CFADF916}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E289F31-7924-418B-9DAC-62A7CFADF916}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E289F31-7924-418B-9DAC-62A7CFADF916}.Release|Any CPU.Build.0 = Release|Any CPU + {9A7EEA08-15BE-476D-8168-53039867038E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A7EEA08-15BE-476D-8168-53039867038E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A7EEA08-15BE-476D-8168-53039867038E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A7EEA08-15BE-476D-8168-53039867038E}.Release|Any CPU.Build.0 = Release|Any CPU {508B6355-AD28-4E60-8549-266D21DBF2CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {508B6355-AD28-4E60-8549-266D21DBF2CF}.Debug|Any CPU.Build.0 = Debug|Any CPU {508B6355-AD28-4E60-8549-266D21DBF2CF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1360,6 +1371,8 @@ Global {90B1866A-EF99-40B9-970E-B898E5AA523F} = {447C8A77-E5F0-4538-8687-7383196D04EA} {40C6740E-BFCA-4D37-8344-3D84E2044BB2} = {447C8A77-E5F0-4538-8687-7383196D04EA} {7B2FCAD6-86E6-49C8-ADBE-A61B4F4B101B} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {6E289F31-7924-418B-9DAC-62A7CFADF916} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {9A7EEA08-15BE-476D-8168-53039867038E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {508B6355-AD28-4E60-8549-266D21DBF2CF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {F7407459-8AFA-45E4-83E9-9BB01412CC08} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection diff --git a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs index cea8a1eee6..dccf2006dc 100644 --- a/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs +++ b/framework/src/Volo.Abp.BackgroundJobs/Volo/Abp/BackgroundJobs/AbpBackgroundJobsModule.cs @@ -20,12 +20,7 @@ namespace Volo.Abp.BackgroundJobs var options = context.ServiceProvider.GetRequiredService>().Value; if (options.IsJobExecutionEnabled) { - context.ServiceProvider - .GetRequiredService() - .Add( - context.ServiceProvider - .GetRequiredService() - ); + context.AddBackgroundWorker(); } } } diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj index 87879334af..68c90489ff 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj @@ -17,7 +17,7 @@ - + diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs index 523a6ee4f2..38e2d9fc84 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs @@ -3,6 +3,7 @@ using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.Domain.Repositories; using Volo.Abp.EventBus; +using Volo.Abp.EventBus.Boxes; using Volo.Abp.ExceptionHandling; using Volo.Abp.Guids; using Volo.Abp.Modularity; @@ -18,7 +19,7 @@ namespace Volo.Abp.Domain [DependsOn( typeof(AbpAuditingModule), typeof(AbpDataModule), - typeof(AbpEventBusModule), + typeof(AbpEventBusBoxesModule), typeof(AbpGuidsModule), typeof(AbpMultiTenancyModule), typeof(AbpThreadingModule), diff --git a/framework/src/Volo.Abp.DistributedLocking/FodyWeavers.xml b/framework/src/Volo.Abp.DistributedLocking/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.DistributedLocking/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.DistributedLocking/FodyWeavers.xsd b/framework/src/Volo.Abp.DistributedLocking/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.DistributedLocking/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.DistributedLocking/Volo.Abp.DistributedLocking.csproj b/framework/src/Volo.Abp.DistributedLocking/Volo.Abp.DistributedLocking.csproj new file mode 100644 index 0000000000..c4b590412d --- /dev/null +++ b/framework/src/Volo.Abp.DistributedLocking/Volo.Abp.DistributedLocking.csproj @@ -0,0 +1,25 @@ + + + + + + + netstandard2.0 + Volo.Abp.DistributedLocking + Volo.Abp.DistributedLocking + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/AbpDistributedLockingModule.cs b/framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/AbpDistributedLockingModule.cs new file mode 100644 index 0000000000..877ac54244 --- /dev/null +++ b/framework/src/Volo.Abp.DistributedLocking/Volo/Abp/DistributedLocking/AbpDistributedLockingModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.DistributedLocking +{ + public class AbpDistributedLockingModule : AbpModule + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/DistributedEvents/MySQLInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/DistributedEvents/MySQLInboxConfigExtensions.cs new file mode 100644 index 0000000000..ecda92702b --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/DistributedEvents/MySQLInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class MySQLInboxConfigExtensions + { + public static void UseMySQL(this InboxConfig outboxConfig) + where TDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(ISqlRawDbContextEventInbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/DistributedEvents/MySQLOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/DistributedEvents/MySQLOutboxConfigExtensions.cs new file mode 100644 index 0000000000..8657ba92ab --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/DistributedEvents/MySQLOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class MySQLOutboxConfigExtensions + { + public static void UseMySQL(this OutboxConfig outboxConfig) + where TDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(ISqlRawDbContextEventOutbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventInbox.cs new file mode 100644 index 0000000000..0cc7ae5531 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventInbox.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IOracleDbContextEventInbox : IDbContextEventInbox + where TDbContext : IHasEventInbox + { + + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventOutbox.cs new file mode 100644 index 0000000000..a588f36e43 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventOutbox.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IOracleDbContextEventOutbox : IDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventInbox.cs new file mode 100644 index 0000000000..c19ca6746a --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventInbox.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Volo.Abp.EventBus.Boxes; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class OracleDbContextEventInbox : DbContextEventInbox , IOracleDbContextEventInbox + where TDbContext : IHasEventInbox + { + public OracleDbContextEventInbox( + IDbContextProvider dbContextProvider, + IClock clock, + IOptions eventBusBoxesOptions) + : base(dbContextProvider, clock, eventBusBoxesOptions) + { + } + + [UnitOfWork] + public override async Task MarkAsProcessedAsync(Guid id) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"UPDATE \"{tableName}\" SET \"Processed\" = '1', \"ProcessedTime\" = TO_DATE('{Clock.Now}', 'yyyy-mm-dd hh24:mi:ss') WHERE \"Id\" = HEXTORAW('{GuidToOracleType(id)}')"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + [UnitOfWork] + public override async Task DeleteOldEventsAsync() + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + var timeToKeepEvents = Clock.Now - EventBusBoxesOptions.WaitTimeToDeleteProcessedInboxEvents; + + var sql = $"DELETE FROM \"{tableName}\" WHERE \"Processed\" = '1' AND \"CreationTime\" < TO_DATE('{timeToKeepEvents}', 'yyyy-mm-dd hh24:mi:ss')"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + protected virtual string GuidToOracleType(Guid id) + { + return BitConverter.ToString(id.ToByteArray()).Replace("-", "").ToUpper(); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventOutbox.cs new file mode 100644 index 0000000000..a5c4566ced --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventOutbox.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class OracleDbContextEventOutbox : DbContextEventOutbox , IOracleDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + public OracleDbContextEventOutbox(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + [UnitOfWork] + public override async Task DeleteAsync(Guid id) + { + var dbContext = (IHasEventOutbox) await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.OutgoingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"DELETE FROM \"{tableName}\" WHERE \"Id\" = HEXTORAW('{GuidToOracleType(id)}')"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + protected virtual string GuidToOracleType(Guid id) + { + return BitConverter.ToString(id.ToByteArray()).Replace("-", "").ToUpper(); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleInboxConfigExtensions.cs new file mode 100644 index 0000000000..ca79019e28 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class OracleInboxConfigExtensions + { + public static void UseOracle(this InboxConfig outboxConfig) + where TDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(IOracleDbContextEventInbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleOutboxConfigExtensions.cs new file mode 100644 index 0000000000..e2d7d33761 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class OracleOutboxConfigExtensions + { + public static void UseOracle(this OutboxConfig outboxConfig) + where TDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(IOracleDbContextEventOutbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs index 9580219cfc..76d0ecdd21 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs @@ -1,4 +1,6 @@ -using Volo.Abp.Guids; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore.DistributedEvents; +using Volo.Abp.Guids; using Volo.Abp.Modularity; namespace Volo.Abp.EntityFrameworkCore.Oracle.Devart @@ -17,6 +19,9 @@ namespace Volo.Abp.EntityFrameworkCore.Oracle.Devart options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary; } }); + + context.Services.AddTransient(typeof(IOracleDbContextEventOutbox<>), typeof(OracleDbContextEventOutbox<>)); + context.Services.AddTransient(typeof(IOracleDbContextEventInbox<>), typeof(OracleDbContextEventInbox<>)); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventInbox.cs new file mode 100644 index 0000000000..0cc7ae5531 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventInbox.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IOracleDbContextEventInbox : IDbContextEventInbox + where TDbContext : IHasEventInbox + { + + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventOutbox.cs new file mode 100644 index 0000000000..a588f36e43 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/IOracleDbContextEventOutbox.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IOracleDbContextEventOutbox : IDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventInbox.cs new file mode 100644 index 0000000000..6d6d04d267 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventInbox.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Volo.Abp.EventBus.Boxes; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class OracleDbContextEventInbox : DbContextEventInbox , IOracleDbContextEventInbox + where TDbContext : IHasEventInbox + { + public OracleDbContextEventInbox( + IDbContextProvider dbContextProvider, + IClock clock, + IOptions eventBusBoxesOptions) + : base(dbContextProvider, clock, eventBusBoxesOptions) + { + } + + [UnitOfWork] + public override async Task MarkAsProcessedAsync(Guid id) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"UPDATE \"{tableName}\" SET \"Processed\" = '1', \"ProcessedTime\" = TO_DATE('{Clock.Now}', 'yyyy-mm-dd hh24:mi:ss') WHERE \"Id\" = HEXTORAW('{GuidToOracleType(id)}')"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + [UnitOfWork] + public override async Task DeleteOldEventsAsync() + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + var timeToKeepEvents = Clock.Now - EventBusBoxesOptions.WaitTimeToDeleteProcessedInboxEvents; + + var sql = $"DELETE FROM \"{tableName}\" WHERE \"Processed\" = '1' AND \"CreationTime\" < TO_DATE('{timeToKeepEvents}', 'yyyy-mm-dd hh24:mi:ss')"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + protected virtual string GuidToOracleType(Guid id) + { + return BitConverter.ToString(id.ToByteArray()).Replace("-", "").ToUpper(); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventOutbox.cs new file mode 100644 index 0000000000..a5c4566ced --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleDbContextEventOutbox.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class OracleDbContextEventOutbox : DbContextEventOutbox , IOracleDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + public OracleDbContextEventOutbox(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + [UnitOfWork] + public override async Task DeleteAsync(Guid id) + { + var dbContext = (IHasEventOutbox) await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.OutgoingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"DELETE FROM \"{tableName}\" WHERE \"Id\" = HEXTORAW('{GuidToOracleType(id)}')"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + protected virtual string GuidToOracleType(Guid id) + { + return BitConverter.ToString(id.ToByteArray()).Replace("-", "").ToUpper(); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleInboxConfigExtensions.cs new file mode 100644 index 0000000000..ca79019e28 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class OracleInboxConfigExtensions + { + public static void UseOracle(this InboxConfig outboxConfig) + where TDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(IOracleDbContextEventInbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleOutboxConfigExtensions.cs new file mode 100644 index 0000000000..e2d7d33761 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/DistributedEvents/OracleOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class OracleOutboxConfigExtensions + { + public static void UseOracle(this OutboxConfig outboxConfig) + where TDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(IOracleDbContextEventOutbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs index b7cbaec1a1..7716ae6150 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs @@ -1,4 +1,6 @@ -using Volo.Abp.Guids; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore.DistributedEvents; +using Volo.Abp.Guids; using Volo.Abp.Modularity; namespace Volo.Abp.EntityFrameworkCore.Oracle @@ -15,6 +17,9 @@ namespace Volo.Abp.EntityFrameworkCore.Oracle options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary; } }); + + context.Services.AddTransient(typeof(IOracleDbContextEventOutbox<>), typeof(OracleDbContextEventOutbox<>)); + context.Services.AddTransient(typeof(IOracleDbContextEventInbox<>), typeof(OracleDbContextEventInbox<>)); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/IPostgreSqlDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/IPostgreSqlDbContextEventInbox.cs new file mode 100644 index 0000000000..5c24d79f88 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/IPostgreSqlDbContextEventInbox.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IPostgreSqlDbContextEventInbox : IDbContextEventInbox + where TDbContext : IHasEventInbox + { + + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/IPostgreSqlDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/IPostgreSqlDbContextEventOutbox.cs new file mode 100644 index 0000000000..7e6bc4bd59 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/IPostgreSqlDbContextEventOutbox.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IPostgreSqlDbContextEventOutbox : IDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventInbox.cs new file mode 100644 index 0000000000..fedff90dec --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventInbox.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Volo.Abp.EventBus.Boxes; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class PostgreSqlDbContextEventInbox : DbContextEventInbox, IPostgreSqlDbContextEventInbox + where TDbContext : IHasEventInbox + { + public PostgreSqlDbContextEventInbox( + IDbContextProvider dbContextProvider, + IClock clock, + IOptions eventBusBoxesOptions) + : base(dbContextProvider, clock, eventBusBoxesOptions) + { + } + + [UnitOfWork] + public override async Task MarkAsProcessedAsync(Guid id) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"UPDATE \"{tableName}\" SET \"Processed\" = '1', \"ProcessedTime\" = '{Clock.Now}' WHERE \"Id\" = '{id}'"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + [UnitOfWork] + public override async Task DeleteOldEventsAsync() + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + var timeToKeepEvents = Clock.Now - EventBusBoxesOptions.WaitTimeToDeleteProcessedInboxEvents; + + var sql = $"DELETE FROM \"{tableName}\" WHERE \"Processed\" = '1' AND \"CreationTime\" < '{timeToKeepEvents}'"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs new file mode 100644 index 0000000000..c5e79a0014 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class PostgreSqlDbContextEventOutbox : DbContextEventOutbox , IPostgreSqlDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + public PostgreSqlDbContextEventOutbox(IDbContextProvider dbContextProvider) : base(dbContextProvider) + { + } + + [UnitOfWork] + public override async Task DeleteAsync(Guid id) + { + var dbContext = (IHasEventOutbox) await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.OutgoingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"DELETE FROM \"{tableName}\" WHERE \"Id\" = '{id}'"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlInboxConfigExtensions.cs new file mode 100644 index 0000000000..f4bb462a1c --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class PostgreSqlInboxConfigExtensions + { + public static void UseNpgsql(this InboxConfig outboxConfig) + where TDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(IPostgreSqlDbContextEventInbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlOutboxConfigExtensions.cs new file mode 100644 index 0000000000..853ae9ba59 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class PostgreSqlOutboxConfigExtensions + { + public static void UseNpgsql(this OutboxConfig outboxConfig) + where TDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(IPostgreSqlDbContextEventOutbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs index 4e76b89113..55c1a4af42 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs @@ -1,4 +1,6 @@ -using Volo.Abp.Guids; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore.DistributedEvents; +using Volo.Abp.Guids; using Volo.Abp.Modularity; namespace Volo.Abp.EntityFrameworkCore.PostgreSql @@ -17,6 +19,9 @@ namespace Volo.Abp.EntityFrameworkCore.PostgreSql options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString; } }); + + context.Services.AddTransient(typeof(IPostgreSqlDbContextEventOutbox<>), typeof(PostgreSqlDbContextEventOutbox<>)); + context.Services.AddTransient(typeof(IPostgreSqlDbContextEventInbox<>), typeof(PostgreSqlDbContextEventInbox<>)); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlServerInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlServerInboxConfigExtensions.cs new file mode 100644 index 0000000000..60adf600c7 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlServerInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class SqlServerInboxConfigExtensions + { + public static void UseSqlServer(this InboxConfig outboxConfig) + where TDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(ISqlRawDbContextEventInbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlServerOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlServerOutboxConfigExtensions.cs new file mode 100644 index 0000000000..9022d5c7e6 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlServerOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class SqlServerOutboxConfigExtensions + { + public static void UseSqlServer(this OutboxConfig outboxConfig) + where TDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(ISqlRawDbContextEventOutbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqliteInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqliteInboxConfigExtensions.cs new file mode 100644 index 0000000000..ccc92d4eb1 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqliteInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class SqliteInboxConfigExtensions + { + public static void UseSqlite(this InboxConfig outboxConfig) + where TDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(ISqlRawDbContextEventInbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqliteOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqliteOutboxConfigExtensions.cs new file mode 100644 index 0000000000..c1d9949c19 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqliteOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class SqliteOutboxConfigExtensions + { + public static void UseSqlite(this OutboxConfig outboxConfig) + where TDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(ISqlRawDbContextEventOutbox); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index f873c2356e..3ac150fe32 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -155,10 +155,6 @@ namespace Volo.Abp.EntityFrameworkCore { try { - ApplyAbpConcepts(); - - var eventReport = CreateEventReport(); - var auditLog = AuditingManager?.Current?.Log; List entityChangeList = null; if (auditLog != null) @@ -166,6 +162,10 @@ namespace Volo.Abp.EntityFrameworkCore entityChangeList = EntityHistoryHelper.CreateChangeList(ChangeTracker.Entries().ToList()); } + HandlePropertiesBeforeSave(); + + var eventReport = CreateEventReport(); + var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); PublishEntityEvents(eventReport); @@ -291,10 +291,12 @@ namespace Volo.Abp.EntityFrameworkCore switch (entry.State) { case EntityState.Added: + ApplyAbpConceptsForAddedEntity(entry); EntityChangeEventHelper.PublishEntityCreatingEvent(entry.Entity); EntityChangeEventHelper.PublishEntityCreatedEvent(entry.Entity); break; case EntityState.Modified: + ApplyAbpConceptsForModifiedEntity(entry); if (entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never)) { if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) @@ -311,17 +313,23 @@ namespace Volo.Abp.EntityFrameworkCore break; case EntityState.Deleted: + ApplyAbpConceptsForDeletedEntity(entry); EntityChangeEventHelper.PublishEntityDeletingEvent(entry.Entity); EntityChangeEventHelper.PublishEntityDeletedEvent(entry.Entity); break; } } - protected virtual void ApplyAbpConcepts() + protected virtual void HandlePropertiesBeforeSave() { foreach (var entry in ChangeTracker.Entries().ToList()) { - ApplyAbpConcepts(entry); + HandleExtraPropertiesOnSave(entry); + + if (entry.State.IsIn(EntityState.Modified, EntityState.Deleted)) + { + UpdateConcurrencyStamp(entry); + } } } @@ -370,24 +378,6 @@ namespace Volo.Abp.EntityFrameworkCore return eventReport; } - protected virtual void ApplyAbpConcepts(EntityEntry entry) - { - switch (entry.State) - { - case EntityState.Added: - ApplyAbpConceptsForAddedEntity(entry); - break; - case EntityState.Modified: - ApplyAbpConceptsForModifiedEntity(entry); - break; - case EntityState.Deleted: - ApplyAbpConceptsForDeletedEntity(entry); - break; - } - - HandleExtraPropertiesOnSave(entry); - } - protected virtual void HandleExtraPropertiesOnSave(EntityEntry entry) { if (entry.State.IsIn(EntityState.Deleted, EntityState.Unchanged)) @@ -473,7 +463,6 @@ namespace Volo.Abp.EntityFrameworkCore { if (entry.State == EntityState.Modified && entry.Properties.Any(x => x.IsModified && x.Metadata.ValueGenerated == ValueGenerated.Never)) { - UpdateConcurrencyStamp(entry); SetModificationAuditProperties(entry); if (entry.Entity is ISoftDelete && entry.Entity.As().IsDeleted) @@ -485,11 +474,19 @@ namespace Volo.Abp.EntityFrameworkCore protected virtual void ApplyAbpConceptsForDeletedEntity(EntityEntry entry) { - if (TryCancelDeletionForSoftDelete(entry)) + if (!(entry.Entity is ISoftDelete)) { - UpdateConcurrencyStamp(entry); - SetDeletionAuditProperties(entry); + return; } + + if (IsHardDeleted(entry)) + { + return; + } + + entry.Reload(); + entry.Entity.As().IsDeleted = true; + entry.State = EntityState.Modified; } protected virtual bool IsHardDeleted(EntityEntry entry) @@ -510,7 +507,7 @@ namespace Volo.Abp.EntityFrameworkCore { return; } - + Entry(entity).Property(x => x.ConcurrencyStamp).OriginalValue = entity.ConcurrencyStamp; entity.ConcurrencyStamp = Guid.NewGuid().ToString("N"); } @@ -531,24 +528,6 @@ namespace Volo.Abp.EntityFrameworkCore entity.ConcurrencyStamp = Guid.NewGuid().ToString("N"); } - protected virtual bool TryCancelDeletionForSoftDelete(EntityEntry entry) - { - if (!(entry.Entity is ISoftDelete)) - { - return false; - } - - if (IsHardDeleted(entry)) - { - return false; - } - - entry.Reload(); - entry.State = EntityState.Modified; - entry.Entity.As().IsDeleted = true; - return true; - } - protected virtual void CheckAndSetId(EntityEntry entry) { if (entry.Entity is IEntity entityWithGuidId) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreModule.cs index b5d9eb8436..96c245be2e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreModule.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Volo.Abp.Domain; -using Volo.Abp.EntityFrameworkCore.DependencyInjection; +using Volo.Abp.EntityFrameworkCore.DistributedEvents; using Volo.Abp.Modularity; using Volo.Abp.Uow.EntityFrameworkCore; @@ -26,6 +26,11 @@ namespace Volo.Abp.EntityFrameworkCore }); context.Services.TryAddTransient(typeof(IDbContextProvider<>), typeof(UnitOfWorkDbContextProvider<>)); + context.Services.AddTransient(typeof(IDbContextEventOutbox<>), typeof(DbContextEventOutbox<>)); + context.Services.AddTransient(typeof(IDbContextEventInbox<>), typeof(DbContextEventInbox<>)); + + context.Services.AddTransient(typeof(ISqlRawDbContextEventOutbox<>), typeof(SqlRawDbContextEventOutbox<>)); + context.Services.AddTransient(typeof(ISqlRawDbContextEventInbox<>), typeof(SqlRawDbContextEventInbox<>)); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventInbox.cs new file mode 100644 index 0000000000..242b601177 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventInbox.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Volo.Abp.EventBus.Boxes; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class DbContextEventInbox : IDbContextEventInbox + where TDbContext : IHasEventInbox + { + protected IDbContextProvider DbContextProvider { get; } + protected AbpEventBusBoxesOptions EventBusBoxesOptions { get; } + protected IClock Clock { get; } + + public DbContextEventInbox( + IDbContextProvider dbContextProvider, + IClock clock, + IOptions eventBusBoxesOptions) + { + DbContextProvider = dbContextProvider; + Clock = clock; + EventBusBoxesOptions = eventBusBoxesOptions.Value; + } + + [UnitOfWork] + public virtual async Task EnqueueAsync(IncomingEventInfo incomingEvent) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + + dbContext.IncomingEvents.Add( + new IncomingEventRecord(incomingEvent) + ); + } + + [UnitOfWork] + public virtual async Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + + var outgoingEventRecords = await dbContext + .IncomingEvents + .AsNoTracking() + .Where(x => !x.Processed) + .OrderBy(x => x.CreationTime) + .Take(maxCount) + .ToListAsync(cancellationToken: cancellationToken); + + return outgoingEventRecords + .Select(x => x.ToIncomingEventInfo()) + .ToList(); + } + + [UnitOfWork] + public virtual async Task MarkAsProcessedAsync(Guid id) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var incomingEvent = await dbContext.IncomingEvents.FindAsync(id); + if (incomingEvent != null) + { + incomingEvent.MarkAsProcessed(Clock.Now); + } + } + + [UnitOfWork] + public virtual async Task ExistsByMessageIdAsync(string messageId) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + return await dbContext.IncomingEvents.AnyAsync(x => x.MessageId == messageId); + } + + [UnitOfWork] + public virtual async Task DeleteOldEventsAsync() + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var timeToKeepEvents = Clock.Now - EventBusBoxesOptions.WaitTimeToDeleteProcessedInboxEvents; + var oldEvents = await dbContext.IncomingEvents + .Where(x => x.Processed && x.CreationTime < timeToKeepEvents) + .ToListAsync(); + dbContext.IncomingEvents.RemoveRange(oldEvents); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs new file mode 100644 index 0000000000..0b8909b966 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/DbContextEventOutbox.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class DbContextEventOutbox : IDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + protected IDbContextProvider DbContextProvider { get; } + + public DbContextEventOutbox( + IDbContextProvider dbContextProvider) + { + DbContextProvider = dbContextProvider; + } + + [UnitOfWork] + public virtual async Task EnqueueAsync(OutgoingEventInfo outgoingEvent) + { + var dbContext = (IHasEventOutbox) await DbContextProvider.GetDbContextAsync(); + dbContext.OutgoingEvents.Add( + new OutgoingEventRecord(outgoingEvent) + ); + } + + [UnitOfWork] + public virtual async Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + { + var dbContext = (IHasEventOutbox) await DbContextProvider.GetDbContextAsync(); + + var outgoingEventRecords = await dbContext + .OutgoingEvents + .AsNoTracking() + .OrderBy(x => x.CreationTime) + .Take(maxCount) + .ToListAsync(cancellationToken: cancellationToken); + + return outgoingEventRecords + .Select(x => x.ToOutgoingEventInfo()) + .ToList(); + } + + [UnitOfWork] + public virtual async Task DeleteAsync(Guid id) + { + var dbContext = (IHasEventOutbox) await DbContextProvider.GetDbContextAsync(); + var outgoingEvent = await dbContext.OutgoingEvents.FindAsync(id); + if (outgoingEvent != null) + { + dbContext.Remove(outgoingEvent); + } + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreInboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreInboxConfigExtensions.cs new file mode 100644 index 0000000000..182c7b20ba --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class EfCoreInboxConfigExtensions + { + public static void UseDbContext(this InboxConfig outboxConfig) + where TDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(IDbContextEventInbox); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreOutboxConfigExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreOutboxConfigExtensions.cs new file mode 100644 index 0000000000..53745477c7 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EfCoreOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class EfCoreOutboxConfigExtensions + { + public static void UseDbContext(this OutboxConfig outboxConfig) + where TDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(IDbContextEventOutbox); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EventInboxDbContextModelBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EventInboxDbContextModelBuilderExtensions.cs new file mode 100644 index 0000000000..29c9f0042c --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EventInboxDbContextModelBuilderExtensions.cs @@ -0,0 +1,24 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class EventInboxDbContextModelBuilderExtensions + { + public static void ConfigureEventInbox([NotNull] this ModelBuilder builder) + { + builder.Entity(b => + { + b.ToTable(AbpCommonDbProperties.DbTablePrefix + "EventInbox", AbpCommonDbProperties.DbSchema); + b.ConfigureByConvention(); + b.Property(x => x.EventName).IsRequired().HasMaxLength(IncomingEventRecord.MaxEventNameLength); + b.Property(x => x.EventData).IsRequired(); + + b.HasIndex(x => new { x.Processed, x.CreationTime }); + b.HasIndex(x => x.MessageId); + }); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EventOutboxDbContextModelBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EventOutboxDbContextModelBuilderExtensions.cs new file mode 100644 index 0000000000..a8236ac43a --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/EventOutboxDbContextModelBuilderExtensions.cs @@ -0,0 +1,23 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public static class EventOutboxDbContextModelBuilderExtensions + { + public static void ConfigureEventOutbox([NotNull] this ModelBuilder builder) + { + builder.Entity(b => + { + b.ToTable(AbpCommonDbProperties.DbTablePrefix + "EventOutbox", AbpCommonDbProperties.DbSchema); + b.ConfigureByConvention(); + b.Property(x => x.EventName).IsRequired().HasMaxLength(OutgoingEventRecord.MaxEventNameLength); + b.Property(x => x.EventData).IsRequired(); + + b.HasIndex(x => x.CreationTime); + }); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IDbContextEventInbox.cs new file mode 100644 index 0000000000..81b51bc11d --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IDbContextEventInbox.cs @@ -0,0 +1,10 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IDbContextEventInbox : IEventInbox + where TDbContext : IHasEventInbox + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IDbContextEventOutbox.cs new file mode 100644 index 0000000000..71d65c7f23 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IDbContextEventOutbox.cs @@ -0,0 +1,10 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IDbContextEventOutbox : IEventOutbox + where TDbContext : IHasEventOutbox + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IHasEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IHasEventInbox.cs new file mode 100644 index 0000000000..f6836a0c19 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IHasEventInbox.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IHasEventInbox : IEfCoreDbContext + { + DbSet IncomingEvents { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IHasEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IHasEventOutbox.cs new file mode 100644 index 0000000000..5e2934d591 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IHasEventOutbox.cs @@ -0,0 +1,9 @@ +using Microsoft.EntityFrameworkCore; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface IHasEventOutbox : IEfCoreDbContext + { + DbSet OutgoingEvents { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/ISqlRawDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/ISqlRawDbContextEventInbox.cs new file mode 100644 index 0000000000..d86e3f36ed --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/ISqlRawDbContextEventInbox.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface ISqlRawDbContextEventInbox : IDbContextEventInbox + where TDbContext : IHasEventInbox + { + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/ISqlRawDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/ISqlRawDbContextEventOutbox.cs new file mode 100644 index 0000000000..776cc2f93c --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/ISqlRawDbContextEventOutbox.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public interface ISqlRawDbContextEventOutbox : IDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs new file mode 100644 index 0000000000..a1f971bbaa --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/IncomingEventRecord.cs @@ -0,0 +1,66 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class IncomingEventRecord : + BasicAggregateRoot, + IHasExtraProperties, + IHasCreationTime + { + public static int MaxEventNameLength { get; set; } = 256; + + public ExtraPropertyDictionary ExtraProperties { get; private set; } + + public string MessageId { get; private set; } + + public string EventName { get; private set; } + + public byte[] EventData { get; private set; } + + public DateTime CreationTime { get; private set; } + + public bool Processed { get; set; } + + public DateTime? ProcessedTime { get; set; } + + protected IncomingEventRecord() + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public IncomingEventRecord( + IncomingEventInfo eventInfo) + : base(eventInfo.Id) + { + MessageId = eventInfo.MessageId; + EventName = eventInfo.EventName; + EventData = eventInfo.EventData; + CreationTime = eventInfo.CreationTime; + + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public IncomingEventInfo ToIncomingEventInfo() + { + return new IncomingEventInfo( + Id, + MessageId, + EventName, + EventData, + CreationTime + ); + } + + public void MarkAsProcessed(DateTime processedTime) + { + Processed = true; + ProcessedTime = processedTime; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs new file mode 100644 index 0000000000..727216de48 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/OutgoingEventRecord.cs @@ -0,0 +1,52 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class OutgoingEventRecord : + BasicAggregateRoot, + IHasExtraProperties, + IHasCreationTime + { + public static int MaxEventNameLength { get; set; } = 256; + + public ExtraPropertyDictionary ExtraProperties { get; private set; } + + public string EventName { get; private set; } + + public byte[] EventData { get; private set; } + + public DateTime CreationTime { get; private set; } + + protected OutgoingEventRecord() + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public OutgoingEventRecord( + OutgoingEventInfo eventInfo) + : base(eventInfo.Id) + { + EventName = eventInfo.EventName; + EventData = eventInfo.EventData; + CreationTime = eventInfo.CreationTime; + + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public OutgoingEventInfo ToOutgoingEventInfo() + { + return new OutgoingEventInfo( + Id, + EventName, + EventData, + CreationTime + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlRawDbContextEventInbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlRawDbContextEventInbox.cs new file mode 100644 index 0000000000..cb764ede17 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlRawDbContextEventInbox.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Volo.Abp.EventBus.Boxes; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class SqlRawDbContextEventInbox : DbContextEventInbox , ISqlRawDbContextEventInbox + where TDbContext : IHasEventInbox + { + public SqlRawDbContextEventInbox( + IDbContextProvider dbContextProvider, + IClock clock, + IOptions eventBusBoxesOptions) + : base(dbContextProvider, clock, eventBusBoxesOptions) + { + } + + [UnitOfWork] + public override async Task MarkAsProcessedAsync(Guid id) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"UPDATE {tableName} SET Processed = '1', ProcessedTime = '{Clock.Now}' WHERE Id = '{id.ToString().ToUpper()}'"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + + [UnitOfWork] + public override async Task DeleteOldEventsAsync() + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.IncomingEvents.EntityType.GetSchemaQualifiedTableName(); + var timeToKeepEvents = Clock.Now - EventBusBoxesOptions.WaitTimeToDeleteProcessedInboxEvents; + + var sql = $"DELETE FROM {tableName} WHERE Processed = '1' AND CreationTime < '{timeToKeepEvents}'"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlRawDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlRawDbContextEventOutbox.cs new file mode 100644 index 0000000000..8748e6dc09 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DistributedEvents/SqlRawDbContextEventOutbox.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Volo.Abp.Uow; + +namespace Volo.Abp.EntityFrameworkCore.DistributedEvents +{ + public class SqlRawDbContextEventOutbox : DbContextEventOutbox , ISqlRawDbContextEventOutbox + where TDbContext : IHasEventOutbox + { + public SqlRawDbContextEventOutbox(IDbContextProvider dbContextProvider) + : base(dbContextProvider) + { + } + + [UnitOfWork] + public override async Task DeleteAsync(Guid id) + { + var dbContext = (IHasEventOutbox) await DbContextProvider.GetDbContextAsync(); + var tableName = dbContext.OutgoingEvents.EntityType.GetSchemaQualifiedTableName(); + + var sql = $"DELETE FROM {tableName} WHERE Id = '{id.ToString().ToUpper()}'"; + await dbContext.Database.ExecuteSqlRawAsync(sql); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Boxes/FodyWeavers.xml b/framework/src/Volo.Abp.EventBus.Boxes/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/FodyWeavers.xsd b/framework/src/Volo.Abp.EventBus.Boxes/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo.Abp.EventBus.Boxes.csproj b/framework/src/Volo.Abp.EventBus.Boxes/Volo.Abp.EventBus.Boxes.csproj new file mode 100644 index 0000000000..6cbaf62aed --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo.Abp.EventBus.Boxes.csproj @@ -0,0 +1,23 @@ + + + + + + + netstandard2.0 + Volo.Abp.EventBus.Boxes + Volo.Abp.EventBus.Boxes + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpDistributedEventBusExtensions.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpDistributedEventBusExtensions.cs new file mode 100644 index 0000000000..a943f6faa7 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpDistributedEventBusExtensions.cs @@ -0,0 +1,18 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus.Boxes +{ + public static class AbpDistributedEventBusExtensions + { + public static ISupportsEventBoxes AsSupportsEventBoxes(this IDistributedEventBus eventBus) + { + var supportsEventBoxes = eventBus as ISupportsEventBoxes; + if (supportsEventBoxes == null) + { + throw new AbpException($"Given type ({eventBus.GetType().AssemblyQualifiedName}) should implement {nameof(ISupportsEventBoxes)}!"); + } + + return supportsEventBoxes; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpEventBusBoxesModule.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpEventBusBoxesModule.cs new file mode 100644 index 0000000000..8e5eeb2c60 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpEventBusBoxesModule.cs @@ -0,0 +1,18 @@ +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.Modularity; + +namespace Volo.Abp.EventBus.Boxes +{ + [DependsOn( + typeof(AbpEventBusModule), + typeof(AbpBackgroundWorkersModule) + )] + public class AbpEventBusBoxesModule : AbpModule + { + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + context.AddBackgroundWorker(); + context.AddBackgroundWorker(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpEventBusBoxesOptions.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpEventBusBoxesOptions.cs new file mode 100644 index 0000000000..1d72666c42 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/AbpEventBusBoxesOptions.cs @@ -0,0 +1,48 @@ +using System; + +namespace Volo.Abp.EventBus.Boxes +{ + public class AbpEventBusBoxesOptions + { + /// + /// Default: 6 hours + /// + public TimeSpan CleanOldEventTimeIntervalSpan { get; set; } + + /// + /// Default: 1000 + /// + public int InboxWaitingEventMaxCount { get; set; } + + /// + /// Default: 1000 + /// + public int OutboxWaitingEventMaxCount { get; set; } + + /// + /// Period time of and + /// Default: 2 seconds + /// + public TimeSpan PeriodTimeSpan { get; set; } + + /// + /// Default: 15 seconds + /// + public TimeSpan DistributedLockWaitDuration { get; set; } + + /// + /// Default: 2 hours + /// + public TimeSpan WaitTimeToDeleteProcessedInboxEvents { get; set; } + + public AbpEventBusBoxesOptions() + { + CleanOldEventTimeIntervalSpan = TimeSpan.FromHours(6); + InboxWaitingEventMaxCount = 1000; + OutboxWaitingEventMaxCount = 1000; + PeriodTimeSpan = TimeSpan.FromSeconds(2); + DistributedLockWaitDuration = TimeSpan.FromSeconds(15); + WaitTimeToDeleteProcessedInboxEvents = TimeSpan.FromHours(2); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/IInboxProcessor.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/IInboxProcessor.cs new file mode 100644 index 0000000000..e93ff0cc2e --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/IInboxProcessor.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus.Boxes +{ + public interface IInboxProcessor + { + Task StartAsync(InboxConfig inboxConfig, CancellationToken cancellationToken = default); + + Task StopAsync(CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/IOutboxSender.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/IOutboxSender.cs new file mode 100644 index 0000000000..4a700eb823 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/IOutboxSender.cs @@ -0,0 +1,13 @@ +using System.Threading; +using System.Threading.Tasks; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus.Boxes +{ + public interface IOutboxSender + { + Task StartAsync(OutboxConfig outboxConfig, CancellationToken cancellationToken = default); + + Task StopAsync(CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/InboxProcessManager.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/InboxProcessManager.cs new file mode 100644 index 0000000000..310cad2886 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/InboxProcessManager.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus.Boxes +{ + public class InboxProcessManager : IBackgroundWorker + { + protected AbpDistributedEventBusOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + protected List Processors { get; } + + public InboxProcessManager( + IOptions options, + IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + Processors = new List(); + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + foreach (var inboxConfig in Options.Inboxes.Values) + { + if (inboxConfig.IsProcessingEnabled) + { + var processor = ServiceProvider.GetRequiredService(); + await processor.StartAsync(inboxConfig, cancellationToken); + Processors.Add(processor); + } + } + } + + public async Task StopAsync(CancellationToken cancellationToken = default) + { + foreach (var processor in Processors) + { + await processor.StopAsync(cancellationToken); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/InboxProcessor.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/InboxProcessor.cs new file mode 100644 index 0000000000..e3c71771ad --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/InboxProcessor.cs @@ -0,0 +1,140 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Medallion.Threading; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Threading; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.EventBus.Boxes +{ + public class InboxProcessor : IInboxProcessor, ITransientDependency + { + protected IServiceProvider ServiceProvider { get; } + protected AbpAsyncTimer Timer { get; } + protected IDistributedEventBus DistributedEventBus { get; } + protected IDistributedLockProvider DistributedLockProvider { get; } + protected IUnitOfWorkManager UnitOfWorkManager { get; } + protected IClock Clock { get; } + protected IEventInbox Inbox { get; private set; } + protected InboxConfig InboxConfig { get; private set; } + protected AbpEventBusBoxesOptions EventBusBoxesOptions { get; } + + protected DateTime? LastCleanTime { get; set; } + + protected string DistributedLockName => "Inbox_" + InboxConfig.Name; + public ILogger Logger { get; set; } + protected CancellationTokenSource StoppingTokenSource { get; } + protected CancellationToken StoppingToken { get; } + + public InboxProcessor( + IServiceProvider serviceProvider, + AbpAsyncTimer timer, + IDistributedEventBus distributedEventBus, + IDistributedLockProvider distributedLockProvider, + IUnitOfWorkManager unitOfWorkManager, + IClock clock, + IOptions eventBusBoxesOptions) + { + ServiceProvider = serviceProvider; + Timer = timer; + DistributedEventBus = distributedEventBus; + DistributedLockProvider = distributedLockProvider; + UnitOfWorkManager = unitOfWorkManager; + Clock = clock; + EventBusBoxesOptions = eventBusBoxesOptions.Value; + Timer.Period = EventBusBoxesOptions.PeriodTimeSpan.Milliseconds; + Timer.Elapsed += TimerOnElapsed; + Logger = NullLogger.Instance; + StoppingTokenSource = new CancellationTokenSource(); + StoppingToken = StoppingTokenSource.Token; + } + + private async Task TimerOnElapsed(AbpAsyncTimer arg) + { + await RunAsync(); + } + + public Task StartAsync(InboxConfig inboxConfig, CancellationToken cancellationToken = default) + { + InboxConfig = inboxConfig; + Inbox = (IEventInbox)ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); + Timer.Start(cancellationToken); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken = default) + { + StoppingTokenSource.Cancel(); + Timer.Stop(cancellationToken); + StoppingTokenSource.Dispose(); + return Task.CompletedTask; + } + + protected virtual async Task RunAsync() + { + if (StoppingToken.IsCancellationRequested) + { + return; + } + + await using (var handle = await DistributedLockProvider.TryAcquireLockAsync(DistributedLockName, cancellationToken: StoppingToken)) + { + if (handle != null) + { + await DeleteOldEventsAsync(); + + while (true) + { + var waitingEvents = await Inbox.GetWaitingEventsAsync(EventBusBoxesOptions.InboxWaitingEventMaxCount, StoppingToken); + if (waitingEvents.Count <= 0) + { + break; + } + + Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); + + foreach (var waitingEvent in waitingEvents) + { + using (var uow = UnitOfWorkManager.Begin(isTransactional: true, requiresNew: true)) + { + await DistributedEventBus + .AsSupportsEventBoxes() + .ProcessFromInboxAsync(waitingEvent, InboxConfig); + + await Inbox.MarkAsProcessedAsync(waitingEvent.Id); + + await uow.CompleteAsync(); + } + + Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); + } + } + } + else + { + Logger.LogDebug("Could not obtain the distributed lock: " + DistributedLockName); + await TaskDelayHelper.DelayAsync(EventBusBoxesOptions.DistributedLockWaitDuration.Milliseconds, StoppingToken); + } + } + } + + protected virtual async Task DeleteOldEventsAsync() + { + if (LastCleanTime != null && LastCleanTime + EventBusBoxesOptions.CleanOldEventTimeIntervalSpan > Clock.Now) + { + return; + } + + await Inbox.DeleteOldEventsAsync(); + + LastCleanTime = Clock.Now; + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/OutboxSender.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/OutboxSender.cs new file mode 100644 index 0000000000..32545227c3 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/OutboxSender.cs @@ -0,0 +1,108 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Medallion.Threading; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Threading; + +namespace Volo.Abp.EventBus.Boxes +{ + public class OutboxSender : IOutboxSender, ITransientDependency + { + protected IServiceProvider ServiceProvider { get; } + protected AbpAsyncTimer Timer { get; } + protected IDistributedEventBus DistributedEventBus { get; } + protected IDistributedLockProvider DistributedLockProvider { get; } + protected IEventOutbox Outbox { get; private set; } + protected OutboxConfig OutboxConfig { get; private set; } + protected AbpEventBusBoxesOptions EventBusBoxesOptions { get; } + protected string DistributedLockName => "Outbox_" + OutboxConfig.Name; + public ILogger Logger { get; set; } + + protected CancellationTokenSource StoppingTokenSource { get; } + protected CancellationToken StoppingToken { get; } + + public OutboxSender( + IServiceProvider serviceProvider, + AbpAsyncTimer timer, + IDistributedEventBus distributedEventBus, + IDistributedLockProvider distributedLockProvider, + IOptions eventBusBoxesOptions) + { + ServiceProvider = serviceProvider; + Timer = timer; + DistributedEventBus = distributedEventBus; + DistributedLockProvider = distributedLockProvider; + EventBusBoxesOptions = eventBusBoxesOptions.Value; + Timer.Period = EventBusBoxesOptions.PeriodTimeSpan.Milliseconds; + Timer.Elapsed += TimerOnElapsed; + Logger = NullLogger.Instance; + StoppingTokenSource = new CancellationTokenSource(); + StoppingToken = StoppingTokenSource.Token; + } + + public virtual Task StartAsync(OutboxConfig outboxConfig, CancellationToken cancellationToken = default) + { + OutboxConfig = outboxConfig; + Outbox = (IEventOutbox)ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); + Timer.Start(cancellationToken); + return Task.CompletedTask; + } + + public virtual Task StopAsync(CancellationToken cancellationToken = default) + { + StoppingTokenSource.Cancel(); + Timer.Stop(cancellationToken); + StoppingTokenSource.Dispose(); + return Task.CompletedTask; + } + + private async Task TimerOnElapsed(AbpAsyncTimer arg) + { + await RunAsync(); + } + + protected virtual async Task RunAsync() + { + await using (var handle = await DistributedLockProvider.TryAcquireLockAsync(DistributedLockName, cancellationToken: StoppingToken)) + { + if (handle != null) + { + while (true) + { + var waitingEvents = await Outbox.GetWaitingEventsAsync(EventBusBoxesOptions.OutboxWaitingEventMaxCount, StoppingToken); + if (waitingEvents.Count <= 0) + { + break; + } + + Logger.LogInformation($"Found {waitingEvents.Count} events in the outbox."); + + foreach (var waitingEvent in waitingEvents) + { + await DistributedEventBus + .AsSupportsEventBoxes() + .PublishFromOutboxAsync( + waitingEvent, + OutboxConfig + ); + + await Outbox.DeleteAsync(waitingEvent.Id); + Logger.LogInformation($"Sent the event to the message broker with id = {waitingEvent.Id:N}"); + } + } + } + else + { + Logger.LogDebug("Could not obtain the distributed lock: " + DistributedLockName); + await TaskDelayHelper.DelayAsync(EventBusBoxesOptions.DistributedLockWaitDuration.Milliseconds, StoppingToken); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/OutboxSenderManager.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/OutboxSenderManager.cs new file mode 100644 index 0000000000..6d403bd243 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/OutboxSenderManager.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.BackgroundWorkers; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus.Boxes +{ + public class OutboxSenderManager : IBackgroundWorker + { + protected AbpDistributedEventBusOptions Options { get; } + protected IServiceProvider ServiceProvider { get; } + protected List Senders { get; } + + public OutboxSenderManager( + IOptions options, + IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + Options = options.Value; + Senders = new List(); + } + + public async Task StartAsync(CancellationToken cancellationToken = default) + { + foreach (var outboxConfig in Options.Outboxes.Values) + { + if (outboxConfig.IsSendingEnabled) + { + var sender = ServiceProvider.GetRequiredService(); + await sender.StartAsync(outboxConfig, cancellationToken); + Senders.Add(sender); + } + } + } + + public async Task StopAsync(CancellationToken cancellationToken = default) + { + foreach (var sender in Senders) + { + await sender.StopAsync(cancellationToken); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/TaskHelper.cs b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/TaskHelper.cs new file mode 100644 index 0000000000..87abe18da8 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Boxes/Volo/Abp/EventBus/Boxes/TaskHelper.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.EventBus.Boxes +{ + internal static class TaskDelayHelper + { + public static async Task DelayAsync(int milliseconds, CancellationToken cancellationToken = default) + { + try + { + await Task.Delay(milliseconds, cancellationToken); + } + catch (TaskCanceledException) + { + return; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 0be3a326b6..b747a858d2 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -9,20 +9,21 @@ using Microsoft.Extensions.Options; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Guids; using Volo.Abp.Kafka; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Timing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.Kafka { [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IDistributedEventBus), typeof(KafkaDistributedEventBus))] - public class KafkaDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency + public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDependency { protected AbpEventBusOptions AbpEventBusOptions { get; } protected AbpKafkaEventBusOptions AbpKafkaEventBusOptions { get; } - protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected IKafkaMessageConsumerFactory MessageConsumerFactory { get; } protected IKafkaSerializer Serializer { get; } protected IProducerPool ProducerPool { get; } @@ -41,11 +42,19 @@ namespace Volo.Abp.EventBus.Kafka IKafkaSerializer serializer, IProducerPool producerPool, IEventErrorHandler errorHandler, - IOptions abpEventBusOptions) - : base(serviceScopeFactory, currentTenant, unitOfWorkManager, errorHandler) + IOptions abpEventBusOptions, + IGuidGenerator guidGenerator, + IClock clock) + : base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager, + errorHandler, + abpDistributedEventBusOptions, + guidGenerator, + clock) { AbpKafkaEventBusOptions = abpKafkaEventBusOptions.Value; - AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; AbpEventBusOptions = abpEventBusOptions.Value; MessageConsumerFactory = messageConsumerFactory; Serializer = serializer; @@ -78,6 +87,18 @@ namespace Volo.Abp.EventBus.Kafka return; } + string messageId = null; + + if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes)) + { + messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes); + } + + if (await AddToInboxAsync(messageId, eventName, eventType, message.Value)) + { + return; + } + var eventData = Serializer.Deserialize(message.Value, eventType); await TriggerHandlersAsync(eventType, eventData, errorContext => @@ -94,11 +115,6 @@ namespace Volo.Abp.EventBus.Kafka }); } - public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class - { - return Subscribe(typeof(TEvent), handler); - } - public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) { var handlerFactories = GetOrCreateHandlerFactories(eventType); @@ -169,7 +185,15 @@ namespace Volo.Abp.EventBus.Kafka protected override async Task PublishToEventBusAsync(Type eventType, object eventData) { - await PublishAsync(eventType, eventData, new Headers {{"messageId", Serializer.Serialize(Guid.NewGuid())}}, null); + await PublishAsync( + eventType, + eventData, + new Headers + { + { "messageId", System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("N")) } + }, + null + ); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) @@ -177,9 +201,55 @@ namespace Volo.Abp.EventBus.Kafka unitOfWork.AddOrReplaceDistributedEvent(eventRecord); } + public override Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig) + { + return PublishAsync( + AbpKafkaEventBusOptions.TopicName, + outgoingEvent.EventName, + outgoingEvent.EventData, + new Headers + { + { "messageId", System.Text.Encoding.UTF8.GetBytes(outgoingEvent.Id.ToString("N")) } + }, + null + ); + } + + public override async Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig) + { + var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); + if (eventType == null) + { + return; + } + + var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); + var exceptions = new List(); + await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); + if (exceptions.Any()) + { + ThrowOriginalExceptions(eventType, exceptions); + } + } + + protected override byte[] Serialize(object eventData) + { + return Serializer.Serialize(eventData); + } + public virtual async Task PublishAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) { - await PublishAsync(AbpKafkaEventBusOptions.TopicName, eventType, eventData, headers, headersArguments); + await PublishAsync( + AbpKafkaEventBusOptions.TopicName, + eventType, + eventData, + headers, + headersArguments + ); } public virtual async Task PublishToDeadLetterAsync(Type eventType, object eventData, Headers headers, Dictionary headersArguments) @@ -187,11 +257,16 @@ namespace Volo.Abp.EventBus.Kafka await PublishAsync(DeadLetterTopicName, eventType, eventData, headers, headersArguments); } - private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers, Dictionary headersArguments) + private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers, Dictionary headersArguments) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); + return PublishAsync(topicName, eventName, body, headers, headersArguments); + } + + private async Task PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary headersArguments) + { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); SetEventMessageHeaders(headers, headersArguments); diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index f5e3682a7a..ceba890234 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -10,23 +10,22 @@ using RabbitMQ.Client.Events; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.RabbitMQ; using Volo.Abp.Threading; +using Volo.Abp.Timing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.RabbitMq { /* TODO: How to handle unsubscribe to unbind on RabbitMq (may not be possible for) - * TODO: Implement Retry system - * TODO: Should be improved */ [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IDistributedEventBus), typeof(RabbitMqDistributedEventBus))] - public class RabbitMqDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency + public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDependency { protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } - protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected AbpEventBusOptions AbpEventBusOptions { get; } protected IConnectionPool ConnectionPool { get; } protected IRabbitMqSerializer Serializer { get; } @@ -47,14 +46,22 @@ namespace Volo.Abp.EventBus.RabbitMq ICurrentTenant currentTenant, IUnitOfWorkManager unitOfWorkManager, IEventErrorHandler errorHandler, - IOptions abpEventBusOptions) - : base(serviceScopeFactory, currentTenant, unitOfWorkManager, errorHandler) + IOptions abpEventBusOptions, + IGuidGenerator guidGenerator, + IClock clock) + : base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager, + errorHandler, + distributedEventBusOptions, + guidGenerator, + clock) { ConnectionPool = connectionPool; Serializer = serializer; MessageConsumerFactory = messageConsumerFactory; AbpEventBusOptions = abpEventBusOptions.Value; - AbpDistributedEventBusOptions = distributedEventBusOptions.Value; AbpRabbitMqEventBusOptions = options.Value; HandlerFactories = new ConcurrentDictionary>(); @@ -96,7 +103,14 @@ namespace Volo.Abp.EventBus.RabbitMq return; } - var eventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); + var eventBytes = ea.Body.ToArray(); + + if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventBytes)) + { + return; + } + + var eventData = Serializer.Deserialize(eventBytes, eventType); await TriggerHandlersAsync(eventType, eventData, errorContext => { @@ -107,17 +121,12 @@ namespace Volo.Abp.EventBus.RabbitMq retryAttempt = (int)ea.BasicProperties.Headers[EventErrorHandlerBase.RetryAttemptKey]; } - errorContext.EventData = Serializer.Deserialize(ea.Body.ToArray(), eventType); + errorContext.EventData = Serializer.Deserialize(eventBytes, eventType); errorContext.SetProperty(EventErrorHandlerBase.HeadersKey, ea.BasicProperties); errorContext.SetProperty(EventErrorHandlerBase.RetryAttemptKey, retryAttempt); }); } - public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class - { - return Subscribe(typeof(TEvent), handler); - } - public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) { var handlerFactories = GetOrCreateHandlerFactories(eventType); @@ -201,11 +210,52 @@ namespace Volo.Abp.EventBus.RabbitMq unitOfWork.AddOrReplaceDistributedEvent(eventRecord); } + public override Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig) + { + return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); + } + + public override async Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig) + { + var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); + if (eventType == null) + { + return; + } + + var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); + var exceptions = new List(); + await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); + if (exceptions.Any()) + { + ThrowOriginalExceptions(eventType, exceptions); + } + } + + protected override byte[] Serialize(object eventData) + { + return Serializer.Serialize(eventData); + } + public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary headersArguments = null) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); + return PublishAsync(eventName, body, properties, headersArguments); + } + + protected Task PublishAsync( + string eventName, + byte[] body, + IBasicProperties properties, + Dictionary headersArguments = null, + Guid? eventId = null) + { using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) { channel.ExchangeDeclare( @@ -218,9 +268,13 @@ namespace Volo.Abp.EventBus.RabbitMq { properties = channel.CreateBasicProperties(); properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent; - properties.MessageId = Guid.NewGuid().ToString("N"); } + if (properties.MessageId.IsNullOrEmpty()) + { + properties.MessageId = (eventId ?? GuidGenerator.Create()).ToString("N"); + } + SetEventMessageHeaders(properties, headersArguments); channel.BasicPublish( diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/IRebusSerializer.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/IRebusSerializer.cs new file mode 100644 index 0000000000..68700b4da5 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/IRebusSerializer.cs @@ -0,0 +1,13 @@ +using System; + +namespace Volo.Abp.EventBus.Rebus +{ + public interface IRebusSerializer + { + byte[] Serialize(object obj); + + object Deserialize(byte[] value, Type type); + + T Deserialize(byte[] value); + } +} diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index 0206b5fefc..1cb380679f 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -6,24 +6,27 @@ using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Rebus.Bus; +using Rebus.Pipeline; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; +using Volo.Abp.Timing; using Volo.Abp.Uow; namespace Volo.Abp.EventBus.Rebus { [Dependency(ReplaceServices = true)] [ExposeServices(typeof(IDistributedEventBus), typeof(RebusDistributedEventBus))] - public class RebusDistributedEventBus : EventBusBase, IDistributedEventBus, ISingletonDependency + public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDependency { protected IBus Rebus { get; } + protected IRebusSerializer Serializer { get; } //TODO: Accessing to the List may not be thread-safe! protected ConcurrentDictionary> HandlerFactories { get; } protected ConcurrentDictionary EventTypes { get; } - protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } protected AbpRebusEventBusOptions AbpRebusEventBusOptions { get; } public RebusDistributedEventBus( @@ -33,12 +36,22 @@ namespace Volo.Abp.EventBus.Rebus IBus rebus, IOptions abpDistributedEventBusOptions, IOptions abpEventBusRebusOptions, - IEventErrorHandler errorHandler) : - base(serviceScopeFactory, currentTenant, unitOfWorkManager, errorHandler) + IEventErrorHandler errorHandler, + IRebusSerializer serializer, + IGuidGenerator guidGenerator, + IClock clock) : + base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager, + errorHandler, + abpDistributedEventBusOptions, + guidGenerator, + clock) { Rebus = rebus; + Serializer = serializer; AbpRebusEventBusOptions = abpEventBusRebusOptions.Value; - AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; HandlerFactories = new ConcurrentDictionary>(); EventTypes = new ConcurrentDictionary(); @@ -122,9 +135,17 @@ namespace Volo.Abp.EventBus.Rebus Rebus.Unsubscribe(eventType); } - public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class + public async Task ProcessEventAsync(Type eventType, object eventData) { - return Subscribe(typeof(TEvent), handler); + var messageId = MessageContext.Current.TransportMessage.GetMessageId(); + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + + if (await AddToInboxAsync(messageId, eventName, eventType, MessageContext.Current.TransportMessage.Body)) + { + return; + } + + await TriggerHandlersAsync(eventType, eventData); } protected override async Task PublishToEventBusAsync(Type eventType, object eventData) @@ -180,5 +201,39 @@ namespace Volo.Abp.EventBus.Rebus return false; } + + public override Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig) + { + var eventType = EventTypes.GetOrDefault(outgoingEvent.EventName); + var eventData = Serializer.Deserialize(outgoingEvent.EventData, eventType); + + return PublishToEventBusAsync(eventType, eventData); + } + + public override async Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig) + { + var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); + if (eventType == null) + { + return; + } + + var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); + var exceptions = new List(); + await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); + if (exceptions.Any()) + { + ThrowOriginalExceptions(eventType, exceptions); + } + } + + protected override byte[] Serialize(object eventData) + { + return Serializer.Serialize(eventData); + } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventHandlerAdapter.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventHandlerAdapter.cs index 005226b885..61330828b4 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventHandlerAdapter.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventHandlerAdapter.cs @@ -14,7 +14,7 @@ namespace Volo.Abp.EventBus.Rebus public async Task Handle(TEventData message) { - await RebusDistributedEventBus.TriggerHandlersAsync(typeof(TEventData), message); + await RebusDistributedEventBus.ProcessEventAsync(message.GetType(), message); } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/Utf8JsonRabbitMqSerializer.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/Utf8JsonRabbitMqSerializer.cs new file mode 100644 index 0000000000..515e616aab --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/Utf8JsonRabbitMqSerializer.cs @@ -0,0 +1,32 @@ +using System; +using System.Text; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Json; + +namespace Volo.Abp.EventBus.Rebus +{ + public class Utf8JsonRebusSerializer : IRebusSerializer, ITransientDependency + { + private readonly IJsonSerializer _jsonSerializer; + + public Utf8JsonRebusSerializer(IJsonSerializer jsonSerializer) + { + _jsonSerializer = jsonSerializer; + } + + public byte[] Serialize(object obj) + { + return Encoding.UTF8.GetBytes(_jsonSerializer.Serialize(obj)); + } + + public object Deserialize(byte[] value, Type type) + { + return _jsonSerializer.Deserialize(type, Encoding.UTF8.GetString(value)); + } + + public T Deserialize(byte[] value) + { + return _jsonSerializer.Deserialize(Encoding.UTF8.GetString(value)); + } + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj index 261b10d8ae..5de5b0893f 100644 --- a/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj +++ b/framework/src/Volo.Abp.EventBus/Volo.Abp.EventBus.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs index 329c26c93f..bd86f14703 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/AbpEventBusModule.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Volo.Abp.EventBus.Abstractions; using Volo.Abp.EventBus.Distributed; using Volo.Abp.EventBus.Local; +using Volo.Abp.Guids; using Volo.Abp.Json; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; @@ -14,7 +15,9 @@ namespace Volo.Abp.EventBus [DependsOn( typeof(AbpEventBusAbstractionsModule), typeof(AbpMultiTenancyModule), - typeof(AbpJsonModule))] + typeof(AbpJsonModule), + typeof(AbpGuidsModule) + )] public class AbpEventBusModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs index 342e7b77bb..ab8fe6823b 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpDistributedEventBusOptions.cs @@ -6,9 +6,14 @@ namespace Volo.Abp.EventBus.Distributed { public ITypeList Handlers { get; } + public OutboxConfigDictionary Outboxes { get; } + + public InboxConfigDictionary Inboxes { get; } public AbpDistributedEventBusOptions() { Handlers = new TypeList(); + Outboxes = new OutboxConfigDictionary(); + Inboxes = new InboxConfigDictionary(); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs new file mode 100644 index 0000000000..70c5bd5533 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Guids; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.EventBus.Distributed +{ + public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventBus, ISupportsEventBoxes + { + protected IGuidGenerator GuidGenerator { get; } + protected IClock Clock { get; } + protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } + + protected DistributedEventBusBase( + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IEventErrorHandler errorHandler, + IOptions abpDistributedEventBusOptions, + IGuidGenerator guidGenerator, + IClock clock + ) : base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager, + errorHandler) + { + GuidGenerator = guidGenerator; + Clock = clock; + AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; + } + + public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class + { + return Subscribe(typeof(TEvent), handler); + } + + public override Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) + { + return PublishAsync(eventType, eventData, onUnitOfWorkComplete, useOutbox: true); + } + + public Task PublishAsync( + TEvent eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true) + where TEvent : class + { + return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox); + } + + public async Task PublishAsync( + Type eventType, + object eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true) + { + if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) + { + AddToUnitOfWork( + UnitOfWorkManager.Current, + new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext(), useOutbox) + ); + return; + } + + if (useOutbox) + { + if (await AddToOutboxAsync(eventType, eventData)) + { + return; + } + } + + await PublishToEventBusAsync(eventType, eventData); + } + + public abstract Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig + ); + + public abstract Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig); + + private async Task AddToOutboxAsync(Type eventType, object eventData) + { + var unitOfWork = UnitOfWorkManager.Current; + if (unitOfWork == null) + { + return false; + } + + foreach (var outboxConfig in AbpDistributedEventBusOptions.Outboxes.Values) + { + if (outboxConfig.Selector == null || outboxConfig.Selector(eventType)) + { + var eventOutbox = (IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + await eventOutbox.EnqueueAsync( + new OutgoingEventInfo( + GuidGenerator.Create(), + eventName, + Serialize(eventData), + Clock.Now + ) + ); + return true; + } + } + + return false; + } + + protected async Task AddToInboxAsync( + string messageId, + string eventName, + Type eventType, + byte[] eventBytes) + { + if (AbpDistributedEventBusOptions.Inboxes.Count <= 0) + { + return false; + } + + using (var scope = ServiceScopeFactory.CreateScope()) + { + foreach (var inboxConfig in AbpDistributedEventBusOptions.Inboxes.Values) + { + if (inboxConfig.EventSelector == null || inboxConfig.EventSelector(eventType)) + { + var eventInbox = (IEventInbox) scope.ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); + + if (!messageId.IsNullOrEmpty()) + { + if (await eventInbox.ExistsByMessageIdAsync(messageId)) + { + continue; + } + } + + await eventInbox.EnqueueAsync( + new IncomingEventInfo( + GuidGenerator.Create(), + messageId, + eventName, + eventBytes, + Clock.Now + ) + ); + } + } + } + + return true; + } + + protected abstract byte[] Serialize(object eventData); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IDistributedEventBus.cs index e406f7069b..80af122242 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IDistributedEventBus.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace Volo.Abp.EventBus.Distributed { @@ -12,5 +13,17 @@ namespace Volo.Abp.EventBus.Distributed /// Object to handle the event IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class; + + Task PublishAsync( + TEvent eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true) + where TEvent : class; + + Task PublishAsync( + Type eventType, + object eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true); } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventInbox.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventInbox.cs new file mode 100644 index 0000000000..d072f5d922 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventInbox.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.EventBus.Distributed +{ + public interface IEventInbox + { + Task EnqueueAsync(IncomingEventInfo incomingEvent); + + Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default); + + Task MarkAsProcessedAsync(Guid id); + + Task ExistsByMessageIdAsync(string messageId); + + Task DeleteOldEventsAsync(); + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs new file mode 100644 index 0000000000..cc00ee6654 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.EventBus.Distributed +{ + public interface IEventOutbox + { + Task EnqueueAsync(OutgoingEventInfo outgoingEvent); + + Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default); + + Task DeleteAsync(Guid id); + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs new file mode 100644 index 0000000000..7a13696bc0 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.EventBus.Distributed +{ + public interface ISupportsEventBoxes + { + Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig + ); + + Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig + ); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxConfig.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxConfig.cs new file mode 100644 index 0000000000..5f708fdb6f --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxConfig.cs @@ -0,0 +1,28 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.EventBus.Distributed +{ + public class InboxConfig + { + [NotNull] + public string Name { get; } + + public Type ImplementationType { get; set; } + + public Func EventSelector { get; set; } + + public Func HandlerSelector { get; set; } + + /// + /// Used to enable/disable processing incoming events. + /// Default: true. + /// + public bool IsProcessingEnabled { get; set; } = true; + + public InboxConfig([NotNull] string name) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxConfigDictionary.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxConfigDictionary.cs new file mode 100644 index 0000000000..9909181138 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxConfigDictionary.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.EventBus.Distributed +{ + public class InboxConfigDictionary : Dictionary + { + public void Configure(Action configAction) + { + Configure("Default", configAction); + } + + public void Configure(string outboxName, Action configAction) + { + var outboxConfig = this.GetOrAdd(outboxName, () => new InboxConfig(outboxName)); + configAction(outboxConfig); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs new file mode 100644 index 0000000000..571c62602b --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IncomingEventInfo.cs @@ -0,0 +1,44 @@ +using System; +using Volo.Abp.Data; + +namespace Volo.Abp.EventBus.Distributed +{ + public class IncomingEventInfo : IHasExtraProperties + { + public static int MaxEventNameLength { get; set; } = 256; + + public ExtraPropertyDictionary ExtraProperties { get; protected set; } + + public Guid Id { get; } + + public string MessageId { get; } + + public string EventName { get; } + + public byte[] EventData { get; } + + public DateTime CreationTime { get; } + + protected IncomingEventInfo() + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public IncomingEventInfo( + Guid id, + string messageId, + string eventName, + byte[] eventData, + DateTime creationTime) + { + Id = id; + MessageId = messageId; + EventName = Check.NotNullOrWhiteSpace(eventName, nameof(eventName), MaxEventNameLength); + EventData = eventData; + CreationTime = creationTime; + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs index 3ee3700a0d..d5dd961802 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/LocalDistributedEventBus.cs @@ -132,5 +132,15 @@ namespace Volo.Abp.EventBus.Distributed { return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); } + + public Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) where TEvent : class + { + return _localEventBus.PublishAsync(eventData, onUnitOfWorkComplete); + } + + public Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) + { + return _localEventBus.PublishAsync(eventType, eventData, onUnitOfWorkComplete); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/NullDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/NullDistributedEventBus.cs index 60be7a147c..97004fbeac 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/NullDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/NullDistributedEventBus.cs @@ -86,5 +86,15 @@ namespace Volo.Abp.EventBus.Distributed { return Task.CompletedTask; } + + public Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) where TEvent : class + { + return Task.CompletedTask; + } + + public Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true, bool useOutbox = true) + { + return Task.CompletedTask; + } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxConfig.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxConfig.cs new file mode 100644 index 0000000000..be7630ddee --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxConfig.cs @@ -0,0 +1,26 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.EventBus.Distributed +{ + public class OutboxConfig + { + [NotNull] + public string Name { get; } + + public Type ImplementationType { get; set; } + + public Func Selector { get; set; } + + /// + /// Used to enable/disable sending events from outbox to the message broker. + /// Default: true. + /// + public bool IsSendingEnabled { get; set; } = true; + + public OutboxConfig([NotNull] string name) + { + Name = Check.NotNullOrWhiteSpace(name, nameof(name)); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxConfigDictionary.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxConfigDictionary.cs new file mode 100644 index 0000000000..be5b838363 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxConfigDictionary.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.EventBus.Distributed +{ + public class OutboxConfigDictionary : Dictionary + { + public void Configure(Action configAction) + { + Configure("Default", configAction); + } + + public void Configure(string outboxName, Action configAction) + { + var outboxConfig = this.GetOrAdd(outboxName, () => new OutboxConfig(outboxName)); + configAction(outboxConfig); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs new file mode 100644 index 0000000000..643db882d2 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutgoingEventInfo.cs @@ -0,0 +1,39 @@ +using System; +using Volo.Abp.Data; + +namespace Volo.Abp.EventBus.Distributed +{ + public class OutgoingEventInfo : IHasExtraProperties + { + public static int MaxEventNameLength { get; set; } = 256; + + public ExtraPropertyDictionary ExtraProperties { get; protected set; } + + public Guid Id { get; } + + public string EventName { get; } + + public byte[] EventData { get; } + + public DateTime CreationTime { get; } + + protected OutgoingEventInfo() + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public OutgoingEventInfo( + Guid id, + string eventName, + byte[] eventData, + DateTime creationTime) + { + Id = id; + EventName = Check.NotNullOrWhiteSpace(eventName, nameof(eventName), MaxEventNameLength); + EventData = eventData; + CreationTime = creationTime; + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + }} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs index f29a169cb5..694166b1bc 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/EventBusBase.cs @@ -92,13 +92,17 @@ namespace Volo.Abp.EventBus public abstract void UnsubscribeAll(Type eventType); /// - public Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true) where TEvent : class + public Task PublishAsync(TEvent eventData, bool onUnitOfWorkComplete = true) + where TEvent : class { return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete); } /// - public async Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) + public virtual async Task PublishAsync( + Type eventType, + object eventData, + bool onUnitOfWorkComplete = true) { if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) { @@ -130,7 +134,7 @@ namespace Volo.Abp.EventBus } } - protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData , List exceptions) + protected virtual async Task TriggerHandlersAsync(Type eventType, object eventData, List exceptions, InboxConfig inboxConfig = null) { await new SynchronizationContextRemover(); @@ -138,7 +142,7 @@ namespace Volo.Abp.EventBus { foreach (var handlerFactory in handlerFactories.EventHandlerFactories) { - await TriggerHandlerAsync(handlerFactory, handlerFactories.EventType, eventData, exceptions); + await TriggerHandlerAsync(handlerFactory, handlerFactories.EventType, eventData, exceptions, inboxConfig); } } @@ -158,6 +162,19 @@ namespace Volo.Abp.EventBus } } } + + protected void ThrowOriginalExceptions(Type eventType, List exceptions) + { + if (exceptions.Count == 1) + { + exceptions[0].ReThrow(); + } + + throw new AggregateException( + "More than one error has occurred while triggering the event: " + eventType, + exceptions + ); + } protected virtual void SubscribeHandlers(ITypeList handlers) { @@ -182,7 +199,8 @@ namespace Volo.Abp.EventBus protected abstract IEnumerable GetHandlerFactories(Type eventType); - protected virtual async Task TriggerHandlerAsync(IEventHandlerFactory asyncHandlerFactory, Type eventType, object eventData, List exceptions) + protected virtual async Task TriggerHandlerAsync(IEventHandlerFactory asyncHandlerFactory, Type eventType, + object eventData, List exceptions, InboxConfig inboxConfig = null) { using (var eventHandlerWrapper = asyncHandlerFactory.GetHandler()) { @@ -190,6 +208,12 @@ namespace Volo.Abp.EventBus { var handlerType = eventHandlerWrapper.EventHandler.GetType(); + if (inboxConfig?.HandlerSelector != null && + !inboxConfig.HandlerSelector(handlerType)) + { + return; + } + using (CurrentTenant.Change(GetEventDataTenantId(eventData))) { if (ReflectionHelper.IsAssignableToGenericType(handlerType, typeof(ILocalEventHandler<>))) diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/UnitOfWorkEventPublisher.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/UnitOfWorkEventPublisher.cs index 65e271afa8..ea6a66a699 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/UnitOfWorkEventPublisher.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/UnitOfWorkEventPublisher.cs @@ -25,7 +25,11 @@ namespace Volo.Abp.EventBus { foreach (var localEvent in localEvents) { - await _localEventBus.PublishAsync(localEvent.EventType, localEvent.EventData, onUnitOfWorkComplete: false); + await _localEventBus.PublishAsync( + localEvent.EventType, + localEvent.EventData, + onUnitOfWorkComplete: false + ); } } @@ -33,7 +37,12 @@ namespace Volo.Abp.EventBus { foreach (var distributedEvent in distributedEvents) { - await _distributedEventBus.PublishAsync(distributedEvent.EventType, distributedEvent.EventData, onUnitOfWorkComplete: false); + await _distributedEventBus.PublishAsync( + distributedEvent.EventType, + distributedEvent.EventData, + onUnitOfWorkComplete: false, + useOutbox: distributedEvent.UseOutbox + ); } } } diff --git a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs index cf3479de67..3193e15ad1 100644 --- a/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs +++ b/framework/src/Volo.Abp.MemoryDb/Volo/Abp/Domain/Repositories/MemoryDb/MemoryDbRepository.cs @@ -100,7 +100,8 @@ namespace Volo.Abp.Domain.Repositories.MemoryDb new UnitOfWorkEventRecord( distributedEvent.EventData.GetType(), distributedEvent.EventData, - distributedEvent.EventOrder + distributedEvent.EventOrder, + useOutbox: true ) ); } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs index a40117e0ff..a90549cf5d 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/Domain/Repositories/MongoDB/MongoDbRepository.cs @@ -570,12 +570,13 @@ namespace Volo.Abp.Domain.Repositories.MongoDB ); } - protected virtual async Task ApplyAbpConceptsForAddedEntityAsync(TEntity entity) + protected virtual Task ApplyAbpConceptsForAddedEntityAsync(TEntity entity) { CheckAndSetId(entity); SetCreationAuditProperties(entity); TriggerEntityCreateEvents(entity); TriggerDomainEvents(entity); + return Task.CompletedTask; } private void TriggerEntityCreateEvents(TEntity entity) diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs index d32f015a33..7598066579 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/AbpMongoDbModule.cs @@ -5,6 +5,7 @@ using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.Modularity; using Volo.Abp.MongoDB.DependencyInjection; using Volo.Abp.Uow.MongoDB; +using Volo.Abp.MongoDB.DistributedEvents; namespace Volo.Abp.MongoDB { @@ -32,6 +33,16 @@ namespace Volo.Abp.MongoDB typeof(IMongoDbRepositoryFilterer<,>), typeof(MongoDbRepositoryFilterer<,>) ); + + context.Services.AddTransient( + typeof(IMongoDbContextEventOutbox<>), + typeof(MongoDbContextEventOutbox<>) + ); + + context.Services.AddTransient( + typeof(IMongoDbContextEventInbox<>), + typeof(MongoDbContextEventInbox<>) + ); } } } diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IHasEventInbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IHasEventInbox.cs new file mode 100644 index 0000000000..792a85e124 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IHasEventInbox.cs @@ -0,0 +1,9 @@ +using MongoDB.Driver; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public interface IHasEventInbox : IAbpMongoDbContext + { + IMongoCollection IncomingEvents { get; } + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IHasEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IHasEventOutbox.cs new file mode 100644 index 0000000000..ab4bc10f35 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IHasEventOutbox.cs @@ -0,0 +1,9 @@ +using MongoDB.Driver; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public interface IHasEventOutbox : IAbpMongoDbContext + { + IMongoCollection OutgoingEvents { get; } + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IMongoDbContextEventInbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IMongoDbContextEventInbox.cs new file mode 100644 index 0000000000..71e0d14562 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IMongoDbContextEventInbox.cs @@ -0,0 +1,10 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public interface IMongoDbContextEventInbox : IEventInbox + where TDbContext : IHasEventInbox + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IMongoDbContextEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IMongoDbContextEventOutbox.cs new file mode 100644 index 0000000000..ddb9cd19c5 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IMongoDbContextEventOutbox.cs @@ -0,0 +1,10 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public interface IMongoDbContextEventOutbox : IEventOutbox + where TDbContext : IHasEventOutbox + { + + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs new file mode 100644 index 0000000000..5e9d07c33f --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/IncomingEventRecord.cs @@ -0,0 +1,66 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public class IncomingEventRecord : + BasicAggregateRoot, + IHasExtraProperties, + IHasCreationTime + { + public static int MaxEventNameLength { get; set; } = 256; + + public ExtraPropertyDictionary ExtraProperties { get; private set; } + + public string MessageId { get; private set; } + + public string EventName { get; private set; } + + public byte[] EventData { get; private set; } + + public DateTime CreationTime { get; private set; } + + public bool Processed { get; set; } + + public DateTime? ProcessedTime { get; set; } + + protected IncomingEventRecord() + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public IncomingEventRecord( + IncomingEventInfo eventInfo) + : base(eventInfo.Id) + { + MessageId = eventInfo.MessageId; + EventName = eventInfo.EventName; + EventData = eventInfo.EventData; + CreationTime = eventInfo.CreationTime; + + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public IncomingEventInfo ToIncomingEventInfo() + { + return new IncomingEventInfo( + Id, + MessageId, + EventName, + EventData, + CreationTime + ); + } + + public void MarkAsProcessed(DateTime processedTime) + { + Processed = true; + ProcessedTime = processedTime; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs new file mode 100644 index 0000000000..3cf5485512 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventInbox.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp.EventBus.Boxes; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Timing; +using Volo.Abp.Uow; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public class MongoDbContextEventInbox : IMongoDbContextEventInbox + where TMongoDbContext : IHasEventInbox + { + protected IMongoDbContextProvider DbContextProvider { get; } + protected AbpEventBusBoxesOptions EventBusBoxesOptions { get; } + protected IClock Clock { get; } + + public MongoDbContextEventInbox( + IMongoDbContextProvider dbContextProvider, + IClock clock, + IOptions eventBusBoxesOptions) + { + DbContextProvider = dbContextProvider; + Clock = clock; + EventBusBoxesOptions = eventBusBoxesOptions.Value; + } + + + [UnitOfWork] + public virtual async Task EnqueueAsync(IncomingEventInfo incomingEvent) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + if (dbContext.SessionHandle != null) + { + await dbContext.IncomingEvents.InsertOneAsync( + dbContext.SessionHandle, + new IncomingEventRecord(incomingEvent) + ); + } + else + { + await dbContext.IncomingEvents.InsertOneAsync( + new IncomingEventRecord(incomingEvent) + ); + } + } + + [UnitOfWork] + public virtual async Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + { + var dbContext = await DbContextProvider.GetDbContextAsync(cancellationToken); + + var outgoingEventRecords = await dbContext + .IncomingEvents + .AsQueryable() + .Where(x => !x.Processed) + .OrderBy(x => x.CreationTime) + .Take(maxCount) + .ToListAsync(cancellationToken: cancellationToken); + + return outgoingEventRecords + .Select(x => x.ToIncomingEventInfo()) + .ToList(); + } + + [UnitOfWork] + public virtual async Task MarkAsProcessedAsync(Guid id) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + + var filter = Builders.Filter.Eq(x => x.Id, id); + var update = Builders.Update.Set(x => x.Processed, true).Set(x => x.ProcessedTime, Clock.Now); + + if (dbContext.SessionHandle != null) + { + await dbContext.IncomingEvents.UpdateOneAsync(dbContext.SessionHandle, filter, update); + } + else + { + await dbContext.IncomingEvents.UpdateOneAsync(filter, update); + } + } + + [UnitOfWork] + public virtual async Task ExistsByMessageIdAsync(string messageId) + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + return await dbContext.IncomingEvents.AsQueryable().AnyAsync(x => x.MessageId == messageId); + } + + [UnitOfWork] + public virtual async Task DeleteOldEventsAsync() + { + var dbContext = await DbContextProvider.GetDbContextAsync(); + var timeToKeepEvents = Clock.Now - EventBusBoxesOptions.WaitTimeToDeleteProcessedInboxEvents; + + if (dbContext.SessionHandle != null) + { + await dbContext.IncomingEvents.DeleteManyAsync(dbContext.SessionHandle, x => x.Processed && x.CreationTime < timeToKeepEvents); + } + else + { + await dbContext.IncomingEvents.DeleteManyAsync(x => x.Processed && x.CreationTime < timeToKeepEvents); + } + } + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs new file mode 100644 index 0000000000..dad0294d1c --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.Uow; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public class MongoDbContextEventOutbox : IMongoDbContextEventOutbox + where TMongoDbContext : IHasEventOutbox + { + protected IMongoDbContextProvider MongoDbContextProvider { get; } + + public MongoDbContextEventOutbox(IMongoDbContextProvider mongoDbContextProvider) + { + MongoDbContextProvider = mongoDbContextProvider; + } + + [UnitOfWork] + public virtual async Task EnqueueAsync(OutgoingEventInfo outgoingEvent) + { + var dbContext = (IHasEventOutbox) await MongoDbContextProvider.GetDbContextAsync(); + if (dbContext.SessionHandle != null) + { + await dbContext.OutgoingEvents.InsertOneAsync( + dbContext.SessionHandle, + new OutgoingEventRecord(outgoingEvent) + ); + } + else + { + await dbContext.OutgoingEvents.InsertOneAsync( + new OutgoingEventRecord(outgoingEvent) + ); + } + } + + [UnitOfWork] + public virtual async Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default) + { + var dbContext = (IHasEventOutbox) await MongoDbContextProvider.GetDbContextAsync(cancellationToken); + + var outgoingEventRecords = await dbContext + .OutgoingEvents.AsQueryable() + .OrderBy(x => x.CreationTime) + .Take(maxCount) + .ToListAsync(cancellationToken: cancellationToken); + + return outgoingEventRecords + .Select(x => x.ToOutgoingEventInfo()) + .ToList(); + } + + [UnitOfWork] + public virtual async Task DeleteAsync(Guid id) + { + var dbContext = (IHasEventOutbox) await MongoDbContextProvider.GetDbContextAsync(); + if (dbContext.SessionHandle != null) + { + await dbContext.OutgoingEvents.DeleteOneAsync(dbContext.SessionHandle, x => x.Id.Equals(id)); + } + else + { + await dbContext.OutgoingEvents.DeleteOneAsync(x => x.Id.Equals(id)); + } + } + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbInboxConfigExtensions.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbInboxConfigExtensions.cs new file mode 100644 index 0000000000..27be4a078b --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbInboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public static class MongoDbInboxConfigExtensions + { + public static void UseMongoDbContext(this InboxConfig outboxConfig) + where TMongoDbContext : IHasEventInbox + { + outboxConfig.ImplementationType = typeof(IMongoDbContextEventInbox); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbOutboxConfigExtensions.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbOutboxConfigExtensions.cs new file mode 100644 index 0000000000..6da1513451 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbOutboxConfigExtensions.cs @@ -0,0 +1,13 @@ +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public static class MongoDbOutboxConfigExtensions + { + public static void UseMongoDbContext(this OutboxConfig outboxConfig) + where TMongoDbContext : IHasEventOutbox + { + outboxConfig.ImplementationType = typeof(IMongoDbContextEventOutbox); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs new file mode 100644 index 0000000000..c38de0cd91 --- /dev/null +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/OutgoingEventRecord.cs @@ -0,0 +1,52 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.MongoDB.DistributedEvents +{ + public class OutgoingEventRecord : + BasicAggregateRoot, + IHasExtraProperties, + IHasCreationTime + { + public static int MaxEventNameLength { get; set; } = 256; + + public ExtraPropertyDictionary ExtraProperties { get; private set; } + + public string EventName { get; private set; } + + public byte[] EventData { get; private set; } + + public DateTime CreationTime { get; private set; } + + protected OutgoingEventRecord() + { + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public OutgoingEventRecord( + OutgoingEventInfo eventInfo) + : base(eventInfo.Id) + { + EventName = eventInfo.EventName; + EventData = eventInfo.EventData; + CreationTime = eventInfo.CreationTime; + + ExtraProperties = new ExtraPropertyDictionary(); + this.SetDefaultsForExtraProperties(); + } + + public OutgoingEventInfo ToOutgoingEventInfo() + { + return new OutgoingEventInfo( + Id, + EventName, + EventData, + CreationTime + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs index e7e6efd5de..d95fc7012b 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs @@ -96,7 +96,7 @@ namespace Volo.Abp.Threading try { - Elapsed.InvokeSafely(this, new EventArgs()); + Elapsed.InvokeSafely(this, EventArgs.Empty); } catch(Exception ex) { diff --git a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs index 11ae4920cd..cba5646f7a 100644 --- a/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs +++ b/framework/src/Volo.Abp.Uow/Volo/Abp/Uow/UnitOfWorkEventRecord.cs @@ -11,6 +11,8 @@ namespace Volo.Abp.Uow public long EventOrder { get; } + public bool UseOutbox { get; } + /// /// Extra properties can be used if needed. /// @@ -19,11 +21,13 @@ namespace Volo.Abp.Uow public UnitOfWorkEventRecord( Type eventType, object eventData, - long eventOrder) + long eventOrder, + bool useOutbox = true) { EventType = eventType; EventData = eventData; EventOrder = eventOrder; + UseOutbox = useOutbox; } } } \ No newline at end of file diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index c6bd29b578..714da2465c 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -91,6 +91,7 @@ $projects = ( "framework/src/Volo.Abp.Ddd.Application", "framework/src/Volo.Abp.Ddd.Application.Contracts", "framework/src/Volo.Abp.Ddd.Domain", + "framework/src/Volo.Abp.DistributedLocking", "framework/src/Volo.Abp.Emailing", "framework/src/Volo.Abp.EntityFrameworkCore", "framework/src/Volo.Abp.EntityFrameworkCore.MySQL", @@ -101,6 +102,7 @@ $projects = ( "framework/src/Volo.Abp.EntityFrameworkCore.SqlServer", "framework/src/Volo.Abp.EventBus.Abstractions", "framework/src/Volo.Abp.EventBus", + "framework/src/Volo.Abp.EventBus.Boxes", "framework/src/Volo.Abp.EventBus.RabbitMQ", "framework/src/Volo.Abp.EventBus.Kafka", "framework/src/Volo.Abp.EventBus.Rebus", diff --git a/test/DistEvents/DistDemoApp/DistDemoApp.csproj b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoApp.EfCoreRabbitMq.csproj similarity index 66% rename from test/DistEvents/DistDemoApp/DistDemoApp.csproj rename to test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoApp.EfCoreRabbitMq.csproj index 524fda1046..a3e769e45b 100644 --- a/test/DistEvents/DistDemoApp/DistDemoApp.csproj +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoApp.EfCoreRabbitMq.csproj @@ -3,19 +3,13 @@ Exe net6.0 + DistDemoApp - - - - - - - - - + + diff --git a/test/DistEvents/DistDemoApp/DistDemoAppModule.cs b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoAppEfCoreRabbitMqModule.cs similarity index 51% rename from test/DistEvents/DistDemoApp/DistDemoAppModule.cs rename to test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoAppEfCoreRabbitMqModule.cs index f69f9c2380..da2e7a1445 100644 --- a/test/DistEvents/DistDemoApp/DistDemoAppModule.cs +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/DistDemoAppEfCoreRabbitMqModule.cs @@ -1,22 +1,22 @@ using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Autofac; -using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.DistributedEvents; using Volo.Abp.EntityFrameworkCore.SqlServer; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.RabbitMq; using Volo.Abp.Modularity; namespace DistDemoApp { [DependsOn( typeof(AbpEntityFrameworkCoreSqlServerModule), - typeof(AbpAutofacModule) + typeof(AbpEventBusRabbitMqModule), + typeof(DistDemoAppSharedModule) )] - public class DistDemoAppModule : AbpModule + public class DistDemoAppEfCoreRabbitMqModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.AddHostedService(); - context.Services.AddAbpDbContext(options => { options.AddDefaultRepositories(); @@ -27,10 +27,17 @@ namespace DistDemoApp options.UseSqlServer(); }); - Configure(options => + Configure(options => { - options.EtoMappings.Add(); - options.AutoEventSelectors.Add(); + options.Outboxes.Configure(config => + { + config.UseDbContext(); + }); + + options.Inboxes.Configure(config => + { + config.UseDbContext(); + }); }); } } diff --git a/test/DistEvents/DistDemoApp/Migrations/20210825112717_Added_Summary_Table.Designer.cs b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.Designer.cs similarity index 57% rename from test/DistEvents/DistDemoApp/Migrations/20210825112717_Added_Summary_Table.Designer.cs rename to test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.Designer.cs index 4c2cf9abbb..292cff66f9 100644 --- a/test/DistEvents/DistDemoApp/Migrations/20210825112717_Added_Summary_Table.Designer.cs +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.Designer.cs @@ -11,8 +11,8 @@ using Volo.Abp.EntityFrameworkCore; namespace DistDemoApp.Migrations { [DbContext(typeof(TodoDbContext))] - [Migration("20210825112717_Added_Summary_Table")] - partial class Added_Summary_Table + [Migration("20210910152547_Added_Boxes_Initial")] + partial class Added_Boxes_Initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -89,6 +89,73 @@ namespace DistDemoApp.Migrations b.ToTable("TodoSummaries"); }); + + modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.IncomingEventRecord", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("EventData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("MessageId") + .HasColumnType("nvarchar(450)"); + + b.Property("Processed") + .HasColumnType("bit"); + + b.Property("ProcessedTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.HasIndex("Processed", "CreationTime"); + + b.ToTable("AbpEventInbox"); + }); + + modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.OutgoingEventRecord", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("EventData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.HasKey("Id"); + + b.ToTable("AbpEventOutbox"); + }); #pragma warning restore 612, 618 } } diff --git a/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.cs b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.cs new file mode 100644 index 0000000000..9094eaa8c9 --- /dev/null +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/20210910152547_Added_Boxes_Initial.cs @@ -0,0 +1,103 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace DistDemoApp.Migrations +{ + public partial class Added_Boxes_Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AbpEventInbox", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + MessageId = table.Column(type: "nvarchar(450)", nullable: true), + EventName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + EventData = table.Column(type: "varbinary(max)", nullable: false), + CreationTime = table.Column(type: "datetime2", nullable: false), + Processed = table.Column(type: "bit", nullable: false), + ProcessedTime = table.Column(type: "datetime2", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEventInbox", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AbpEventOutbox", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + EventName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + EventData = table.Column(type: "varbinary(max)", nullable: false), + CreationTime = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AbpEventOutbox", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TodoItems", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Text = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), + CreationTime = table.Column(type: "datetime2", nullable: false), + CreatorId = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItems", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TodoSummaries", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Year = table.Column(type: "int", nullable: false), + Month = table.Column(type: "tinyint", nullable: false), + Day = table.Column(type: "tinyint", nullable: false), + TotalCount = table.Column(type: "int", nullable: false), + ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoSummaries", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_AbpEventInbox_MessageId", + table: "AbpEventInbox", + column: "MessageId"); + + migrationBuilder.CreateIndex( + name: "IX_AbpEventInbox_Processed_CreationTime", + table: "AbpEventInbox", + columns: new[] { "Processed", "CreationTime" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AbpEventInbox"); + + migrationBuilder.DropTable( + name: "AbpEventOutbox"); + + migrationBuilder.DropTable( + name: "TodoItems"); + + migrationBuilder.DropTable( + name: "TodoSummaries"); + } + } +} diff --git a/test/DistEvents/DistDemoApp/Migrations/TodoDbContextModelSnapshot.cs b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/TodoDbContextModelSnapshot.cs similarity index 58% rename from test/DistEvents/DistDemoApp/Migrations/TodoDbContextModelSnapshot.cs rename to test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/TodoDbContextModelSnapshot.cs index 99f0f92ab7..57e8c14442 100644 --- a/test/DistEvents/DistDemoApp/Migrations/TodoDbContextModelSnapshot.cs +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Migrations/TodoDbContextModelSnapshot.cs @@ -87,6 +87,73 @@ namespace DistDemoApp.Migrations b.ToTable("TodoSummaries"); }); + + modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.IncomingEventRecord", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("EventData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.Property("MessageId") + .HasColumnType("nvarchar(450)"); + + b.Property("Processed") + .HasColumnType("bit"); + + b.Property("ProcessedTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("MessageId"); + + b.HasIndex("Processed", "CreationTime"); + + b.ToTable("AbpEventInbox"); + }); + + modelBuilder.Entity("Volo.Abp.EntityFrameworkCore.DistributedEvents.OutgoingEventRecord", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnType("datetime2") + .HasColumnName("CreationTime"); + + b.Property("EventData") + .IsRequired() + .HasColumnType("varbinary(max)"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("ExtraProperties") + .HasColumnType("nvarchar(max)") + .HasColumnName("ExtraProperties"); + + b.HasKey("Id"); + + b.ToTable("AbpEventOutbox"); + }); #pragma warning restore 612, 618 } } diff --git a/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Program.cs b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Program.cs new file mode 100644 index 0000000000..597b29ae22 --- /dev/null +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/Program.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Events; + +namespace DistDemoApp +{ + public class Program + { + public static async Task Main(string[] args) + { + Log.Logger = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Debug() +#else + .MinimumLevel.Information() +#endif + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Async(c => c.File("Logs/logs.txt")) + .WriteTo.Async(c => c.Console()) + .CreateLogger(); + + try + { + Log.Information("Starting console host."); + await CreateHostBuilder(args).RunConsoleAsync(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + + } + + internal static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseAutofac() + .UseSerilog() + .ConfigureAppConfiguration((context, config) => + { + //setup your additional configuration sources + }) + .ConfigureServices((hostContext, services) => + { + services.AddApplication(); + }); + } +} diff --git a/test/DistEvents/DistDemoApp/TodoDbContext.cs b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContext.cs similarity index 63% rename from test/DistEvents/DistDemoApp/TodoDbContext.cs rename to test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContext.cs index 000f18c802..5a1ddd2c3f 100644 --- a/test/DistEvents/DistDemoApp/TodoDbContext.cs +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContext.cs @@ -1,13 +1,16 @@ using Microsoft.EntityFrameworkCore; using Volo.Abp.Domain.Entities; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.DistributedEvents; namespace DistDemoApp { - public class TodoDbContext : AbpDbContext + public class TodoDbContext : AbpDbContext, IHasEventOutbox, IHasEventInbox { public DbSet TodoItems { get; set; } public DbSet TodoSummaries { get; set; } + public DbSet OutgoingEvents { get; set; } + public DbSet IncomingEvents { get; set; } public TodoDbContext(DbContextOptions options) : base(options) @@ -18,6 +21,9 @@ namespace DistDemoApp protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureEventOutbox(); + modelBuilder.ConfigureEventInbox(); modelBuilder.Entity(b => { diff --git a/test/DistEvents/DistDemoApp/TodoDbContextFactory.cs b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContextFactory.cs similarity index 100% rename from test/DistEvents/DistDemoApp/TodoDbContextFactory.cs rename to test/DistEvents/DistDemoApp.EfCoreRabbitMq/TodoDbContextFactory.cs diff --git a/test/DistEvents/DistDemoApp.EfCoreRabbitMq/appsettings.json b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/appsettings.json new file mode 100644 index 0000000000..5359c021b7 --- /dev/null +++ b/test/DistEvents/DistDemoApp.EfCoreRabbitMq/appsettings.json @@ -0,0 +1,19 @@ +{ + "ConnectionStrings": { + "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=DistEventsDemo;Trusted_Connection=True" + }, + "RabbitMQ": { + "Connections": { + "Default": { + "HostName": "localhost" + } + }, + "EventBus": { + "ClientName": "DistDemoApp", + "ExchangeName": "DistDemo" + } + }, + "Redis": { + "Configuration": "127.0.0.1" + } +} diff --git a/test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoApp.MongoDbKafka.csproj b/test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoApp.MongoDbKafka.csproj new file mode 100644 index 0000000000..a503c6f6c0 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoApp.MongoDbKafka.csproj @@ -0,0 +1,21 @@ + + + + Exe + net5.0 + DistDemoApp + + + + + + + + + + + Always + + + + diff --git a/test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoAppMongoDbKafkaModule.cs b/test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoAppMongoDbKafkaModule.cs new file mode 100644 index 0000000000..b2e41b6ca7 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbKafka/DistDemoAppMongoDbKafkaModule.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Kafka; +using Volo.Abp.Modularity; +using Volo.Abp.MongoDB; +using Volo.Abp.MongoDB.DistributedEvents; + +namespace DistDemoApp +{ + [DependsOn( + typeof(AbpMongoDbModule), + typeof(AbpEventBusKafkaModule), + typeof(DistDemoAppSharedModule) + )] + public class DistDemoAppMongoDbKafkaModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMongoDbContext(options => + { + options.AddDefaultRepositories(); + }); + + Configure(options => + { + options.Outboxes.Configure(config => + { + config.UseMongoDbContext(); + }); + + options.Inboxes.Configure(config => + { + config.UseMongoDbContext(); + }); + }); + } + } +} diff --git a/test/DistEvents/DistDemoApp/Program.cs b/test/DistEvents/DistDemoApp.MongoDbKafka/Program.cs similarity index 95% rename from test/DistEvents/DistDemoApp/Program.cs rename to test/DistEvents/DistDemoApp.MongoDbKafka/Program.cs index 676a07e357..b048c17389 100644 --- a/test/DistEvents/DistDemoApp/Program.cs +++ b/test/DistEvents/DistDemoApp.MongoDbKafka/Program.cs @@ -51,7 +51,7 @@ namespace DistDemoApp }) .ConfigureServices((hostContext, services) => { - services.AddApplication(); + services.AddApplication(); }); } } diff --git a/test/DistEvents/DistDemoApp.MongoDbKafka/TodoMongoDbContext.cs b/test/DistEvents/DistDemoApp.MongoDbKafka/TodoMongoDbContext.cs new file mode 100644 index 0000000000..a7f1b78f86 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbKafka/TodoMongoDbContext.cs @@ -0,0 +1,26 @@ +using MongoDB.Driver; +using Volo.Abp.Data; +using Volo.Abp.MongoDB; +using Volo.Abp.MongoDB.DistributedEvents; + +namespace DistDemoApp +{ + [ConnectionStringName("Default")] + public class TodoMongoDbContext : AbpMongoDbContext, IHasEventOutbox, IHasEventInbox + { + public IMongoCollection TodoItems => Collection(); + public IMongoCollection TodoSummaries => Collection(); + + public IMongoCollection OutgoingEvents + { + get => Collection(); + set {} + } + public IMongoCollection IncomingEvents + { + get => Collection(); + set {} + } + } + +} diff --git a/test/DistEvents/DistDemoApp.MongoDbKafka/appsettings.json b/test/DistEvents/DistDemoApp.MongoDbKafka/appsettings.json new file mode 100644 index 0000000000..d8c528fe87 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbKafka/appsettings.json @@ -0,0 +1,19 @@ +{ + "ConnectionStrings": { + "Default": "mongodb://localhost:27018,localhost:27019,localhost:27020/DistEventsDemo" + }, + "Kafka": { + "Connections": { + "Default": { + "BootstrapServers": "localhost:9092" + } + }, + "EventBus": { + "GroupId": "DistDemoApp", + "TopicName": "DistDemoTopic" + } + }, + "Redis": { + "Configuration": "127.0.0.1" + } +} diff --git a/test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoApp.MongoDbRebus.csproj b/test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoApp.MongoDbRebus.csproj new file mode 100644 index 0000000000..c102adb382 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoApp.MongoDbRebus.csproj @@ -0,0 +1,21 @@ + + + + Exe + net5.0 + DistDemoApp + + + + + + + + + + + Always + + + + diff --git a/test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoAppMongoDbRebusModule.cs b/test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoAppMongoDbRebusModule.cs new file mode 100644 index 0000000000..21dab7b9b5 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbRebus/DistDemoAppMongoDbRebusModule.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.DependencyInjection; +using Rebus.Persistence.InMem; +using Rebus.Transport.InMem; +using Volo.Abp.EventBus.Distributed; +using Volo.Abp.EventBus.Rebus; +using Volo.Abp.Modularity; +using Volo.Abp.MongoDB; +using Volo.Abp.MongoDB.DistributedEvents; + +namespace DistDemoApp +{ + [DependsOn( + typeof(AbpMongoDbModule), + typeof(AbpEventBusRebusModule), + typeof(DistDemoAppSharedModule) + )] + public class DistDemoAppMongoDbRebusModule : AbpModule + { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.InputQueueName = "eventbus"; + options.Configurer = rebusConfigurer => + { + rebusConfigurer.Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "eventbus")); + rebusConfigurer.Subscriptions(s => s.StoreInMemory()); + }; + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMongoDbContext(options => + { + options.AddDefaultRepositories(); + }); + + Configure(options => + { + options.Outboxes.Configure(config => + { + config.UseMongoDbContext(); + }); + + options.Inboxes.Configure(config => + { + config.UseMongoDbContext(); + }); + }); + } + } +} diff --git a/test/DistEvents/DistDemoApp.MongoDbRebus/Program.cs b/test/DistEvents/DistDemoApp.MongoDbRebus/Program.cs new file mode 100644 index 0000000000..a79ad1b1bf --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbRebus/Program.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Events; + +namespace DistDemoApp +{ + public class Program + { + public static async Task Main(string[] args) + { + Log.Logger = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Debug() +#else + .MinimumLevel.Information() +#endif + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .Enrich.FromLogContext() + .WriteTo.Async(c => c.File("Logs/logs.txt")) + .WriteTo.Async(c => c.Console()) + .CreateLogger(); + + try + { + Log.Information("Starting console host."); + await CreateHostBuilder(args).RunConsoleAsync(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + + } + + internal static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseAutofac() + .UseSerilog() + .ConfigureAppConfiguration((context, config) => + { + //setup your additional configuration sources + }) + .ConfigureServices((hostContext, services) => + { + services.AddApplication(); + }); + } +} diff --git a/test/DistEvents/DistDemoApp.MongoDbRebus/TodoMongoDbContext.cs b/test/DistEvents/DistDemoApp.MongoDbRebus/TodoMongoDbContext.cs new file mode 100644 index 0000000000..95370bb4d2 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbRebus/TodoMongoDbContext.cs @@ -0,0 +1,19 @@ +using MongoDB.Driver; +using Volo.Abp.Data; +using Volo.Abp.MongoDB; +using Volo.Abp.MongoDB.DistributedEvents; + +namespace DistDemoApp +{ + [ConnectionStringName("Default")] + public class TodoMongoDbContext : AbpMongoDbContext, IHasEventOutbox, IHasEventInbox + { + public IMongoCollection TodoItems => Collection(); + public IMongoCollection TodoSummaries => Collection(); + + public IMongoCollection OutgoingEvents => Collection(); + + public IMongoCollection IncomingEvents => Collection(); + } + +} diff --git a/test/DistEvents/DistDemoApp.MongoDbRebus/appsettings.json b/test/DistEvents/DistDemoApp.MongoDbRebus/appsettings.json new file mode 100644 index 0000000000..d8c528fe87 --- /dev/null +++ b/test/DistEvents/DistDemoApp.MongoDbRebus/appsettings.json @@ -0,0 +1,19 @@ +{ + "ConnectionStrings": { + "Default": "mongodb://localhost:27018,localhost:27019,localhost:27020/DistEventsDemo" + }, + "Kafka": { + "Connections": { + "Default": { + "BootstrapServers": "localhost:9092" + } + }, + "EventBus": { + "GroupId": "DistDemoApp", + "TopicName": "DistDemoTopic" + } + }, + "Redis": { + "Configuration": "127.0.0.1" + } +} diff --git a/test/DistEvents/DistDemoApp/DemoService.cs b/test/DistEvents/DistDemoApp.Shared/DemoService.cs similarity index 100% rename from test/DistEvents/DistDemoApp/DemoService.cs rename to test/DistEvents/DistDemoApp.Shared/DemoService.cs diff --git a/test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj b/test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj new file mode 100644 index 0000000000..4dc9d1dd64 --- /dev/null +++ b/test/DistEvents/DistDemoApp.Shared/DistDemoApp.Shared.csproj @@ -0,0 +1,23 @@ + + + + net5.0 + DistDemoApp + + + + + + + + + + + + + + + + + + diff --git a/test/DistEvents/DistDemoApp/MyProjectNameHostedService.cs b/test/DistEvents/DistDemoApp.Shared/DistDemoAppHostedService.cs similarity index 90% rename from test/DistEvents/DistDemoApp/MyProjectNameHostedService.cs rename to test/DistEvents/DistDemoApp.Shared/DistDemoAppHostedService.cs index 7522d9fe9d..ba72d6902a 100644 --- a/test/DistEvents/DistDemoApp/MyProjectNameHostedService.cs +++ b/test/DistEvents/DistDemoApp.Shared/DistDemoAppHostedService.cs @@ -6,13 +6,13 @@ using Volo.Abp; namespace DistDemoApp { - public class MyProjectNameHostedService : IHostedService + public class DistDemoAppHostedService : IHostedService { private readonly IAbpApplicationWithExternalServiceProvider _application; private readonly IServiceProvider _serviceProvider; private readonly DemoService _demoService; - public MyProjectNameHostedService( + public DistDemoAppHostedService( IAbpApplicationWithExternalServiceProvider application, IServiceProvider serviceProvider, DemoService demoService) diff --git a/test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs b/test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs new file mode 100644 index 0000000000..138b64f567 --- /dev/null +++ b/test/DistEvents/DistDemoApp.Shared/DistDemoAppSharedModule.cs @@ -0,0 +1,39 @@ +using Medallion.Threading; +using Medallion.Threading.Redis; +using Microsoft.Extensions.DependencyInjection; +using StackExchange.Redis; +using Volo.Abp.Autofac; +using Volo.Abp.Domain; +using Volo.Abp.Domain.Entities.Events.Distributed; +using Volo.Abp.EventBus.Boxes; +using Volo.Abp.Modularity; + +namespace DistDemoApp +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpDddDomainModule), + typeof(AbpEventBusBoxesModule) + )] + public class DistDemoAppSharedModule : AbpModule + { + public override void ConfigureServices(ServiceConfigurationContext context) + { + var configuration = context.Services.GetConfiguration(); + + context.Services.AddHostedService(); + + Configure(options => + { + options.EtoMappings.Add(); + options.AutoEventSelectors.Add(); + }); + + context.Services.AddSingleton(sp => + { + var connection = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); + return new RedisDistributedSynchronizationProvider(connection.GetDatabase()); + }); + } + } +} \ No newline at end of file diff --git a/test/DistEvents/DistDemoApp/TodoEventHandler.cs b/test/DistEvents/DistDemoApp.Shared/TodoEventHandler.cs similarity index 96% rename from test/DistEvents/DistDemoApp/TodoEventHandler.cs rename to test/DistEvents/DistDemoApp.Shared/TodoEventHandler.cs index 73c7040732..7a69ceed1e 100644 --- a/test/DistEvents/DistDemoApp/TodoEventHandler.cs +++ b/test/DistEvents/DistDemoApp.Shared/TodoEventHandler.cs @@ -41,8 +41,6 @@ namespace DistDemoApp } Console.WriteLine("Increased total count: " + todoSummary); - - throw new ApplicationException("Thrown to rollback the UOW!"); } public async Task HandleEventAsync(EntityDeletedEto eventData) diff --git a/test/DistEvents/DistDemoApp/TodoItem.cs b/test/DistEvents/DistDemoApp.Shared/TodoItem.cs similarity index 100% rename from test/DistEvents/DistDemoApp/TodoItem.cs rename to test/DistEvents/DistDemoApp.Shared/TodoItem.cs diff --git a/test/DistEvents/DistDemoApp/TodoItemEto.cs b/test/DistEvents/DistDemoApp.Shared/TodoItemEto.cs similarity index 100% rename from test/DistEvents/DistDemoApp/TodoItemEto.cs rename to test/DistEvents/DistDemoApp.Shared/TodoItemEto.cs diff --git a/test/DistEvents/DistDemoApp/TodoItemObjectMapper.cs b/test/DistEvents/DistDemoApp.Shared/TodoItemObjectMapper.cs similarity index 100% rename from test/DistEvents/DistDemoApp/TodoItemObjectMapper.cs rename to test/DistEvents/DistDemoApp.Shared/TodoItemObjectMapper.cs diff --git a/test/DistEvents/DistDemoApp/TodoSummary.cs b/test/DistEvents/DistDemoApp.Shared/TodoSummary.cs similarity index 100% rename from test/DistEvents/DistDemoApp/TodoSummary.cs rename to test/DistEvents/DistDemoApp.Shared/TodoSummary.cs diff --git a/test/DistEvents/DistDemoApp/Migrations/20210825110134_Initial.Designer.cs b/test/DistEvents/DistDemoApp/Migrations/20210825110134_Initial.Designer.cs deleted file mode 100644 index 990ef3e967..0000000000 --- a/test/DistEvents/DistDemoApp/Migrations/20210825110134_Initial.Designer.cs +++ /dev/null @@ -1,61 +0,0 @@ -// -using System; -using DistDemoApp; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Volo.Abp.EntityFrameworkCore; - -namespace DistDemoApp.Migrations -{ - [DbContext(typeof(TodoDbContext))] - [Migration("20210825110134_Initial")] - partial class Initial - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("_Abp_DatabaseProvider", EfCoreDatabaseProvider.SqlServer) - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.9") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("DistDemoApp.TodoItem", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasMaxLength(40) - .HasColumnType("nvarchar(40)") - .HasColumnName("ConcurrencyStamp"); - - b.Property("CreationTime") - .HasColumnType("datetime2") - .HasColumnName("CreationTime"); - - b.Property("CreatorId") - .HasColumnType("uniqueidentifier") - .HasColumnName("CreatorId"); - - b.Property("ExtraProperties") - .HasColumnType("nvarchar(max)") - .HasColumnName("ExtraProperties"); - - b.Property("Text") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("nvarchar(128)"); - - b.HasKey("Id"); - - b.ToTable("TodoItems"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/test/DistEvents/DistDemoApp/Migrations/20210825110134_Initial.cs b/test/DistEvents/DistDemoApp/Migrations/20210825110134_Initial.cs deleted file mode 100644 index b84d1f433f..0000000000 --- a/test/DistEvents/DistDemoApp/Migrations/20210825110134_Initial.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace DistDemoApp.Migrations -{ - public partial class Initial : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TodoItems", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Text = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true), - CreationTime = table.Column(type: "datetime2", nullable: false), - CreatorId = table.Column(type: "uniqueidentifier", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoItems", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TodoItems"); - } - } -} diff --git a/test/DistEvents/DistDemoApp/Migrations/20210825112717_Added_Summary_Table.cs b/test/DistEvents/DistDemoApp/Migrations/20210825112717_Added_Summary_Table.cs deleted file mode 100644 index 7a7f167470..0000000000 --- a/test/DistEvents/DistDemoApp/Migrations/20210825112717_Added_Summary_Table.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace DistDemoApp.Migrations -{ - public partial class Added_Summary_Table : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TodoSummaries", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Year = table.Column(type: "int", nullable: false), - Month = table.Column(type: "tinyint", nullable: false), - Day = table.Column(type: "tinyint", nullable: false), - TotalCount = table.Column(type: "int", nullable: false), - ExtraProperties = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoSummaries", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TodoSummaries"); - } - } -} diff --git a/test/DistEvents/DistDemoApp/appsettings.json b/test/DistEvents/DistDemoApp/appsettings.json deleted file mode 100644 index 6bb7ba54ca..0000000000 --- a/test/DistEvents/DistDemoApp/appsettings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ConnectionStrings": { - "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=DistEventsDemo;Trusted_Connection=True" - } -} \ No newline at end of file diff --git a/test/DistEvents/DistEventsDemo.sln b/test/DistEvents/DistEventsDemo.sln index de7a36535a..e6c3348a5f 100644 --- a/test/DistEvents/DistEventsDemo.sln +++ b/test/DistEvents/DistEventsDemo.sln @@ -1,6 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistDemoApp", "DistDemoApp\DistDemoApp.csproj", "{10DBC6BC-1269-4C68-9F6C-12209A3FBF5B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistDemoApp.EfCoreRabbitMq", "DistDemoApp.EfCoreRabbitMq\DistDemoApp.EfCoreRabbitMq.csproj", "{10DBC6BC-1269-4C68-9F6C-12209A3FBF5B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistDemoApp.MongoDbKafka", "DistDemoApp.MongoDbKafka\DistDemoApp.MongoDbKafka.csproj", "{19762F48-4CDB-4723-A72F-D859C0DC815A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistDemoApp.Shared", "DistDemoApp.Shared\DistDemoApp.Shared.csproj", "{C515F4E2-0ED3-4561-BC58-FC633B50E2EB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistDemoApp.MongoDbRebus", "DistDemoApp.MongoDbRebus\DistDemoApp.MongoDbRebus.csproj", "{4FB63540-4CC5-4A7B-900B-F5FCD907456E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -12,5 +18,17 @@ Global {10DBC6BC-1269-4C68-9F6C-12209A3FBF5B}.Debug|Any CPU.Build.0 = Debug|Any CPU {10DBC6BC-1269-4C68-9F6C-12209A3FBF5B}.Release|Any CPU.ActiveCfg = Release|Any CPU {10DBC6BC-1269-4C68-9F6C-12209A3FBF5B}.Release|Any CPU.Build.0 = Release|Any CPU + {19762F48-4CDB-4723-A72F-D859C0DC815A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19762F48-4CDB-4723-A72F-D859C0DC815A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19762F48-4CDB-4723-A72F-D859C0DC815A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19762F48-4CDB-4723-A72F-D859C0DC815A}.Release|Any CPU.Build.0 = Release|Any CPU + {C515F4E2-0ED3-4561-BC58-FC633B50E2EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C515F4E2-0ED3-4561-BC58-FC633B50E2EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C515F4E2-0ED3-4561-BC58-FC633B50E2EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C515F4E2-0ED3-4561-BC58-FC633B50E2EB}.Release|Any CPU.Build.0 = Release|Any CPU + {4FB63540-4CC5-4A7B-900B-F5FCD907456E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FB63540-4CC5-4A7B-900B-F5FCD907456E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FB63540-4CC5-4A7B-900B-F5FCD907456E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FB63540-4CC5-4A7B-900B-F5FCD907456E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal