diff --git a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs index 2b31e6681..0b68e56a0 100644 --- a/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs +++ b/src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs @@ -8,6 +8,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF { public class IntegrationEventLogEntry { + private IntegrationEventLogEntry() { } public IntegrationEventLogEntry(IntegrationEvent @event) { EventId = @event.Id; diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 2feaa423d..bbc30a772 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -1,5 +1,7 @@ -using Microsoft.AspNetCore.Mvc; +using Catalog.API.IntegrationEvents; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; @@ -17,18 +19,18 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers public class CatalogController : ControllerBase { private readonly CatalogContext _catalogContext; - private readonly IntegrationEventLogContext _integrationEventLogContext; private readonly IOptionsSnapshot _settings; private readonly IEventBus _eventBus; + private readonly IIntegrationEventLogService _integrationEventLogService; - public CatalogController(CatalogContext catalogContext, IntegrationEventLogContext integrationEventLogContext, IOptionsSnapshot settings, IEventBus eventBus) + public CatalogController(CatalogContext Context, IOptionsSnapshot settings, IEventBus eventBus, IIntegrationEventLogService integrationEventLogService) { - _catalogContext = catalogContext; - _integrationEventLogContext = integrationEventLogContext; + _catalogContext = Context; _settings = settings; _eventBus = eventBus; + _integrationEventLogService = integrationEventLogService; - ((DbContext)catalogContext).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } // GET api/v1/[controller]/items[?pageSize=3&pageIndex=10] @@ -149,21 +151,21 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers { var oldPrice = item.Price; item.Price = product.Price; - _catalogContext.CatalogItems.Update(item); - var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice); - var eventLogEntry = new IntegrationEventLogEntry(@event); - _integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); - await _integrationEventLogContext.SaveChangesAsync(); - await _catalogContext.SaveChangesAsync(); - + using (var transaction = _catalogContext.Database.BeginTransaction()) + { + _catalogContext.CatalogItems.Update(item); + await _catalogContext.SaveChangesAsync(); + + await _integrationEventLogService.SaveEventAsync(@event); + + transaction.Commit(); + } + _eventBus.Publish(@event); - - eventLogEntry.TimesSent++; - eventLogEntry.State = EventStateEnum.Published; - _integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); - await _integrationEventLogContext.SaveChangesAsync(); + + await _integrationEventLogService.MarkEventAsPublishedAsync(@event); } return Ok(); diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs new file mode 100644 index 000000000..423a0eb98 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs @@ -0,0 +1,14 @@ +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Catalog.API.IntegrationEvents +{ + public interface IIntegrationEventLogService + { + Task SaveEventAsync(IntegrationEvent @event); + Task MarkEventAsPublishedAsync(IntegrationEvent @event); + } +} diff --git a/src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs b/src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs new file mode 100644 index 000000000..e9859c5ae --- /dev/null +++ b/src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; +using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Catalog.API.IntegrationEvents +{ + public class IntegrationEventLogService : IIntegrationEventLogService + { + private readonly IntegrationEventLogContext _integrationEventLogContext; + private readonly CatalogContext _catalogContext; + + public IntegrationEventLogService(CatalogContext catalogContext) + { + _catalogContext = catalogContext; + _integrationEventLogContext = new IntegrationEventLogContext( + new DbContextOptionsBuilder() + .UseSqlServer(catalogContext.Database.GetDbConnection()) + .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)) + .Options); + } + + public Task SaveEventAsync(IntegrationEvent @event) + { + var eventLogEntry = new IntegrationEventLogEntry(@event); + + // as a constraint this transaction has to be done together with a catalogContext transaction + _integrationEventLogContext.Database.UseTransaction(_catalogContext.Database.CurrentTransaction.GetDbTransaction()); + _integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); + + return _integrationEventLogContext.SaveChangesAsync(); + } + + public Task MarkEventAsPublishedAsync(IntegrationEvent @event) + { + var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id); + eventLogEntry.TimesSent++; + eventLogEntry.State = EventStateEnum.Published; + + _integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry); + + return _integrationEventLogContext.SaveChangesAsync(); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Startup.cs b/src/Services/Catalog/Catalog.API/Startup.cs index 05c6409fe..1598eb5a0 100644 --- a/src/Services/Catalog/Catalog.API/Startup.cs +++ b/src/Services/Catalog/Catalog.API/Startup.cs @@ -1,5 +1,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API { + using global::Catalog.API.IntegrationEvents; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -12,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using System.Data.SqlClient; using System.Reflection; public class Startup @@ -37,9 +39,11 @@ public void ConfigureServices(IServiceCollection services) { + var sqlConnection = new SqlConnection(Configuration["ConnectionString"]); + services.AddDbContext(c => { - c.UseSqlServer(Configuration["ConnectionString"]); + c.UseSqlServer(sqlConnection); // Changing default behavior when client evaluation occurs to throw. // Default in EF Core would be to log a warning when client evaluation is performed. c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); @@ -48,7 +52,7 @@ services.AddDbContext(c => { - c.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API")); + c.UseSqlServer(sqlConnection, b => b.MigrationsAssembly("Catalog.API")); c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning)); }); @@ -77,6 +81,8 @@ .AllowCredentials()); }); + services.AddTransient(); + var serviceProvider = services.BuildServiceProvider(); var configuration = serviceProvider.GetRequiredService>().Value; services.AddSingleton(new EventBusRabbitMQ(configuration.EventBusConnection)); @@ -106,9 +112,8 @@ //Seed Data CatalogContextSeed.SeedAsync(app, loggerFactory) .Wait(); - - // TODO: move this creation to a db initializer - integrationEventLogContext.Database.Migrate(); + + integrationEventLogContext.Database.Migrate(); } }