Skip to content

Commit 352739c

Browse files
Merge pull request dotnet-architecture#137 from dotnet/feedback/IntegrationEventLogService-refactor
IntegrationEventLogService refactoring
2 parents 0d78461 + d8d1e5a commit 352739c

5 files changed

Lines changed: 55 additions & 60 deletions

File tree

src/Services/Catalog/Catalog.API/IntegrationEvents/IIntegrationEventLogService.cs renamed to src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IIntegrationEventLogService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
22
using System;
33
using System.Collections.Generic;
4+
using System.Data.Common;
45
using System.Linq;
56
using System.Threading.Tasks;
67

7-
namespace Catalog.API.IntegrationEvents
8+
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
89
{
910
public interface IIntegrationEventLogService
1011
{
11-
Task SaveEventAsync(IntegrationEvent @event);
12+
Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction);
1213
Task MarkEventAsPublishedAsync(IntegrationEvent @event);
1314
}
1415
}

src/Services/Catalog/Catalog.API/IntegrationEvents/IntegrationEventLogService.cs renamed to src/BuildingBlocks/EventBus/IntegrationEventLogEF/Services/IntegrationEventLogService.cs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using Microsoft.EntityFrameworkCore;
2+
using Microsoft.EntityFrameworkCore.Infrastructure;
3+
using Microsoft.EntityFrameworkCore.Storage;
4+
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
5+
using System.Data.Common;
36
using System.Linq;
47
using System.Threading.Tasks;
5-
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
6-
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
7-
using Microsoft.EntityFrameworkCore;
8-
using Microsoft.EntityFrameworkCore.Storage;
9-
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
10-
using Microsoft.EntityFrameworkCore.Infrastructure;
8+
using System;
119

12-
namespace Catalog.API.IntegrationEvents
10+
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services
1311
{
1412
public class IntegrationEventLogService : IIntegrationEventLogService
1513
{
1614
private readonly IntegrationEventLogContext _integrationEventLogContext;
17-
private readonly CatalogContext _catalogContext;
15+
private readonly DbConnection _dbConnection;
1816

19-
public IntegrationEventLogService(CatalogContext catalogContext)
17+
public IntegrationEventLogService(DbConnection dbConnection)
2018
{
21-
_catalogContext = catalogContext;
19+
_dbConnection = dbConnection?? throw new ArgumentNullException("dbConnection");
2220
_integrationEventLogContext = new IntegrationEventLogContext(
2321
new DbContextOptionsBuilder<IntegrationEventLogContext>()
24-
.UseSqlServer(catalogContext.Database.GetDbConnection())
22+
.UseSqlServer(_dbConnection)
2523
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
2624
.Options);
2725
}
2826

29-
public Task SaveEventAsync(IntegrationEvent @event)
27+
public Task SaveEventAsync(IntegrationEvent @event, DbTransaction transaction)
3028
{
29+
if(transaction == null)
30+
{
31+
throw new ArgumentNullException("transaction", $"A {typeof(DbTransaction).FullName} is required as a pre-requisite to save the event.");
32+
}
33+
3134
var eventLogEntry = new IntegrationEventLogEntry(@event);
3235

33-
// as a constraint this transaction has to be done together with a catalogContext transaction
34-
_integrationEventLogContext.Database.UseTransaction(_catalogContext.Database.CurrentTransaction.GetDbTransaction());
36+
_integrationEventLogContext.Database.UseTransaction(transaction);
3537
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry);
3638

3739
return _integrationEventLogContext.SaveChangesAsync();

src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
using Catalog.API.IntegrationEvents;
2-
using Microsoft.AspNetCore.Mvc;
1+
using Microsoft.AspNetCore.Mvc;
32
using Microsoft.EntityFrameworkCore;
43
using Microsoft.EntityFrameworkCore.Storage;
4+
using Microsoft.EntityFrameworkCore.Infrastructure;
55
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
6-
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
7-
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
6+
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
87
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
98
using Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events;
109
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
1110
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
1211
using Microsoft.Extensions.Options;
12+
using System;
1313
using System.Collections.Generic;
14+
using System.Data.Common;
1415
using System.Linq;
1516
using System.Threading.Tasks;
17+
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
1618

1719
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
1820
{
@@ -22,14 +24,14 @@ public class CatalogController : ControllerBase
2224
private readonly CatalogContext _catalogContext;
2325
private readonly IOptionsSnapshot<Settings> _settings;
2426
private readonly IEventBus _eventBus;
25-
private readonly IIntegrationEventLogService _integrationEventLogService;
27+
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory;
2628

27-
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, IIntegrationEventLogService integrationEventLogService)
29+
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory)
2830
{
2931
_catalogContext = Context;
3032
_settings = settings;
3133
_eventBus = eventBus;
32-
_integrationEventLogService = integrationEventLogService;
34+
_integrationEventLogServiceFactory = integrationEventLogServiceFactory;
3335

3436
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
3537
}
@@ -161,7 +163,7 @@ public async Task<IActionResult> UpdateProduct([FromBody]CatalogItem productToUp
161163
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction():
162164
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
163165
var strategy = _catalogContext.Database.CreateExecutionStrategy();
164-
166+
var eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection());
165167
await strategy.ExecuteAsync(async () =>
166168
{
167169
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
@@ -172,7 +174,10 @@ await strategy.ExecuteAsync(async () =>
172174

173175
//Save to EventLog only if product price changed
174176
if (raiseProductPriceChangedEvent)
175-
await _integrationEventLogService.SaveEventAsync(priceChangedEvent);
177+
{
178+
179+
await eventLogService.SaveEventAsync(priceChangedEvent, _catalogContext.Database.CurrentTransaction.GetDbTransaction());
180+
}
176181

177182
transaction.Commit();
178183
}
@@ -183,9 +188,9 @@ await strategy.ExecuteAsync(async () =>
183188
if (raiseProductPriceChangedEvent)
184189
{
185190
_eventBus.Publish(priceChangedEvent);
186-
await _integrationEventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
191+
await eventLogService.MarkEventAsPublishedAsync(priceChangedEvent);
187192
}
188-
193+
189194
return Ok();
190195
}
191196

src/Services/Catalog/Catalog.API/Startup.cs

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
namespace Microsoft.eShopOnContainers.Services.Catalog.API
22
{
3-
using global::Catalog.API.IntegrationEvents;
43
using Microsoft.AspNetCore.Builder;
54
using Microsoft.AspNetCore.Hosting;
65
using Microsoft.EntityFrameworkCore;
76
using Microsoft.EntityFrameworkCore.Infrastructure;
87
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
98
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
109
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
10+
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services;
1111
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
1212
using Microsoft.Extensions.Configuration;
1313
using Microsoft.Extensions.DependencyInjection;
1414
using Microsoft.Extensions.Logging;
1515
using Microsoft.Extensions.Options;
1616
using System;
17+
using System.Data.Common;
1718
using System.Data.SqlClient;
1819
using System.Reflection;
1920

@@ -40,14 +41,11 @@ public Startup(IHostingEnvironment env)
4041

4142
public void ConfigureServices(IServiceCollection services)
4243
{
43-
//Using the same SqlConnection for both DbContexts (CatalogContext and IntegrationEventLogContext)
44-
var sqlConnection = new SqlConnection(Configuration["ConnectionString"]);
45-
4644
services.AddDbContext<CatalogContext>(options =>
4745
{
48-
options.UseSqlServer(sqlConnection,
46+
options.UseSqlServer(Configuration["ConnectionString"],
4947
sqlServerOptionsAction: sqlOptions =>
50-
{
48+
{
5149
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
5250
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
5351
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
@@ -58,19 +56,6 @@ public void ConfigureServices(IServiceCollection services)
5856
//Check Client vs. Server evaluation: https://docs.microsoft.com/en-us/ef/core/querying/client-eval
5957
});
6058

61-
services.AddDbContext<IntegrationEventLogContext>(options =>
62-
{
63-
options.UseSqlServer(sqlConnection,
64-
sqlServerOptionsAction: sqlOptions =>
65-
{
66-
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
67-
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
68-
sqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
69-
});
70-
71-
options.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
72-
});
73-
7459
services.Configure<Settings>(Configuration);
7560

7661
// Add framework services.
@@ -96,16 +81,17 @@ public void ConfigureServices(IServiceCollection services)
9681
.AllowCredentials());
9782
});
9883

99-
services.AddTransient<IIntegrationEventLogService, IntegrationEventLogService>();
100-
84+
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
85+
sp => (DbConnection c) => new IntegrationEventLogService(c));
86+
10187
var serviceProvider = services.BuildServiceProvider();
10288
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
10389
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
10490

10591
services.AddMvc();
10692
}
10793

108-
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IntegrationEventLogContext integrationEventLogContext)
94+
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
10995
{
11096
//Configure logs
11197

@@ -127,7 +113,11 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
127113
//Seed Data
128114
CatalogContextSeed.SeedAsync(app, loggerFactory)
129115
.Wait();
130-
116+
117+
var integrationEventLogContext = new IntegrationEventLogContext(
118+
new DbContextOptionsBuilder<IntegrationEventLogContext>()
119+
.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API"))
120+
.Options);
131121
integrationEventLogContext.Database.Migrate();
132122

133123
}

test/Services/FunctionalTests/Services/IntegrationEventsScenarios.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public async Task Post_update_product_price_and_catalog_and_basket_list_modified
4343
var itemToModify = basket.Items[2];
4444
var oldPrice = itemToModify.UnitPrice;
4545
var newPrice = oldPrice + priceModification;
46-
var pRes = await catalogClient.PostAsync(CatalogScenariosBase.Post.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice), UTF8Encoding.UTF8, "application/json"));
46+
var pRes = await catalogClient.PostAsync(CatalogScenariosBase.Post.UpdateCatalogProduct, new StringContent(ChangePrice(itemToModify, newPrice, originalCatalogProducts), UTF8Encoding.UTF8, "application/json"));
4747

4848
var modifiedCatalogProducts = await GetCatalogAsync(catalogClient);
4949

@@ -100,14 +100,11 @@ private async Task<PaginatedItemsViewModel<CatalogItem>> GetCatalogAsync(HttpCl
100100
return JsonConvert.DeserializeObject<PaginatedItemsViewModel<CatalogItem>>(items);
101101
}
102102

103-
private string ChangePrice(BasketItem itemToModify, decimal newPrice)
103+
private string ChangePrice(BasketItem itemToModify, decimal newPrice, PaginatedItemsViewModel<CatalogItem> catalogProducts)
104104
{
105-
var item = new CatalogItem()
106-
{
107-
Id = int.Parse(itemToModify.ProductId),
108-
Price = newPrice
109-
};
110-
return JsonConvert.SerializeObject(item);
105+
var catalogProduct = catalogProducts.Data.Single(pr => pr.Id == int.Parse(itemToModify.ProductId));
106+
catalogProduct.Price = newPrice;
107+
return JsonConvert.SerializeObject(catalogProduct);
111108
}
112109

113110
private CustomerBasket ComposeBasket(string customerId, IEnumerable<CatalogItem> items)

0 commit comments

Comments
 (0)