Browse Source

Merge pull request #137 from dotnet/feedback/IntegrationEventLogService-refactor

IntegrationEventLogService refactoring
pull/142/head
RamonTC 7 years ago
committed by GitHub
parent
commit
352739cf9f
5 changed files with 55 additions and 60 deletions
  1. +3
    -2
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs
  2. +18
    -16
      src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs
  3. +16
    -11
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  4. +13
    -23
      src/Services/Catalog/Catalog.API/Startup.cs
  5. +5
    -8
      test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs

src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs → src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs View File

@ -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);
} }
} }

src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs → src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs View File

@ -1,37 +1,39 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
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 Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
using Microsoft.EntityFrameworkCore.Infrastructure;
using System;
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(_catalogContext.Database.CurrentTransaction.GetDbTransaction());
_integrationEventLogContext.Database.UseTransaction(transaction);
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry); _integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry);
return _integrationEventLogContext.SaveChangesAsync(); return _integrationEventLogContext.SaveChangesAsync();

+ 16
- 11
src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs View File

@ -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;
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
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();
} }


+ 13
- 23
src/Services/Catalog/Catalog.API/Startup.cs View File

@ -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();
} }


+ 5
- 8
test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs View File

@ -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()
{
Id = int.Parse(itemToModify.ProductId),
Price = newPrice
};
return JsonConvert.SerializeObject(item);
var catalogProduct = catalogProducts.Data.Single(pr => pr.Id == int.Parse(itemToModify.ProductId));
catalogProduct.Price = newPrice;
return JsonConvert.SerializeObject(catalogProduct);
} }
private CustomerBasket ComposeBasket(string customerId, IEnumerable<CatalogItem> items) private CustomerBasket ComposeBasket(string customerId, IEnumerable<CatalogItem> items)


Loading…
Cancel
Save