Merge pull request #137 from dotnet/feedback/IntegrationEventLogService-refactor
IntegrationEventLogService refactoring
This commit is contained in:
commit
352739cf9f
@ -1,14 +1,15 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Catalog.API.IntegrationEvents
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
|
||||||
{
|
{
|
||||||
public interface IIntegrationEventLogService
|
public interface IIntegrationEventLogService
|
||||||
{
|
{
|
||||||
Task SaveEventAsync(IntegrationEvent @event);
|
Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction);
|
||||||
Task MarkEventAsPublishedAsync(IntegrationEvent @event);
|
Task MarkEventAsPublishedAsync(IntegrationEvent @event);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,37 +1,39 @@
|
|||||||
using System;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Collections.Generic;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
using System;
|
||||||
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
|
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
|
||||||
{
|
{
|
||||||
public class IntegrationEventLogService : IIntegrationEventLogService
|
public class IntegrationEventLogService : IIntegrationEventLogService
|
||||||
{
|
{
|
||||||
private readonly IntegrationEventLogContext _integrationEventLogContext;
|
private readonly IntegrationEventLogContext _integrationEventLogContext;
|
||||||
private readonly CatalogContext _catalogContext;
|
private readonly DbConnection _dbConnection;
|
||||||
|
|
||||||
public IntegrationEventLogService(CatalogContext catalogContext)
|
public IntegrationEventLogService(DbConnection dbConnection)
|
||||||
{
|
{
|
||||||
_catalogContext = catalogContext;
|
_dbConnection = dbConnection?? throw new ArgumentNullException("dbConnection");
|
||||||
_integrationEventLogContext = new IntegrationEventLogContext(
|
_integrationEventLogContext = new IntegrationEventLogContext(
|
||||||
new DbContextOptionsBuilder<IntegrationEventLogContext>()
|
new DbContextOptionsBuilder<IntegrationEventLogContext>()
|
||||||
.UseSqlServer(catalogContext.Database.GetDbConnection())
|
.UseSqlServer(_dbConnection)
|
||||||
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
|
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
|
||||||
.Options);
|
.Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SaveEventAsync(IntegrationEvent @event)
|
public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction)
|
||||||
{
|
{
|
||||||
|
if(transaction == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("transaction", $"A {typeof(DbTransaction).FullName} is required as a pre-requisite to save the event.");
|
||||||
|
}
|
||||||
|
|
||||||
var eventLogEntry = new IntegrationEventLogEntry(@event);
|
var eventLogEntry = new IntegrationEventLogEntry(@event);
|
||||||
|
|
||||||
// as a constraint this transaction has to be done together with a catalogContext transaction
|
_integrationEventLogContext.Database.UseTransaction(transaction);
|
||||||
_integrationEventLogContext.Database.UseTransaction(_catalogContext.Database.CurrentTransaction.GetDbTransaction());
|
|
||||||
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry);
|
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry);
|
||||||
|
|
||||||
return _integrationEventLogContext.SaveChangesAsync();
|
return _integrationEventLogContext.SaveChangesAsync();
|
@ -1,18 +1,20 @@
|
|||||||
using Catalog.API.IntegrationEvents;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
|
||||||
|
|
||||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
||||||
{
|
{
|
||||||
@ -22,14 +24,14 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
private readonly CatalogContext _catalogContext;
|
private readonly CatalogContext _catalogContext;
|
||||||
private readonly IOptionsSnapshot<Settings> _settings;
|
private readonly IOptionsSnapshot<Settings> _settings;
|
||||||
private readonly IEventBus _eventBus;
|
private readonly IEventBus _eventBus;
|
||||||
private readonly IIntegrationEventLogService _integrationEventLogService;
|
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
|
||||||
|
|
||||||
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, IIntegrationEventLogService integrationEventLogService)
|
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
|
||||||
{
|
{
|
||||||
_catalogContext = Context;
|
_catalogContext = Context;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
_eventBus = eventBus;
|
_eventBus = eventBus;
|
||||||
_integrationEventLogService = integrationEventLogService;
|
_integrationEventLogServiceFactory = integrationEventLogServiceFactory;
|
||||||
|
|
||||||
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||||
}
|
}
|
||||||
@ -161,7 +163,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
|
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
|
||||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
||||||
var strategy = _catalogContext.Database.CreateExecutionStrategy();
|
var strategy = _catalogContext.Database.CreateExecutionStrategy();
|
||||||
|
var eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection());
|
||||||
await strategy.ExecuteAsync(async () =>
|
await strategy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
|
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
|
||||||
@ -172,7 +174,10 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
|
|
||||||
//Save to EventLog only if product price changed
|
//Save to EventLog only if product price changed
|
||||||
if (raiseProductPriceChangedEvent)
|
if (raiseProductPriceChangedEvent)
|
||||||
await _integrationEventLogService.SaveEventAsync(priceChangedEvent);
|
{
|
||||||
|
|
||||||
|
await eventLogService.SaveEventAsync(priceChangedEvent, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
@ -183,9 +188,9 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
|
|||||||
if (raiseProductPriceChangedEvent)
|
if (raiseProductPriceChangedEvent)
|
||||||
{
|
{
|
||||||
_eventBus.Publish(priceChangedEvent);
|
_eventBus.Publish(priceChangedEvent);
|
||||||
await _integrationEventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
|
await eventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
namespace Microsoft.eShopOnContainers.Services.Catalog.API
|
namespace Microsoft.eShopOnContainers.Services.Catalog.API
|
||||||
{
|
{
|
||||||
using global::Catalog.API.IntegrationEvents;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -8,12 +7,14 @@
|
|||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
|
||||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
|
||||||
|
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
|
||||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Data.SqlClient;
|
using System.Data.SqlClient;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
@ -40,14 +41,11 @@
|
|||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
//Using the same SqlConnection for both DbContexts (CatalogContext and IntegrationEventLogContext)
|
|
||||||
var sqlConnection = new SqlConnection(Configuration["ConnectionString"]);
|
|
||||||
|
|
||||||
services.AddDbContext<CatalogContext>(options =>
|
services.AddDbContext<CatalogContext>(options =>
|
||||||
{
|
{
|
||||||
options.UseSqlServer(sqlConnection,
|
options.UseSqlServer(Configuration["ConnectionString"],
|
||||||
sqlServerOptionsAction: sqlOptions =>
|
sqlServerOptionsAction: sqlOptions =>
|
||||||
{
|
{
|
||||||
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
|
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
|
||||||
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
||||||
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
|
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
|
||||||
@ -58,19 +56,6 @@
|
|||||||
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
|
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddDbContext<IntegrationEventLogContext>(options =>
|
|
||||||
{
|
|
||||||
options.UseSqlServer(sqlConnection,
|
|
||||||
sqlServerOptionsAction: sqlOptions =>
|
|
||||||
{
|
|
||||||
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
|
|
||||||
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
|
|
||||||
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
|
|
||||||
});
|
|
||||||
|
|
||||||
options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
|
|
||||||
});
|
|
||||||
|
|
||||||
services.Configure<Settings>(Configuration);
|
services.Configure<Settings>(Configuration);
|
||||||
|
|
||||||
// Add framework services.
|
// Add framework services.
|
||||||
@ -96,8 +81,9 @@
|
|||||||
.AllowCredentials());
|
.AllowCredentials());
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddTransient<IIntegrationEventLogService, IntegrationEventLogService>();
|
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
|
||||||
|
sp => (DbConnection c) => new IntegrationEventLogService(c));
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
var serviceProvider = services.BuildServiceProvider();
|
||||||
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
|
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
|
||||||
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
|
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
|
||||||
@ -105,7 +91,7 @@
|
|||||||
services.AddMvc();
|
services.AddMvc();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IntegrationEventLogContext integrationEventLogContext)
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
//Configure logs
|
//Configure logs
|
||||||
|
|
||||||
@ -127,7 +113,11 @@
|
|||||||
//Seed Data
|
//Seed Data
|
||||||
CatalogContextSeed.SeedAsync(app, loggerFactory)
|
CatalogContextSeed.SeedAsync(app, loggerFactory)
|
||||||
.Wait();
|
.Wait();
|
||||||
|
|
||||||
|
var integrationEventLogContext = new IntegrationEventLogContext(
|
||||||
|
new DbContextOptionsBuilder<IntegrationEventLogContext>()
|
||||||
|
.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API"))
|
||||||
|
.Options);
|
||||||
integrationEventLogContext.Database.Migrate();
|
integrationEventLogContext.Database.Migrate();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ namespace FunctionalTests.Services
|
|||||||
var itemToModify = basket.Items[2];
|
var itemToModify = basket.Items[2];
|
||||||
var oldPrice = itemToModify.UnitPrice;
|
var oldPrice = itemToModify.UnitPrice;
|
||||||
var newPrice = oldPrice + priceModification;
|
var newPrice = oldPrice + priceModification;
|
||||||
var pRes = await catalogClient.PostAsync(CatalogScenariosBase.Post.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice), UTF8Encoding.UTF8, "application/json"));
|
var pRes = await catalogClient.PostAsync(CatalogScenariosBase.Post.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), UTF8Encoding.UTF8, "application/json"));
|
||||||
|
|
||||||
var modifiedCatalogProducts = await GetCatalogAsync(catalogClient);
|
var modifiedCatalogProducts = await GetCatalogAsync(catalogClient);
|
||||||
|
|
||||||
@ -100,14 +100,11 @@ namespace FunctionalTests.Services
|
|||||||
return JsonConvert.DeserializeObject<PaginatedItemsViewModel<CatalogItem>>(items);
|
return JsonConvert.DeserializeObject<PaginatedItemsViewModel<CatalogItem>>(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ChangePrice(BasketItem itemToModify, decimal newPrice)
|
private string ChangePrice(BasketItem itemToModify, decimal newPrice, PaginatedItemsViewModel<CatalogItem> catalogProducts)
|
||||||
{
|
{
|
||||||
var item = new CatalogItem()
|
var catalogProduct = catalogProducts.Data.Single(pr => pr.Id == int.Parse(itemToModify.ProductId));
|
||||||
{
|
catalogProduct.Price = newPrice;
|
||||||
Id = int.Parse(itemToModify.ProductId),
|
return JsonConvert.SerializeObject(catalogProduct);
|
||||||
Price = newPrice
|
|
||||||
};
|
|
||||||
return JsonConvert.SerializeObject(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CustomerBasket ComposeBasket(string customerId, IEnumerable<CatalogItem> items)
|
private CustomerBasket ComposeBasket(string customerId, IEnumerable<CatalogItem> items)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user