From f502c2388fde2af1c9971d64965a50a0919aefa1 Mon Sep 17 00:00:00 2001 From: dsanz Date: Tue, 14 Mar 2017 09:47:36 +0100 Subject: [PATCH] Add persistence of published integration events for Catalog (the only microservice publishing integration events by the moment). --- .../Catalog/Catalog.API/Catalog.API.csproj | 1 + .../Controllers/CatalogController.cs | 30 +++-- .../Infrastructure/CatalogContext.cs | 32 ++++- .../20170314083211_AddEventTable.Designer.cs | 123 ++++++++++++++++++ .../20170314083211_AddEventTable.cs | 34 +++++ .../Migrations/CatalogContextModelSnapshot.cs | 44 +++++-- .../Catalog/CatalogPriceChanged.cs | 2 +- .../EventStateEnum.cs} | 7 +- .../Infrastructure/Data/IntegrationEvent.cs | 26 ++++ .../Common/Infrastructure/EventBus.cs | 6 +- .../Common/Infrastructure/IEventBus.cs | 6 +- .../IIntegrationEventHandler.cs | 2 +- .../Infrastructure/IntegrationEventBase.cs | 16 +++ 13 files changed, 300 insertions(+), 29 deletions(-) create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs create mode 100644 src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs rename src/Services/Common/Infrastructure/{IIntegrationEvent.cs => Data/EventStateEnum.cs} (59%) create mode 100644 src/Services/Common/Infrastructure/Data/IntegrationEvent.cs create mode 100644 src/Services/Common/Infrastructure/IntegrationEventBase.cs diff --git a/src/Services/Catalog/Catalog.API/Catalog.API.csproj b/src/Services/Catalog/Catalog.API/Catalog.API.csproj index 1c0fddf92..387908b90 100644 --- a/src/Services/Catalog/Catalog.API/Catalog.API.csproj +++ b/src/Services/Catalog/Catalog.API/Catalog.API.csproj @@ -45,6 +45,7 @@ + diff --git a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs index 9d8cc0b11..71dcd5d1e 100644 --- a/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs +++ b/src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs @@ -5,7 +5,9 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.Model; using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel; using Microsoft.eShopOnContainers.Services.Common.Infrastructure; using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data; using Microsoft.Extensions.Options; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -104,12 +106,6 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers var model = new PaginatedItemsViewModel( pageIndex, pageSize, totalItems, itemsOnPage); - //hook to run integration tests until POST methods are created - if (catalogTypeId.HasValue && catalogTypeId == 1) - { - _eventBus.Publish(new CatalogPriceChanged(2, 10.4M, 8.4M)); - } - return Ok(model); } @@ -153,11 +149,12 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers _context.CatalogItems.Update(item); await _context.SaveChangesAsync(); - _eventBus.Publish(new CatalogPriceChanged(item.Id, item.Price, oldPrice)); + var @event = new CatalogPriceChanged(item.Id, item.Price, oldPrice); + await ProcessEventAsync(@event); } return Ok(); - } + } private List ComposePicUri(List items) { var baseUri = _settings.Value.ExternalCatalogBaseUrl; @@ -168,5 +165,22 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers return items; } + + private async Task ProcessEventAsync(IntegrationEventBase @event) + { + _eventBus.Publish(@event); + var eventData = new IntegrationEvent(@event); + eventData.TimesSent++; + eventData.State = EventStateEnum.Sent; + try + { + _context.IntegrationEvents.Add(eventData); + await _context.SaveChangesAsync(); + } + catch (Exception ex) + { + var t = ex.Message; + } + } } } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs index 5c5498099..aac778940 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/CatalogContext.cs @@ -3,6 +3,7 @@ using EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore; using Model; + using Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data; public class CatalogContext : DbContext { @@ -12,12 +13,15 @@ public DbSet CatalogItems { get; set; } public DbSet CatalogBrands { get; set; } public DbSet CatalogTypes { get; set; } + public DbSet IntegrationEvents { get; set; } + protected override void OnModelCreating(ModelBuilder builder) { builder.Entity(ConfigureCatalogBrand); builder.Entity(ConfigureCatalogType); builder.Entity(ConfigureCatalogItem); - } + builder.Entity(ConfigureIntegrationEvent); + } void ConfigureCatalogItem(EntityTypeBuilder builder) { @@ -75,5 +79,31 @@ .IsRequired() .HasMaxLength(100); } + + void ConfigureIntegrationEvent(EntityTypeBuilder builder) + { + builder.ToTable("IntegrationEvent"); + + builder.HasKey(e => e.EventId); + + builder.Property(e => e.EventId) + .IsRequired(); + + builder.Property(e => e.Content) + .IsRequired(); + + builder.Property(e => e.CreationTime) + .IsRequired(); + + builder.Property(e => e.State) + .IsRequired(); + + builder.Property(e => e.TimesSent) + .IsRequired(); + + builder.Property(e => e.EventTypeName) + .IsRequired(); + + } } } diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs new file mode 100644 index 000000000..126047b85 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.Designer.cs @@ -0,0 +1,123 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; + +namespace Catalog.API.Infrastructure.Migrations +{ + [DbContext(typeof(CatalogContext))] + [Migration("20170314083211_AddEventTable")] + partial class AddEventTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") + .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogBrand"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("CatalogBrandId"); + + b.Property("CatalogTypeId"); + + b.Property("Description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50); + + b.Property("PictureUri"); + + b.Property("Price"); + + b.HasKey("Id"); + + b.HasIndex("CatalogBrandId"); + + b.HasIndex("CatalogTypeId"); + + b.ToTable("Catalog"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("CatalogType"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEvent", b => + { + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired() + .HasMaxLength(200); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEvent"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") + .WithMany() + .HasForeignKey("CatalogBrandId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType") + .WithMany() + .HasForeignKey("CatalogTypeId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs new file mode 100644 index 000000000..e6494d2a3 --- /dev/null +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/20170314083211_AddEventTable.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Catalog.API.Infrastructure.Migrations +{ + public partial class AddEventTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "IntegrationEvent", + columns: table => new + { + EventId = table.Column(nullable: false), + Content = table.Column(nullable: false), + CreationTime = table.Column(nullable: false), + EventTypeName = table.Column(maxLength: 200, nullable: false), + State = table.Column(nullable: false), + TimesSent = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IntegrationEvent", x => x.EventId); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "IntegrationEvent"); + } + } +} diff --git a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs index c0eb1db72..4ea056e0d 100644 --- a/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs +++ b/src/Services/Catalog/Catalog.API/Infrastructure/Migrations/CatalogContextModelSnapshot.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; +using Microsoft.eShopOnContainers.Services.Common.Infrastructure; namespace Catalog.API.Infrastructure.Migrations { @@ -13,13 +14,13 @@ namespace Catalog.API.Infrastructure.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.0.1") + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752") .HasAnnotation("SqlServer:Sequence:.catalog_brand_hilo", "'catalog_brand_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:.catalog_hilo", "'catalog_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:Sequence:.catalog_type_hilo", "'catalog_type_hilo', '', '1', '10', '', '', 'Int64', 'False'") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogBrand", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -28,14 +29,14 @@ namespace Catalog.API.Infrastructure.Migrations b.Property("Brand") .IsRequired() - .HasAnnotation("MaxLength", 100); + .HasMaxLength(100); b.HasKey("Id"); b.ToTable("CatalogBrand"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogItem", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -50,7 +51,7 @@ namespace Catalog.API.Infrastructure.Migrations b.Property("Name") .IsRequired() - .HasAnnotation("MaxLength", 50); + .HasMaxLength(50); b.Property("PictureUri"); @@ -65,7 +66,7 @@ namespace Catalog.API.Infrastructure.Migrations b.ToTable("Catalog"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogType", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -74,21 +75,44 @@ namespace Catalog.API.Infrastructure.Migrations b.Property("Type") .IsRequired() - .HasAnnotation("MaxLength", 100); + .HasMaxLength(100); b.HasKey("Id"); b.ToTable("CatalogType"); }); - modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogItem", b => + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data.IntegrationEvent", b => { - b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogBrand", "CatalogBrand") + b.Property("EventId") + .ValueGeneratedOnAdd(); + + b.Property("Content") + .IsRequired(); + + b.Property("CreationTime"); + + b.Property("EventTypeName") + .IsRequired() + .HasMaxLength(200); + + b.Property("State"); + + b.Property("TimesSent"); + + b.HasKey("EventId"); + + b.ToTable("IntegrationEvent"); + }); + + modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b => + { + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogBrand", "CatalogBrand") .WithMany() .HasForeignKey("CatalogBrandId") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure.CatalogType", "CatalogType") + b.HasOne("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", "CatalogType") .WithMany() .HasForeignKey("CatalogTypeId") .OnDelete(DeleteBehavior.Cascade); diff --git a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs index 427aa69c2..2c31a85b6 100644 --- a/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs +++ b/src/Services/Common/Infrastructure/Catalog/CatalogPriceChanged.cs @@ -4,7 +4,7 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Catalog { - public class CatalogPriceChanged : IIntegrationEvent + public class CatalogPriceChanged : IntegrationEventBase { public int ItemId { get; private set; } diff --git a/src/Services/Common/Infrastructure/IIntegrationEvent.cs b/src/Services/Common/Infrastructure/Data/EventStateEnum.cs similarity index 59% rename from src/Services/Common/Infrastructure/IIntegrationEvent.cs rename to src/Services/Common/Infrastructure/Data/EventStateEnum.cs index 48d991283..e952b7742 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEvent.cs +++ b/src/Services/Common/Infrastructure/Data/EventStateEnum.cs @@ -4,7 +4,10 @@ using System.Text; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { - public interface IIntegrationEvent - { + public enum EventStateEnum + { + NotSend = 0, + Sent = 1, + SendingFailed = 2 } } diff --git a/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs b/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs new file mode 100644 index 000000000..97b313021 --- /dev/null +++ b/src/Services/Common/Infrastructure/Data/IntegrationEvent.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure.Data +{ + public class IntegrationEvent + { + public IntegrationEvent(IntegrationEventBase @event) + { + EventId = @event.Id; + CreationTime = DateTime.UtcNow; + EventTypeName = @event.GetType().FullName; + Content = JsonConvert.SerializeObject(@event); + State = EventStateEnum.NotSend; + TimesSent = 0; + } + public Guid EventId { get; private set; } + public string EventTypeName { get; private set; } + public EventStateEnum State { get; set; } + public int TimesSent { get; set; } + public DateTime CreationTime { get; private set; } + public string Content { get; private set; } + } +} diff --git a/src/Services/Common/Infrastructure/EventBus.cs b/src/Services/Common/Infrastructure/EventBus.cs index 47e51d28d..4ff33e79b 100644 --- a/src/Services/Common/Infrastructure/EventBus.cs +++ b/src/Services/Common/Infrastructure/EventBus.cs @@ -30,7 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure _handlers = new Dictionary>(); _eventTypes = new List(); } - public void Publish(IIntegrationEvent @event) + public void Publish(IntegrationEventBase @event) { var eventName = @event.GetType().Name; var factory = new ConnectionFactory() { HostName = _connectionString }; @@ -51,7 +51,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } - public void Subscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent + public void Subscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase { var eventName = typeof(T).Name; if (_handlers.ContainsKey(eventName)) @@ -72,7 +72,7 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure } - public void Unsubscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent + public void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase { var eventName = typeof(T).Name; if (_handlers.ContainsKey(eventName) && _handlers[eventName].Contains(handler)) diff --git a/src/Services/Common/Infrastructure/IEventBus.cs b/src/Services/Common/Infrastructure/IEventBus.cs index 5751f1b81..483e550ec 100644 --- a/src/Services/Common/Infrastructure/IEventBus.cs +++ b/src/Services/Common/Infrastructure/IEventBus.cs @@ -6,8 +6,8 @@ namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IEventBus { - void Subscribe(IIntegrationEventHandler handler) where T: IIntegrationEvent; - void Unsubscribe(IIntegrationEventHandler handler) where T : IIntegrationEvent; - void Publish(IIntegrationEvent @event); + void Subscribe(IIntegrationEventHandler handler) where T: IntegrationEventBase; + void Unsubscribe(IIntegrationEventHandler handler) where T : IntegrationEventBase; + void Publish(IntegrationEventBase @event); } } diff --git a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs index 6824a1665..f4c00fa6b 100644 --- a/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs +++ b/src/Services/Common/Infrastructure/IIntegrationEventHandler.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure { public interface IIntegrationEventHandler : IIntegrationEventHandler - where TIntegrationEvent: IIntegrationEvent + where TIntegrationEvent: IntegrationEventBase { Task Handle(TIntegrationEvent @event); } diff --git a/src/Services/Common/Infrastructure/IntegrationEventBase.cs b/src/Services/Common/Infrastructure/IntegrationEventBase.cs new file mode 100644 index 000000000..b11c17815 --- /dev/null +++ b/src/Services/Common/Infrastructure/IntegrationEventBase.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.eShopOnContainers.Services.Common.Infrastructure +{ + public class IntegrationEventBase + { + public IntegrationEventBase() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; private set; } + } +}