Skip to content

Commit 6e4d946

Browse files
committed
Add shared scope transaction between updating catalog product priceand store ProductPriceChangedIntegrationEvent. Added service to encapsulate logic for storage of integration event logs.
1 parent dee6ea7 commit 6e4d946

5 files changed

Lines changed: 96 additions & 23 deletions

File tree

src/BuildingBlocks/EventBus/IntegrationEventLogEF/IntegrationEventLogEntry.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF
88
{
99
public class IntegrationEventLogEntry
1010
{
11+
private IntegrationEventLogEntry() { }
1112
public IntegrationEventLogEntry(IntegrationEvent @event)
1213
{
1314
EventId = @event.Id;

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

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using Microsoft.AspNetCore.Mvc;
1+
using Catalog.API.IntegrationEvents;
2+
using Microsoft.AspNetCore.Mvc;
23
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.Storage;
35
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
46
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF;
57
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
@@ -17,18 +19,18 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
1719
public class CatalogController : ControllerBase
1820
{
1921
private readonly CatalogContext _catalogContext;
20-
private readonly IntegrationEventLogContext _integrationEventLogContext;
2122
private readonly IOptionsSnapshot<Settings> _settings;
2223
private readonly IEventBus _eventBus;
24+
private readonly IIntegrationEventLogService _integrationEventLogService;
2325

24-
public CatalogController(CatalogContext catalogContext, IntegrationEventLogContext integrationEventLogContext, IOptionsSnapshot<Settings> settings, IEventBus eventBus)
26+
public CatalogController(CatalogContext Context, IOptionsSnapshot<Settings> settings, IEventBus eventBus, IIntegrationEventLogService integrationEventLogService)
2527
{
26-
_catalogContext = catalogContext;
27-
_integrationEventLogContext = integrationEventLogContext;
28+
_catalogContext = Context;
2829
_settings = settings;
2930
_eventBus = eventBus;
31+
_integrationEventLogService = integrationEventLogService;
3032

31-
((DbContext)catalogContext).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
33+
((DbContext)Context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
3234
}
3335

3436
// GET api/v1/[controller]/items[?pageSize=3&pageIndex=10]
@@ -149,21 +151,21 @@ public async Task<IActionResult> EditProduct([FromBody]CatalogItem product)
149151
{
150152
var oldPrice = item.Price;
151153
item.Price = product.Price;
152-
_catalogContext.CatalogItems.Update(item);
153-
154154
var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice);
155-
var eventLogEntry = new IntegrationEventLogEntry(@event);
156-
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry);
157155

158-
await _integrationEventLogContext.SaveChangesAsync();
159-
await _catalogContext.SaveChangesAsync();
160-
156+
using (var transaction = _catalogContext.Database.BeginTransaction())
157+
{
158+
_catalogContext.CatalogItems.Update(item);
159+
await _catalogContext.SaveChangesAsync();
160+
161+
await _integrationEventLogService.SaveEventAsync(@event);
162+
163+
transaction.Commit();
164+
}
165+
161166
_eventBus.Publish(@event);
162-
163-
eventLogEntry.TimesSent++;
164-
eventLogEntry.State = EventStateEnum.Published;
165-
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry);
166-
await _integrationEventLogContext.SaveChangesAsync();
167+
168+
await _integrationEventLogService.MarkEventAsPublishedAsync(@event);
167169
}
168170

169171
return Ok();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
7+
namespace Catalog.API.IntegrationEvents
8+
{
9+
public interface IIntegrationEventLogService
10+
{
11+
Task SaveEventAsync(IntegrationEvent @event);
12+
Task MarkEventAsPublishedAsync(IntegrationEvent @event);
13+
}
14+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
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;
11+
12+
namespace Catalog.API.IntegrationEvents
13+
{
14+
public class IntegrationEventLogService : IIntegrationEventLogService
15+
{
16+
private readonly IntegrationEventLogContext _integrationEventLogContext;
17+
private readonly CatalogContext _catalogContext;
18+
19+
public IntegrationEventLogService(CatalogContext catalogContext)
20+
{
21+
_catalogContext = catalogContext;
22+
_integrationEventLogContext = new IntegrationEventLogContext(
23+
new DbContextOptionsBuilder<IntegrationEventLogContext>()
24+
.UseSqlServer(catalogContext.Database.GetDbConnection())
25+
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning))
26+
.Options);
27+
}
28+
29+
public Task SaveEventAsync(IntegrationEvent @event)
30+
{
31+
var eventLogEntry = new IntegrationEventLogEntry(@event);
32+
33+
// as a constraint this transaction has to be done together with a catalogContext transaction
34+
_integrationEventLogContext.Database.UseTransaction(_catalogContext.Database.CurrentTransaction.GetDbTransaction());
35+
_integrationEventLogContext.IntegrationEventLogs.Add(eventLogEntry);
36+
37+
return _integrationEventLogContext.SaveChangesAsync();
38+
}
39+
40+
public Task MarkEventAsPublishedAsync(IntegrationEvent @event)
41+
{
42+
var eventLogEntry = _integrationEventLogContext.IntegrationEventLogs.Single(ie => ie.EventId == @event.Id);
43+
eventLogEntry.TimesSent++;
44+
eventLogEntry.State = EventStateEnum.Published;
45+
46+
_integrationEventLogContext.IntegrationEventLogs.Update(eventLogEntry);
47+
48+
return _integrationEventLogContext.SaveChangesAsync();
49+
}
50+
}
51+
}

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Microsoft.eShopOnContainers.Services.Catalog.API
22
{
3+
using global::Catalog.API.IntegrationEvents;
34
using Microsoft.AspNetCore.Builder;
45
using Microsoft.AspNetCore.Hosting;
56
using Microsoft.EntityFrameworkCore;
@@ -12,6 +13,7 @@
1213
using Microsoft.Extensions.DependencyInjection;
1314
using Microsoft.Extensions.Logging;
1415
using Microsoft.Extensions.Options;
16+
using System.Data.SqlClient;
1517
using System.Reflection;
1618

1719
public class Startup
@@ -37,9 +39,11 @@ public Startup(IHostingEnvironment env)
3739

3840
public void ConfigureServices(IServiceCollection services)
3941
{
42+
var sqlConnection = new SqlConnection(Configuration["ConnectionString"]);
43+
4044
services.AddDbContext<CatalogContext>(c =>
4145
{
42-
c.UseSqlServer(Configuration["ConnectionString"]);
46+
c.UseSqlServer(sqlConnection);
4347
// Changing default behavior when client evaluation occurs to throw.
4448
// Default in EF Core would be to log a warning when client evaluation is performed.
4549
c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
@@ -48,7 +52,7 @@ public void ConfigureServices(IServiceCollection services)
4852

4953
services.AddDbContext<IntegrationEventLogContext>(c =>
5054
{
51-
c.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Catalog.API"));
55+
c.UseSqlServer(sqlConnection, b => b.MigrationsAssembly("Catalog.API"));
5256
c.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
5357
});
5458

@@ -77,6 +81,8 @@ public void ConfigureServices(IServiceCollection services)
7781
.AllowCredentials());
7882
});
7983

84+
services.AddTransient<IIntegrationEventLogService, IntegrationEventLogService>();
85+
8086
var serviceProvider = services.BuildServiceProvider();
8187
var configuration = serviceProvider.GetRequiredService<IOptionsSnapshot<Settings>>().Value;
8288
services.AddSingleton<IEventBus>(new EventBusRabbitMQ(configuration.EventBusConnection));
@@ -106,9 +112,8 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF
106112
//Seed Data
107113
CatalogContextSeed.SeedAsync(app, loggerFactory)
108114
.Wait();
109-
110-
// TODO: move this creation to a db initializer
111-
integrationEventLogContext.Database.Migrate();
115+
116+
integrationEventLogContext.Database.Migrate();
112117

113118
}
114119
}

0 commit comments

Comments
 (0)