Browse Source

Merge

pull/223/head
Ramón Tomás 7 years ago
parent
commit
7cbd77bc7a
40 changed files with 1091 additions and 29 deletions
  1. +6
    -1
      docker-compose.override.yml
  2. +13
    -0
      docker-compose.vs.debug.yml
  3. +10
    -0
      docker-compose.vs.release.yml
  4. +11
    -1
      docker-compose.yml
  5. +54
    -0
      eShopOnContainers-ServicesAndWebApps.sln
  6. +107
    -0
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.Designer.cs
  7. +55
    -0
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.cs
  8. +8
    -0
      src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs
  9. +61
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/ConfirmOrderStockIntegrationEventHandler.cs
  10. +30
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ConfirmOrderStockIntegrationEvent.cs
  11. +11
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs
  12. +31
    -0
      src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockNotConfirmedIntegrationEvent.cs
  13. +77
    -3
      src/Services/Catalog/Catalog.API/Model/CatalogItem.cs
  14. +50
    -0
      src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmation/UpdateOrderWhenOrderStockMethodVerifiedDomainEventHandler.cs
  15. +3
    -6
      src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/ConfirmGracePeriodCommandMsg.cs
  16. +30
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/ConfirmOrderStockCommandMsg.cs
  17. +14
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/PayOrderCommandMsg.cs
  18. +48
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs
  19. +48
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs
  20. +1
    -1
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs
  21. +11
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs
  22. +32
    -0
      src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockNotConfirmedIntegrationEvent.cs
  23. +23
    -7
      src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs
  24. +5
    -2
      src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs
  25. +15
    -0
      src/Services/Ordering/Ordering.API/Startup.cs
  26. +20
    -1
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  27. +5
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs
  28. +8
    -7
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs
  29. +22
    -0
      src/Services/Ordering/Ordering.Domain/Events/OrderStockMethodVerifiedDomainEvent.cs
  30. +3
    -0
      src/Services/SagaManager/SagaManager/.dockerignore
  31. +5
    -0
      src/Services/SagaManager/SagaManager/Dockerfile
  32. +14
    -0
      src/Services/SagaManager/SagaManager/IntegrationEvents/Events/ConfirmGracePeriodCommandMsg.cs
  33. +9
    -0
      src/Services/SagaManager/SagaManager/IntegrationEvents/ISagaManagerIntegrationEventService.cs
  34. +21
    -0
      src/Services/SagaManager/SagaManager/IntegrationEvents/SagaManagerIntegrationEventService.cs
  35. +95
    -0
      src/Services/SagaManager/SagaManager/Program.cs
  36. +34
    -0
      src/Services/SagaManager/SagaManager/SagaManager.csproj
  37. +11
    -0
      src/Services/SagaManager/SagaManager/SagaManagerSettings.cs
  38. +7
    -0
      src/Services/SagaManager/SagaManager/Services/ISagaManagerService.cs
  39. +72
    -0
      src/Services/SagaManager/SagaManager/Services/SagaManagerService.cs
  40. +11
    -0
      src/Services/SagaManager/SagaManager/appsettings.json

+ 6
- 1
docker-compose.override.yml View File

@ -7,7 +7,12 @@ version: '2'
# An external IP or DNS name has to be used (instead localhost and the 10.0.75.1 IP) when testing the Web apps and the Xamarin apps from remote machines/devices using the same WiFi, for instance.
services:
sagamanager:
environment:
- ConnectionString=Server=sql.data;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word
- EventBusConnection=rabbitmq
- GracePeriod=15 #In minutes
basket.api:
environment:
- ASPNETCORE_ENVIRONMENT=Development


+ 13
- 0
docker-compose.vs.debug.yml View File

@ -120,3 +120,16 @@ services:
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
sagamanager:
image: eshop/sagamanager:dev
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ./src/Services/SagaManager/SagaManager:/app
- ~/.nuget/packages:/root/.nuget/packages:ro
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

+ 10
- 0
docker-compose.vs.release.yml View File

@ -80,3 +80,13 @@ services:
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"
sagamanager:
build:
args:
source: ${DOCKER_BUILD_SOURCE}
volumes:
- ~/clrdbg:/clrdbg:ro
entrypoint: tail -f /dev/null
labels:
- "com.microsoft.visualstudio.targetoperatingsystem=linux"

+ 11
- 1
docker-compose.yml View File

@ -1,6 +1,16 @@
version: '2'
services:
sagamanager:
image: eshop/sagamanager
build:
context: ./src/Services/SagaManager/SagaManager
dockerfile: Dockerfile
depends_on:
- sql.data
- rabbitmq
basket.api:
image: eshop/basket.api
build:
@ -9,7 +19,6 @@ services:
depends_on:
- basket.data
- identity.api
- rabbitmq
catalog.api:
image: eshop/catalog.api
@ -35,6 +44,7 @@ services:
dockerfile: Dockerfile
depends_on:
- sql.data
- rabbitmq
webspa:
image: eshop/webspa


+ 54
- 0
eShopOnContainers-ServicesAndWebApps.sln View File

@ -80,6 +80,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Health
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBus.Tests", "src\BuildingBlocks\EventBus\EventBus.Tests\EventBus.Tests.csproj", "{4A980AC4-7205-46BF-8CCB-09E44D700FD4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SagaManager", "SagaManager", "{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SagaManager", "src\Services\SagaManager\SagaManager\SagaManager.csproj", "{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -1054,6 +1058,54 @@ Global
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x64.Build.0 = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.ActiveCfg = Release|Any CPU
{4A980AC4-7205-46BF-8CCB-09E44D700FD4}.Release|x86.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.AppStore|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|ARM.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhone.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x64.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.ActiveCfg = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Debug|x86.Build.0 = Debug|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|Any CPU.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|ARM.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhone.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x64.Build.0 = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.ActiveCfg = Release|Any CPU
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1092,5 +1144,7 @@ Global
{22A0F9C1-2D4A-4107-95B7-8459E6688BC5} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{4BD76717-3102-4969-8C2C-BAAA3F0263B6} = {A81ECBC2-6B00-4DCD-8388-469174033379}
{4A980AC4-7205-46BF-8CCB-09E44D700FD4} = {807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}
{F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E} = {91CF7717-08AB-4E65-B10E-0B426F01E2E8}
{F6E0F0DD-1400-43C3-B5E0-7CC325728C47} = {F38B4FF0-0B49-405A-B1B4-F7A5E3BC4C4E}
EndGlobalSection
EndGlobal

+ 107
- 0
src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.Designer.cs View File

@ -0,0 +1,107 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
namespace Catalog.API.Infrastructure.Migrations
{
[DbContext(typeof(CatalogContext))]
[Migration("20170509130025_AddStockProductItem")]
partial class AddStockProductItem
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
modelBuilder
.HasAnnotation("ProductVersion", "1.1.1")
.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<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_brand_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Brand")
.IsRequired()
.HasMaxLength(100);
b.HasKey("Id");
b.ToTable("CatalogBrand");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int>("AvailableStock");
b.Property<int>("CatalogBrandId");
b.Property<int>("CatalogTypeId");
b.Property<string>("Description");
b.Property<int>("MaxStockThreshold");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50);
b.Property<bool>("OnReorder");
b.Property<string>("PictureUri");
b.Property<decimal>("Price");
b.Property<int>("RestockThreshold");
b.HasKey("Id");
b.HasIndex("CatalogBrandId");
b.HasIndex("CatalogTypeId");
b.ToTable("Catalog");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Catalog.API.Model.CatalogType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_type_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("Type")
.IsRequired()
.HasMaxLength(100);
b.HasKey("Id");
b.ToTable("CatalogType");
});
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);
});
}
}
}

+ 55
- 0
src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/20170509130025_AddStockProductItem.cs View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Catalog.API.Infrastructure.Migrations
{
public partial class AddStockProductItem : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "AvailableStock",
table: "Catalog",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "MaxStockThreshold",
table: "Catalog",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<bool>(
name: "OnReorder",
table: "Catalog",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<int>(
name: "RestockThreshold",
table: "Catalog",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "AvailableStock",
table: "Catalog");
migrationBuilder.DropColumn(
name: "MaxStockThreshold",
table: "Catalog");
migrationBuilder.DropColumn(
name: "OnReorder",
table: "Catalog");
migrationBuilder.DropColumn(
name: "RestockThreshold",
table: "Catalog");
}
}
}

+ 8
- 0
src/Services/Catalog/Catalog.API/Infrastructure/CatalogMigrations/CatalogContextModelSnapshot.cs View File

@ -42,20 +42,28 @@ namespace Catalog.API.Infrastructure.Migrations
.HasAnnotation("SqlServer:HiLoSequenceName", "catalog_hilo")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int>("AvailableStock");
b.Property<int>("CatalogBrandId");
b.Property<int>("CatalogTypeId");
b.Property<string>("Description");
b.Property<int>("MaxStockThreshold");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50);
b.Property<bool>("OnReorder");
b.Property<string>("PictureUri");
b.Property<decimal>("Price");
b.Property<int>("RestockThreshold");
b.HasKey("Id");
b.HasIndex("CatalogBrandId");


+ 61
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/EventHandling/ConfirmOrderStockIntegrationEventHandler.cs View File

@ -0,0 +1,61 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.EventHandling
{
using BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using BuildingBlocks.EventBus.Events;
using Infrastructure;
using System.Collections.Generic;
using System.Linq;
using global::Catalog.API.Infrastructure.Exceptions;
using global::Catalog.API.IntegrationEvents;
using Model;
using Events;
public class ConfirmOrderStockIntegrationEventHandler : IIntegrationEventHandler<ConfirmOrderStockIntegrationEvent>
{
private readonly CatalogContext _catalogContext;
private readonly ICatalogIntegrationEventService _catalogIntegrationEventService;
public ConfirmOrderStockIntegrationEventHandler(CatalogContext catalogContext,
ICatalogIntegrationEventService catalogIntegrationEventService)
{
_catalogContext = catalogContext;
_catalogIntegrationEventService = catalogIntegrationEventService;
}
public async Task Handle(ConfirmOrderStockIntegrationEvent @event)
{
var confirmedOrderStockItems = new List<ConfirmedOrderStockItem>();
foreach (var orderStockItem in @event.OrderStockItems)
{
var catalogItem = _catalogContext.CatalogItems.Find(orderStockItem.ProductId);
CheckValidcatalogItemId(catalogItem);
var confirmedOrderStockItem = new ConfirmedOrderStockItem(catalogItem.Id,
catalogItem.AvailableStock >= orderStockItem.Units);
confirmedOrderStockItems.Add(confirmedOrderStockItem);
}
//Create Integration Event to be published through the Event Bus
var confirmedIntegrationEvent = confirmedOrderStockItems.Any(c => !c.Confirmed)
? (IntegrationEvent) new OrderStockNotConfirmedIntegrationEvent(@event.OrderId, confirmedOrderStockItems)
: new OrderStockConfirmedIntegrationEvent(@event.OrderId);
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
await _catalogIntegrationEventService.SaveEventAndCatalogContextChangesAsync(confirmedIntegrationEvent);
// Publish through the Event Bus and mark the saved event as published
await _catalogIntegrationEventService.PublishThroughEventBusAsync(confirmedIntegrationEvent);
}
private void CheckValidcatalogItemId(CatalogItem catalogItem)
{
if (catalogItem is null)
{
throw new CatalogDomainException("Not able to process catalog event. Reason: no valid catalogItemId");
}
}
}
}

+ 30
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/ConfirmOrderStockIntegrationEvent.cs View File

@ -0,0 +1,30 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{
using BuildingBlocks.EventBus.Events;
using System.Collections.Generic;
public class ConfirmOrderStockIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItems { get; }
public ConfirmOrderStockIntegrationEvent(int orderId,
IEnumerable<OrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
public class OrderStockItem
{
public int ProductId { get; }
public int Units { get; }
public OrderStockItem(int productId, int units)
{
ProductId = productId;
Units = units;
}
}
}

+ 11
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs View File

@ -0,0 +1,11 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStockConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public OrderStockConfirmedIntegrationEvent(int orderId) => OrderId = orderId;
}
}

+ 31
- 0
src/Services/Catalog/Catalog.API/IntegrationEvents/Events/OrderStockNotConfirmedIntegrationEvent.cs View File

@ -0,0 +1,31 @@
namespace Microsoft.eShopOnContainers.Services.Catalog.API.IntegrationEvents.Events
{
using BuildingBlocks.EventBus.Events;
using System.Collections.Generic;
public class OrderStockNotConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<ConfirmedOrderStockItem> OrderStockItem { get; }
public OrderStockNotConfirmedIntegrationEvent(int orderId,
IEnumerable<ConfirmedOrderStockItem> orderStockItem)
{
OrderId = orderId;
OrderStockItem = orderStockItem;
}
}
public class ConfirmedOrderStockItem
{
public int ProductId { get; }
public bool Confirmed { get; }
public ConfirmedOrderStockItem(int productId, bool confirmed)
{
ProductId = productId;
Confirmed = confirmed;
}
}
}

+ 77
- 3
src/Services/Catalog/Catalog.API/Model/CatalogItem.cs View File

@ -1,4 +1,5 @@
using System;
using Catalog.API.Infrastructure.Exceptions;
using System;
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model
{
@ -22,6 +23,79 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Model
public CatalogBrand CatalogBrand { get; set; }
public CatalogItem() { }
// Quantity in stock
public int AvailableStock { get; set; }
// Available stock at which we should reorder
public int RestockThreshold { get; set; }
// Maximum number of units that can be in-stock at any time (due to physicial/logistical constraints in warehouses)
public int MaxStockThreshold { get; set; }
/// <summary>
/// True if item is on reorder
/// </summary>
public bool OnReorder { get; set; }
public CatalogItem() { }
/// <summary>
/// Decrements the quantity of a particular item in inventory and ensures the restockThreshold hasn't
/// been breached. If so, a RestockRequest is generated in CheckThreshold.
///
/// If there is sufficient stock of an item, then the integer returned at the end of this call should be the same as quantityDesired.
/// In the event that there is not sufficient stock available, the method will remove whatever stock is available and return that quantity to the client.
/// In this case, it is the responsibility of the client to determine if the amount that is returned is the same as quantityDesired.
/// It is invalid to pass in a negative number.
/// </summary>
/// <param name="quantityDesired"></param>
/// <returns>int: Returns the number actually removed from stock. </returns>
///
public int RemoveStock(int quantityDesired)
{
if (AvailableStock == 0)
{
throw new CatalogDomainException($"Empty stock, product item {Name} is sold out");
}
if (quantityDesired <= 0)
{
throw new CatalogDomainException($"Item units desired should be greater than cero");
}
int removed = Math.Min(quantityDesired, this.AvailableStock);
this.AvailableStock -= removed;
return removed;
}
/// <summary>
/// Increments the quantity of a particular item in inventory.
/// <param name="quantity"></param>
/// <returns>int: Returns the quantity that has been added to stock</returns>
/// </summary>
public int AddStock(int quantity)
{
int original = this.AvailableStock;
// The quantity that the client is trying to add to stock is greater than what can be physically accommodated in the Warehouse
if ((this.AvailableStock + quantity) > this.MaxStockThreshold)
{
// For now, this method only adds new units up maximum stock threshold. In an expanded version of this application, we
//could include tracking for the remaining units and store information about overstock elsewhere.
this.AvailableStock += (this.MaxStockThreshold - this.AvailableStock);
}
else
{
this.AvailableStock += quantity;
}
this.OnReorder = false;
return this.AvailableStock - original;
}
}
}
}

+ 50
- 0
src/Services/Ordering/Ordering.API/Application/DomainEventHandlers/OrderStockConfirmation/UpdateOrderWhenOrderStockMethodVerifiedDomainEventHandler.cs View File

@ -0,0 +1,50 @@
namespace Ordering.API.Application.DomainEventHandlers.OrderStartedEvent
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Domain.Events;
using System;
using System.Threading.Tasks;
public class UpdateOrderWhenOrderStockMethodVerifiedDomainEventHandler
: IAsyncNotificationHandler<OrderStockMethodVerifiedDomainEvent>
{
private readonly IOrderRepository _orderRepository;
private readonly ILoggerFactory _logger;
public UpdateOrderWhenOrderStockMethodVerifiedDomainEventHandler(
IOrderRepository orderRepository, ILoggerFactory logger)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
// Domain Logic comment:
// When the Order Stock items method have been validate and confirmed,
// then we can update the original Order with the new order status
public async Task Handle(OrderStockMethodVerifiedDomainEvent orderStockMethodVerifiedDomainEvent)
{
var orderToUpdate = await _orderRepository.GetAsync(orderStockMethodVerifiedDomainEvent.OrderId);
orderToUpdate.SetOrderStatusId(orderStockMethodVerifiedDomainEvent.OrderStatus.Id);
_orderRepository.Update(orderToUpdate);
await _orderRepository.UnitOfWork
.SaveEntitiesAsync();
_logger.CreateLogger(nameof(UpdateOrderWhenOrderStockMethodVerifiedDomainEventHandler))
.LogTrace($"Order with Id: {orderStockMethodVerifiedDomainEvent.OrderId} has been successfully updated with " +
$"a status order id: { orderStockMethodVerifiedDomainEvent.OrderStatus.Id }");
//var payOrderCommandMsg = new PayOrderCommandMsg(order.Id);
//// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
//await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(payOrderCommandMsg);
//// Publish through the Event Bus and mark the saved event as published
//await _orderingIntegrationEventService.PublishThroughEventBusAsync(payOrderCommandMsg);
}
}
}

+ 3
- 6
src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/ConfirmGracePeriodCommandMsg.cs View File

@ -1,15 +1,12 @@
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.API.Application.IntegrationCommands.Commands
{
public class ConfirmGracePeriodCommandMsg : IntegrationEvent
{
public int OrderNumber { get; private set; }
public int OrderId { get; }
//TODO: message should change to Integration command type once command bus is implemented
public ConfirmGracePeriodCommandMsg(int orderId) =>
OrderId = orderId;
}
}

+ 30
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/ConfirmOrderStockCommandMsg.cs View File

@ -0,0 +1,30 @@
namespace Ordering.API.Application.IntegrationCommands.Commands
{
using System.Collections.Generic;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class ConfirmOrderStockCommandMsg : IntegrationEvent
{
public int OrderId { get; }
public IEnumerable<OrderStockItem> OrderStockItem { get; }
public ConfirmOrderStockCommandMsg(int orderId,
IEnumerable<OrderStockItem> orderStockItem)
{
OrderId = orderId;
OrderStockItem = orderStockItem;
}
}
public class OrderStockItem
{
public int ProductId { get; }
public int Units { get; }
public OrderStockItem(int productId, int units)
{
ProductId = productId;
Units = units;
}
}
}

+ 14
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationCommands/Commands/PayOrderCommandMsg.cs View File

@ -0,0 +1,14 @@
namespace Ordering.API.Application.IntegrationCommands.Commands
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class PayOrderCommandMsg : IntegrationEvent
{
public int OrderId { get; }
public PayOrderCommandMsg(int orderId)
{
OrderId = orderId;
}
}
}

+ 48
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockConfirmedIntegrationEventHandler.cs View File

@ -0,0 +1,48 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System.Threading.Tasks;
using Events;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.Domain.Exceptions;
public class OrderStockConfirmedIntegrationEventHandler : IIntegrationEventHandler<OrderStockConfirmedIntegrationEvent>
{
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly IOrderRepository _orderRepository;
public OrderStockConfirmedIntegrationEventHandler(IOrderRepository orderRepository,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository;
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStockConfirmedIntegrationEvent @event)
{
//TODO: 1) Updates the state to "StockValidated" and any meaningful OrderContextDescription message saying that all the items were confirmed with available stock, etc
var order = await _orderRepository.GetAsync(@event.OrderId);
CheckValidSagaId(order);
order.SetOrderStockConfirmed(true);
//Create Integration Event to be published through the Event Bus
var payOrderCommandMsg = new PayOrderCommandMsg(order.Id);
// Achieving atomicity between original Catalog database operation and the IntegrationEventLog thanks to a local transaction
await _orderingIntegrationEventService.SaveEventAndOrderingContextChangesAsync(payOrderCommandMsg);
// Publish through the Event Bus and mark the saved event as published
await _orderingIntegrationEventService.PublishThroughEventBusAsync(payOrderCommandMsg);
}
private void CheckValidSagaId(Order orderSaga)
{
if (orderSaga is null)
{
throw new OrderingDomainException("Not able to process order saga event. Reason: no valid orderId");
}
}
}
}

+ 48
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/EventHandling/OrderStockNotConfirmedIntegrationEventHandler.cs View File

@ -0,0 +1,48 @@
namespace Ordering.API.Application.IntegrationEvents.EventHandling
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using System;
using System.Threading.Tasks;
using Events;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Ordering.API.Application.Sagas;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Ordering.Domain.Exceptions;
public class OrderStockNotConfirmedIntegrationEventHandler : IIntegrationEventHandler<OrderStockNotConfirmedIntegrationEvent>
{
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
private readonly IOrderRepository _orderRepository;
public OrderStockNotConfirmedIntegrationEventHandler(IOrderRepository orderRepository,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_orderRepository = orderRepository;
_orderingIntegrationEventService = orderingIntegrationEventService;
}
public async Task Handle(OrderStockNotConfirmedIntegrationEvent @event)
{
//TODO: must update the order state to cancelled and the CurrentOrderStateContextDescription with the reasons of no-stock confirm
var order = await _orderRepository.GetAsync(@event.OrderId);
CheckValidSagaId(order);
order.SetOrderStockConfirmed(false);
var orderStockNotConfirmedItems = @event.OrderStockItems.FindAll(c => !c.Confirmed);
foreach (var orderStockNotConfirmedItem in orderStockNotConfirmedItems)
{
//TODO: Add messages
}
}
private void CheckValidSagaId(Order orderSaga)
{
if (orderSaga is null)
{
throw new OrderingDomainException("Not able to process order saga event. Reason: no valid orderId");
}
}
}
}

+ 1
- 1
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStartedIntegrationEvent.cs View File

@ -16,4 +16,4 @@ namespace Ordering.API.Application.IntegrationEvents.Events
public OrderStartedIntegrationEvent(string userId) =>
UserId = userId;
}
}
}

+ 11
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockConfirmedIntegrationEvent.cs View File

@ -0,0 +1,11 @@
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStockConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public OrderStockConfirmedIntegrationEvent(int orderId) => OrderId = orderId;
}
}

+ 32
- 0
src/Services/Ordering/Ordering.API/Application/IntegrationEvents/Events/OrderStockNotConfirmedIntegrationEvent.cs View File

@ -0,0 +1,32 @@
using System.Collections.Generic;
namespace Ordering.API.Application.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public class OrderStockNotConfirmedIntegrationEvent : IntegrationEvent
{
public int OrderId { get; }
public List<ConfirmedOrderStockItem> OrderStockItems { get; }
public OrderStockNotConfirmedIntegrationEvent(int orderId,
List<ConfirmedOrderStockItem> orderStockItems)
{
OrderId = orderId;
OrderStockItems = orderStockItems;
}
}
public class ConfirmedOrderStockItem
{
public int ProductId { get; }
public bool Confirmed { get; }
public ConfirmedOrderStockItem(int productId, bool confirmed)
{
ProductId = productId;
Confirmed = confirmed;
}
}
}

+ 23
- 7
src/Services/Ordering/Ordering.API/Application/Sagas/OrderProcessSaga.cs View File

@ -10,7 +10,12 @@ using Ordering.API.Application.IntegrationCommands.Commands;
using Ordering.API.Application.IntegrationEvents.Events;
using Ordering.Domain.Exceptions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Ordering.API.Application.IntegrationEvents;
using Ordering.API.Application.IntegrationEvents.Events;
namespace Ordering.API.Application.Sagas
{
@ -29,14 +34,16 @@ namespace Ordering.API.Application.Sagas
{
private readonly IMediator _mediator;
private readonly Func<Owned<OrderingContext>> _dbContextFactory;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public OrderProcessSaga(
Func<Owned<OrderingContext>> dbContextFactory, OrderingContext orderingContext,
IMediator mediator)
IMediator mediator, IOrderingIntegrationEventService orderingIntegrationEventService)
: base(orderingContext)
{
_dbContextFactory = dbContextFactory;
_mediator = mediator;
_orderingIntegrationEventService = orderingIntegrationEventService;
}
/// <summary>
@ -72,18 +79,27 @@ namespace Ordering.API.Application.Sagas
/// period has completed.
/// </param>
/// <returns></returns>
public async Task Handle(ConfirmGracePeriodCommandMsg command)
public async Task Handle(ConfirmGracePeriodCommandMsg @event)
{
var orderSaga = FindSagaById(command.OrderNumber);
var orderSaga = FindSagaById(@event.OrderId);
CheckValidSagaId(orderSaga);
// TODO: This handler should change to Integration command handler type once command bus is implemented
if (orderSaga.OrderStatus != OrderStatus.Cancelled)
{
orderSaga.SetOrderStatusId(OrderStatus.AwaitingValidation.Id);
await SaveChangesAsync();
// TODO: If order status is not cancelled, change state to awaitingValidation and
// send ConfirmOrderStockCommandMsg to Inventory api
var orderStockList = orderSaga.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
//Create Integration Event to be published through the Event Bus
var confirmOrderStockEvent = new ConfirmOrderStockCommandMsg(orderSaga.Id, orderStockList);
// Publish through the Event Bus and mark the saved event as published
await _orderingIntegrationEventService.PublishThroughEventBusAsync(confirmOrderStockEvent);
}
}
/// <summary>
/// Handler which processes the command when
/// customer executes cancel order from app


+ 5
- 2
src/Services/Ordering/Ordering.API/Infrastructure/OrderingContextSeed.cs View File

@ -31,9 +31,12 @@
if (!context.OrderStatus.Any())
{
context.OrderStatus.Add(OrderStatus.Canceled);
context.OrderStatus.Add(OrderStatus.InProcess);
context.OrderStatus.Add(OrderStatus.Submited);
context.OrderStatus.Add(OrderStatus.AwaitingValidation);
context.OrderStatus.Add(OrderStatus.StockValidated);
context.OrderStatus.Add(OrderStatus.Paid);
context.OrderStatus.Add(OrderStatus.Shipped);
context.OrderStatus.Add(OrderStatus.Cancelled);
}
await context.SaveChangesAsync();


+ 15
- 0
src/Services/Ordering/Ordering.API/Startup.cs View File

@ -6,6 +6,9 @@
using global::Ordering.API.Application.IntegrationEvents;
using global::Ordering.API.Application.IntegrationEvents.Events;
using global::Ordering.API.Infrastructure.Middlewares;
using global::Ordering.API.Application.IntegrationCommands.Commands;
using global::Ordering.API.Application.IntegrationEvents.Events;
using global::Ordering.API.Application.Sagas;
using Infrastructure;
using Infrastructure.Auth;
using Infrastructure.AutofacModules;
@ -125,6 +128,9 @@
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
//services.AddTransient<UserCheckoutAcceptedIntegrationEventHandler>();
services.AddTransient<IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>, OrderProcessSaga>();
services.AddTransient<OrderStockConfirmedIntegrationEventHandler>();
services.AddTransient<OrderStockNotConfirmedIntegrationEventHandler>();
services.AddOptions();
//configure autofac
@ -163,6 +169,7 @@
.UseSqlServer(Configuration["ConnectionString"], b => b.MigrationsAssembly("Ordering.API"))
.Options);
integrationEventLogContext.Database.Migrate();
}
private void ConfigureEventBus(IApplicationBuilder app)
@ -172,6 +179,14 @@
eventBus.Subscribe<UserCheckoutAcceptedIntegrationEvent,IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>>(
() => app.ApplicationServices.GetRequiredService<IIntegrationEventHandler<UserCheckoutAcceptedIntegrationEvent>>());
eventBus.Subscribe<ConfirmGracePeriodCommandMsg, IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>>
(() => app.ApplicationServices.GetRequiredService<IIntegrationEventHandler<ConfirmGracePeriodCommandMsg>>());
eventBus.Subscribe<OrderStockConfirmedIntegrationEvent, OrderStockConfirmedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<OrderStockConfirmedIntegrationEventHandler>());
eventBus.Subscribe<OrderStockNotConfirmedIntegrationEvent, OrderStockNotConfirmedIntegrationEventHandler>
(() => app.ApplicationServices.GetRequiredService<OrderStockNotConfirmedIntegrationEventHandler>());
}
protected virtual void ConfigureAuth(IApplicationBuilder app)


+ 20
- 1
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -46,7 +46,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
_orderItems = new List<OrderItem>();
_buyerId = buyerId;
_paymentMethodId = paymentMethodId;
_orderStatusId = OrderStatus.InProcess.Id;
_orderStatusId = OrderStatus.Submited.Id;
_orderDate = DateTime.UtcNow;
Address = address;
@ -94,6 +94,25 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
_buyerId = id;
}
public void SetOrderStatusId(int id)
{
_orderStatusId = id;
}
public void SetOrderStockConfirmed(bool confirmed)
{
if(confirmed)
{
OrderStatus = OrderStatus.StockValidated;
AddDomainEvent(new OrderStockMethodVerifiedDomainEvent(Id, OrderStatus.StockValidated));
}
else
{
OrderStatus = OrderStatus.Cancelled;
AddDomainEvent(new OrderStockMethodVerifiedDomainEvent(Id, OrderStatus.Cancelled));
}
}
private void AddOrderStartedDomainEvent(int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
{


+ 5
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderItem.cs View File

@ -54,6 +54,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
return _discount;
}
public int GetUnits()
{
return _units;
}
public void SetNewDiscount(decimal discount)
{
if (discount < 0)


+ 8
- 7
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/OrderStatus.cs View File

@ -10,9 +10,12 @@
public class OrderStatus
: Enumeration
{
public static OrderStatus InProcess = new OrderStatus(1, nameof(InProcess).ToLowerInvariant());
public static OrderStatus Shipped = new OrderStatus(2, nameof(Shipped).ToLowerInvariant());
public static OrderStatus Canceled = new OrderStatus(3, nameof(Canceled).ToLowerInvariant());
public static OrderStatus Submited = new OrderStatus(1, nameof(Submited).ToLowerInvariant());
public static OrderStatus AwaitingValidation = new OrderStatus(2, nameof(AwaitingValidation).ToLowerInvariant());
public static OrderStatus StockValidated = new OrderStatus(3, nameof(StockValidated).ToLowerInvariant());
public static OrderStatus Paid = new OrderStatus(4, nameof(Paid).ToLowerInvariant());
public static OrderStatus Shipped = new OrderStatus(5, nameof(Shipped).ToLowerInvariant());
public static OrderStatus Cancelled = new OrderStatus(6, nameof(Cancelled).ToLowerInvariant());
protected OrderStatus()
{
@ -23,10 +26,8 @@
{
}
public static IEnumerable<OrderStatus> List()
{
return new[] { InProcess, Shipped, Canceled };
}
public static IEnumerable<OrderStatus> List() =>
new[] { Submited, AwaitingValidation, StockValidated, Paid, Shipped, Cancelled };
public static OrderStatus FromName(string name)
{


+ 22
- 0
src/Services/Ordering/Ordering.Domain/Events/OrderStockMethodVerifiedDomainEvent.cs View File

@ -0,0 +1,22 @@
namespace Ordering.Domain.Events
{
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
/// <summary>
/// Event used when the order stock items are verified
/// </summary>
public class OrderStockMethodVerifiedDomainEvent
: IAsyncNotification
{
public int OrderId { get; }
public OrderStatus OrderStatus { get; }
public OrderStockMethodVerifiedDomainEvent(int orderId,
OrderStatus orderStatus)
{
OrderId = orderId;
OrderStatus = orderStatus;
}
}
}

+ 3
- 0
src/Services/SagaManager/SagaManager/.dockerignore View File

@ -0,0 +1,3 @@
*
!obj/Docker/publish/*
!obj/Docker/empty/

+ 5
- 0
src/Services/SagaManager/SagaManager/Dockerfile View File

@ -0,0 +1,5 @@
FROM microsoft/dotnet:1.1-runtime
ARG source
WORKDIR /app
COPY ${source:-obj/Docker/publish} .
ENTRYPOINT ["dotnet", "SagaManager.dll"]

+ 14
- 0
src/Services/SagaManager/SagaManager/IntegrationEvents/Events/ConfirmGracePeriodCommandMsg.cs View File

@ -0,0 +1,14 @@
namespace SagaManager.IntegrationEvents.Events
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
// Integration Events notes:
// An Event is “something that has happened in the past”, therefore its name has to be
// An Integration Event is an event that can cause side effects to other microsrvices, Bounded-Contexts or external systems.
public class ConfirmGracePeriodCommandMsg : IntegrationEvent
{
public int OrderId { get;}
public ConfirmGracePeriodCommandMsg(int orderId) => OrderId = orderId;
}
}

+ 9
- 0
src/Services/SagaManager/SagaManager/IntegrationEvents/ISagaManagerIntegrationEventService.cs View File

@ -0,0 +1,9 @@
namespace SagaManager.IntegrationEvents
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
public interface ISagaManagerIntegrationEventService
{
void PublishThroughEventBus(IntegrationEvent evt);
}
}

+ 21
- 0
src/Services/SagaManager/SagaManager/IntegrationEvents/SagaManagerIntegrationEventService.cs View File

@ -0,0 +1,21 @@
namespace SagaManager.IntegrationEvents
{
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events;
using System;
public class SagaManagerIntegrationEventService : ISagaManagerIntegrationEventService
{
private readonly IEventBus _eventBus;
public SagaManagerIntegrationEventService(IEventBus eventBus)
{
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
}
public void PublishThroughEventBus(IntegrationEvent evt)
{
_eventBus.Publish(evt);
}
}
}

+ 95
- 0
src/Services/SagaManager/SagaManager/Program.cs View File

@ -0,0 +1,95 @@
using System.Reflection;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using Microsoft.EntityFrameworkCore;
using SagaManager.IntegrationEvents;
namespace SagaManager
{
using System.IO;
using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions;
using Microsoft.eShopOnContainers.BuildingBlocks.EventBusRabbitMQ;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RabbitMQ.Client;
using Services;
public class Program
{
public static IConfigurationRoot Configuration { get; set; }
public static void Main(string[] args)
{
StartUp();
IServiceCollection services = new ServiceCollection();
var serviceProvider = ConfigureServices(services);
var logger = serviceProvider.GetService<ILoggerFactory>();
Configure(logger);
var sagaManagerService = serviceProvider
.GetRequiredService<ISagaManagerService>();
while (true)
{
sagaManagerService.CheckFinishedGracePeriodOrders();
System.Threading.Thread.Sleep(30000);
}
}
public static void StartUp()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public static IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddLogging()
.AddOptions()
.Configure<SagaManagerSettings>(Configuration)
.AddSingleton<ISagaManagerService, SagaManagerService>()
.AddSingleton<ISagaManagerIntegrationEventService, SagaManagerIntegrationEventService>()
.AddSingleton<IEventBus, EventBusRabbitMQ>()
.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>()
.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var settings = sp.GetRequiredService<IOptions<SagaManagerSettings>>().Value;
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = settings.EventBusConnection
};
return new DefaultRabbitMQPersistentConnection(factory, logger);
})
.AddSingleton<IEventBus, EventBusRabbitMQ>();
RegisterServiceBus(services);
return services.BuildServiceProvider();
}
public static void Configure(ILoggerFactory loggerFactory)
{
loggerFactory
.AddConsole(Configuration.GetSection("Logging"))
.AddConsole(LogLevel.Debug);
}
private static void RegisterServiceBus(IServiceCollection services)
{
services.AddSingleton<IEventBus, EventBusRabbitMQ>();
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
}
}
}

+ 34
- 0
src/Services/SagaManager/SagaManager/SagaManager.csproj View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBusRabbitMQ\EventBusRabbitMQ.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\EventBus\EventBus.csproj" />
<ProjectReference Include="..\..\..\BuildingBlocks\EventBus\IntegrationEventLogEF\IntegrationEventLogEF.csproj" />
<ProjectReference Include="..\..\Ordering\Ordering.Infrastructure\Ordering.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<None Update=".dockerignore">
<DependentUpon>Dockerfile</DependentUpon>
</None>
</ItemGroup>
</Project>

+ 11
- 0
src/Services/SagaManager/SagaManager/SagaManagerSettings.cs View File

@ -0,0 +1,11 @@
namespace SagaManager
{
public class SagaManagerSettings
{
public string ConnectionString { get; set; }
public string EventBusConnection { get; set; }
public int GracePeriod { get; set; }
}
}

+ 7
- 0
src/Services/SagaManager/SagaManager/Services/ISagaManagerService.cs View File

@ -0,0 +1,7 @@
namespace SagaManager.Services
{
public interface ISagaManagerService
{
void CheckFinishedGracePeriodOrders();
}
}

+ 72
- 0
src/Services/SagaManager/SagaManager/Services/SagaManagerService.cs View File

@ -0,0 +1,72 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace SagaManager.Services
{
using System.Collections.Generic;
using System.Data.SqlClient;
using Microsoft.Extensions.Options;
using Dapper;
using IntegrationEvents;
using IntegrationEvents.Events;
public class SagaManagerService : ISagaManagerService
{
private readonly SagaManagerSettings _settings;
private readonly ISagaManagerIntegrationEventService _sagaManagerIntegrationEventService;
private readonly ILogger<SagaManagerService> _logger;
public SagaManagerService(IOptions<SagaManagerSettings> settings,
ISagaManagerIntegrationEventService sagaManagerIntegrationEventService,
ILogger<SagaManagerService> logger)
{
_settings = settings.Value;
_sagaManagerIntegrationEventService = sagaManagerIntegrationEventService;
_logger = logger;
}
public void CheckFinishedGracePeriodOrders()
{
var orderIds = GetFinishedGracePeriodOrders();
foreach (var orderId in orderIds)
{
Publish(orderId);
}
}
private IEnumerable<int> GetFinishedGracePeriodOrders()
{
IEnumerable<int> orderIds = new List<int>();
using (var conn = new SqlConnection(_settings.ConnectionString))
{
try
{
_logger.LogInformation("SagaManager Client is trying to connect to database server");
conn.Open();
orderIds = conn.Query<int>(
@"SELECT Id FROM [Microsoft.eShopOnContainers.Services.OrderingDb].[ordering].[orders]
WHERE DATEDIFF(hour, [OrderDate], GETDATE()) >= @GracePeriod
AND [OrderStatusId] = 1",
new { GracePeriod = _settings.GracePeriod });
}
catch (SqlException exception)
{
_logger.LogCritical($"FATAL ERROR: Database connections could not be opened: {exception.Message}");
}
}
return orderIds;
}
private void Publish(int orderId)
{
var confirmGracePeriodEvent = new ConfirmGracePeriodCommandMsg(orderId);
// Publish through the Event Bus
_sagaManagerIntegrationEventService.PublishThroughEventBus(confirmGracePeriodEvent);
}
}
}

+ 11
- 0
src/Services/SagaManager/SagaManager/appsettings.json View File

@ -0,0 +1,11 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ConnectionString": "Server=tcp:127.0.0.1,5433;Database=Microsoft.eShopOnContainers.Services.OrderingDb;User Id=sa;Password=Pass@word;"
}

Loading…
Cancel
Save