@ -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); | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -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"); | |||||
} | |||||
} | |||||
} |
@ -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"); | |||||
} | |||||
} | |||||
} | |||||
} |
@ -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; | |||||
} | |||||
} | |||||
} |
@ -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; | |||||
} | |||||
} |
@ -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; | |||||
} | |||||
} | |||||
} |
@ -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); | |||||
} | |||||
} | |||||
} |
@ -1,15 +1,12 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | 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 | namespace Ordering.API.Application.IntegrationCommands.Commands | ||||
{ | { | ||||
public class ConfirmGracePeriodCommandMsg : IntegrationEvent | 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; | |||||
} | } | ||||
} | } |
@ -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; | |||||
} | |||||
} | |||||
} |
@ -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; | |||||
} | |||||
} | |||||
} |
@ -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"); | |||||
} | |||||
} | |||||
} | |||||
} |
@ -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"); | |||||
} | |||||
} | |||||
} | |||||
} |
@ -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; | |||||
} | |||||
} |
@ -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; | |||||
} | |||||
} | |||||
} |
@ -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; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
* | |||||
!obj/Docker/publish/* | |||||
!obj/Docker/empty/ |
@ -0,0 +1,5 @@ | |||||
FROM microsoft/dotnet:1.1-runtime | |||||
ARG source | |||||
WORKDIR /app | |||||
COPY ${source:-obj/Docker/publish} . | |||||
ENTRYPOINT ["dotnet", "SagaManager.dll"] |
@ -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; | |||||
} | |||||
} |
@ -0,0 +1,9 @@ | |||||
namespace SagaManager.IntegrationEvents | |||||
{ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
public interface ISagaManagerIntegrationEventService | |||||
{ | |||||
void PublishThroughEventBus(IntegrationEvent evt); | |||||
} | |||||
} |
@ -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); | |||||
} | |||||
} | |||||
} |
@ -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>(); | |||||
} | |||||
} | |||||
} |
@ -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> |
@ -0,0 +1,11 @@ | |||||
namespace SagaManager | |||||
{ | |||||
public class SagaManagerSettings | |||||
{ | |||||
public string ConnectionString { get; set; } | |||||
public string EventBusConnection { get; set; } | |||||
public int GracePeriod { get; set; } | |||||
} | |||||
} |
@ -0,0 +1,7 @@ | |||||
namespace SagaManager.Services | |||||
{ | |||||
public interface ISagaManagerService | |||||
{ | |||||
void CheckFinishedGracePeriodOrders(); | |||||
} | |||||
} |
@ -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); | |||||
} | |||||
} | |||||
} |
@ -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;" | |||||
} |