@ -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 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; | |||
} | |||
} |
@ -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;" | |||
} |