@ -1,160 +0,0 @@ | |||||
######################################################################################################### | |||||
# This "expanded Script" can be used when debugging issues when building the .NET Core bits | |||||
# as it is easier to follow and debug than when using a loop (like in the optimized build-bits.ps1) | |||||
######################################################################################################### | |||||
Param([string] $rootPath) | |||||
$scriptPath = Split-Path $script:MyInvocation.MyCommand.Path | |||||
Write-Host "Current script directory is $scriptPath" -ForegroundColor Yellow | |||||
if ([string]::IsNullOrEmpty($rootPath)) { | |||||
$rootPath = "$scriptPath\.." | |||||
} | |||||
Write-Host "Root path used is $rootPath" -ForegroundColor Yellow | |||||
# *** WebMVC paths *** | |||||
$webMVCPath = $rootPath + "\src\Web\WebMVC" | |||||
$webMVCPathToProject = $webMVCPath + "\WebMVC.csproj" | |||||
Write-Host "webMVCPathToProject is $webMVCPathToProject" -ForegroundColor Yellow | |||||
$webMVCPathToPub = $webMVCPath + "\obj\Docker\publish" | |||||
Write-Host "webMVCPathToPub is $webMVCPathToPub" -ForegroundColor Yellow | |||||
# *** WebSPA paths *** | |||||
$webSPAPath = $rootPath + "\src\Web\WebSPA" | |||||
$webSPAPathToProject = $webSPAPath + "\WebSPA.csproj" | |||||
Write-Host "webSPAPathToProject is $webSPAPathToProject" -ForegroundColor Yellow | |||||
$webSPAPathToPub = $webSPAPath + "\obj\Docker\publish" | |||||
Write-Host "webSPAPathToPub is $webSPAPathToPub" -ForegroundColor Yellow | |||||
# *** IdentitySvc paths *** | |||||
$identitySvcPath = $rootPath + "\src\Services\Identity\Identity.API" | |||||
$identitySvcToProject = $identitySvcPath + "\Identity.API.csproj" | |||||
Write-Host "identitySvcToProject is $identitySvcToProject" -ForegroundColor Yellow | |||||
$identitySvcPathToPub = $identitySvcPath + "\obj\Docker\publish" | |||||
Write-Host "identitySvcPathToPub is $identitySvcPathToPub" -ForegroundColor Yellow | |||||
# *** Catalog paths *** | |||||
$catalogPath = $rootPath + "\src\Services\Catalog\Catalog.API" | |||||
$catalogPathToProject = $catalogPath + "\Catalog.API.csproj" | |||||
Write-Host "catalogPathToProject is $catalogPathToProject" -ForegroundColor Yellow | |||||
$catalogPathToPub = $catalogPath + "\obj\Docker\publish" | |||||
Write-Host "catalogPathToPub is $catalogPathToPub" -ForegroundColor Yellow | |||||
# *** Ordering paths *** | |||||
$orderingPath = $rootPath + "\src\Services\Ordering\Ordering.API" | |||||
$orderingPathToProject = $orderingPath + "\Ordering.API.csproj" | |||||
Write-Host "orderingPathToProject is $orderingPathToProject" -ForegroundColor Yellow | |||||
$orderingPathToPub = $orderingPath + "\obj\Docker\publish" | |||||
Write-Host "orderingPathToPub is $orderingPathToPub" -ForegroundColor Yellow | |||||
# *** Basket paths *** | |||||
$basketPath = $rootPath + "\src\Services\Basket\Basket.API" | |||||
$basketPathToProject = $basketPath + "\Basket.API.csproj" | |||||
Write-Host "basketPathToProject is $basketPathToProject" -ForegroundColor Yellow | |||||
$basketPathToPub = $basketPath + "\obj\Docker\publish" | |||||
Write-Host "basketPathToPub is $basketPathToPub" -ForegroundColor Yellow | |||||
######################################################################################## | |||||
# Delete old eShop dotnet publish bits | |||||
######################################################################################## | |||||
# Write-Host "Deleting previous dotnet publish bits from all projects" -ForegroundColor Blue | |||||
remove-item -path $WebMVCPathToPub -Force -Recurse -ErrorAction SilentlyContinue | |||||
remove-item -path $webSPAPathToPub -Force -Recurse -ErrorAction SilentlyContinue | |||||
remove-item -path $identitySvcPathToPub -Force -Recurse -ErrorAction SilentlyContinue | |||||
remove-item -path $catalogPathToPub -Force -Recurse -ErrorAction SilentlyContinue | |||||
remove-item -path $orderingPathToPub -Force -Recurse -ErrorAction SilentlyContinue | |||||
remove-item -path $basketPathToPub -Force -Recurse -ErrorAction SilentlyContinue | |||||
######################################################################################## | |||||
# Building DotNet bits | |||||
######################################################################################## | |||||
# WebMVC: Build dotnet bits | |||||
Write-Host "WebMVC: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue | |||||
dotnet restore $WebMVCPathToProject | |||||
dotnet build $WebMVCPathToProject | |||||
dotnet publish $WebMVCPathToProject -o $WebMVCPathToPub | |||||
# WebSPA: Build dotnet bits | |||||
Write-Host "WebSPA: Installing npm dependencies" | |||||
#TEMP COMMENT--- Start-Process -WorkingDirectory $webSPAPath -NoNewWindow -Wait npm i | |||||
Write-Host "WebSPA: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue | |||||
dotnet restore $webSPAPathToProject | |||||
dotnet build $webSPAPathToProject | |||||
dotnet publish $webSPAPathToProject -o $webSPAPathToPub | |||||
# Identity Service: Build dotnet bits | |||||
Write-Host "Identity Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue | |||||
dotnet restore $identitySvcToProject | |||||
dotnet build $identitySvcToProject | |||||
dotnet publish $identitySvcToProject -o $identitySvcPathToPub | |||||
# Catalog Service: Build dotnet bits | |||||
Write-Host "Catalog Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue | |||||
dotnet restore $catalogPathToProject | |||||
dotnet build $catalogPathToProject | |||||
dotnet publish $catalogPathToProject -o $catalogPathToPub | |||||
# Ordering Service: Build dotnet bits | |||||
Write-Host "Ordering Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue | |||||
dotnet restore $orderingPathToProject | |||||
dotnet build $orderingPathToProject | |||||
dotnet publish $orderingPathToProject -o $orderingPathToPub | |||||
# Basket Service: Build dotnet bits | |||||
Write-Host "Basket Service: Restore Dependencies, dotnet build and dotnet publish" -ForegroundColor Blue | |||||
dotnet restore $basketPathToProject | |||||
dotnet build $basketPathToProject | |||||
dotnet publish $basketPathToProject -o $basketPathToPub | |||||
######################################################################################## | |||||
# Delete old eShop Docker images | |||||
######################################################################################## | |||||
$imagesToDelete = docker images --filter=reference="eshop/*" -q | |||||
If (-Not $imagesToDelete) {Write-Host "Not deleting eShop images as there are no eShop images in the current local Docker repo."} | |||||
Else | |||||
{ | |||||
# Delete all containers | |||||
Write-Host "Deleting all containers in local Docker Host" | |||||
docker rm $(docker ps -a -q) -f | |||||
# Delete all eshop images | |||||
Write-Host "Deleting eShop images in local Docker repo" | |||||
Write-Host $imagesToDelete | |||||
docker rmi $(docker images --filter=reference="eshop/*" -q) -f | |||||
} | |||||
######################################################################################## | |||||
# Build new eShop images | |||||
######################################################################################## | |||||
# WE DON'T NEED DOCKER BUILD AS WE CAN RUN "DOCKER-COMPOSE BUILD" OR "DOCKER-COMPOSE UP" AND IT WILL BUILD ALL THE IMAGES IN THE .YML FOR US | |||||
#*** build docker images *** | |||||
# docker build -t eshop/web $webPathToPub | |||||
# docker build -t eshop/catalog.api $catalogPathToPub | |||||
# docker build -t eshop/ordering.api $orderingApiPathToPub | |||||
# docker build -t eshop/basket.api $basketPathToPub | |||||
# docker build -t eshop/webspa $webSPAPathToPub | |||||
# docker build -t eshop/identity $identitySvcPathToPub |
@ -0,0 +1,37 @@ | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Storage; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Data.Common; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities | |||||
{ | |||||
public class ResilientTransaction | |||||
{ | |||||
private DbContext _context; | |||||
private ResilientTransaction(DbContext context) => | |||||
_context = context ?? throw new ArgumentNullException(nameof(context)); | |||||
public static ResilientTransaction New (DbContext context) => | |||||
new ResilientTransaction(context); | |||||
public async Task ExecuteAsync(Func<Task> action) | |||||
{ | |||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): | |||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||||
var strategy = _context.Database.CreateExecutionStrategy(); | |||||
await strategy.ExecuteAsync(async () => | |||||
{ | |||||
using (var transaction = _context.Database.BeginTransaction()) | |||||
{ | |||||
await action(); | |||||
transaction.Commit(); | |||||
} | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
<Project Sdk="Microsoft.NET.Sdk"> | |||||
<PropertyGroup> | |||||
<TargetFramework>netstandard1.4</TargetFramework> | |||||
<RootNamespace>Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http</RootNamespace> | |||||
</PropertyGroup> | |||||
<ItemGroup> | |||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.0" /> | |||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" /> | |||||
<PackageReference Include="Polly" Version="5.0.6" /> | |||||
</ItemGroup> | |||||
</Project> |
@ -0,0 +1,10 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Text; | |||||
namespace Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http | |||||
{ | |||||
public class ResiliencePolicy | |||||
{ | |||||
} | |||||
} |
@ -0,0 +1,23 @@ | |||||
using System; | |||||
using System.Globalization; | |||||
using Xamarin.Forms; | |||||
namespace eShopOnContainers.Core.Converters | |||||
{ | |||||
public class WebNavigatedEventArgsConverter : IValueConverter | |||||
{ | |||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | |||||
{ | |||||
var eventArgs = value as WebNavigatedEventArgs; | |||||
if (eventArgs == null) | |||||
throw new ArgumentException("Expected WebNavigatedEventArgs as value", "value"); | |||||
return eventArgs.Url; | |||||
} | |||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | |||||
{ | |||||
throw new NotImplementedException(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,27 @@ | |||||
using Basket.API.IntegrationEvents.Events; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||||
using Microsoft.eShopOnContainers.Services.Basket.API.Model; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Basket.API.IntegrationEvents.EventHandling | |||||
{ | |||||
public class OrderStartedIntegrationEventHandler : IIntegrationEventHandler<OrderStartedIntegrationEvent> | |||||
{ | |||||
private readonly IBasketRepository _repository; | |||||
public OrderStartedIntegrationEventHandler(IBasketRepository repository) | |||||
{ | |||||
_repository = repository; | |||||
} | |||||
public async Task Handle(OrderStartedIntegrationEvent @event) | |||||
{ | |||||
await _repository.DeleteBasketAsync(@event.UserId.ToString()); | |||||
} | |||||
} | |||||
} | |||||
@ -0,0 +1,19 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Basket.API.IntegrationEvents.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 OrderStartedIntegrationEvent : IntegrationEvent | |||||
{ | |||||
public string UserId { get; } | |||||
public OrderStartedIntegrationEvent(string userId) => | |||||
UserId = userId; | |||||
} | |||||
} |
@ -0,0 +1,48 @@ | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Storage; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; | |||||
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; | |||||
using System; | |||||
using System.Data.Common; | |||||
using System.Threading.Tasks; | |||||
namespace Catalog.API.IntegrationEvents | |||||
{ | |||||
public class CatalogIntegrationEventService : ICatalogIntegrationEventService | |||||
{ | |||||
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory; | |||||
private readonly IEventBus _eventBus; | |||||
private readonly CatalogContext _catalogContext; | |||||
private readonly IIntegrationEventLogService _eventLogService; | |||||
public CatalogIntegrationEventService(IEventBus eventBus, CatalogContext catalogContext, | |||||
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory) | |||||
{ | |||||
_catalogContext = catalogContext ?? throw new ArgumentNullException(nameof(catalogContext)); | |||||
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); | |||||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); | |||||
_eventLogService = _integrationEventLogServiceFactory(_catalogContext.Database.GetDbConnection()); | |||||
} | |||||
public async Task PublishThroughEventBusAsync(IntegrationEvent evt) | |||||
{ | |||||
_eventBus.Publish(evt); | |||||
await _eventLogService.MarkEventAsPublishedAsync(evt); | |||||
} | |||||
public async Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt) | |||||
{ | |||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): | |||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||||
await ResilientTransaction.New(_catalogContext) | |||||
.ExecuteAsync(async () => { | |||||
// Achieving atomicity between original catalog database operation and the IntegrationEventLog thanks to a local transaction | |||||
await _catalogContext.SaveChangesAsync(); | |||||
await _eventLogService.SaveEventAsync(evt, _catalogContext.Database.CurrentTransaction.GetDbTransaction()); | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Catalog.API.IntegrationEvents | |||||
{ | |||||
public interface ICatalogIntegrationEventService | |||||
{ | |||||
Task SaveEventAndCatalogContextChangesAsync(IntegrationEvent evt); | |||||
Task PublishThroughEventBusAsync(IntegrationEvent evt); | |||||
} | |||||
} |
@ -0,0 +1,43 @@ | |||||
using System; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||||
using Microsoft.EntityFrameworkCore.Metadata; | |||||
using Microsoft.EntityFrameworkCore.Migrations; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; | |||||
namespace Ordering.API.Infrastructure.IntegrationEventMigrations | |||||
{ | |||||
[DbContext(typeof(IntegrationEventLogContext))] | |||||
[Migration("20170330131634_IntegrationEventInitial")] | |||||
partial class IntegrationEventInitial | |||||
{ | |||||
protected override void BuildTargetModel(ModelBuilder modelBuilder) | |||||
{ | |||||
modelBuilder | |||||
.HasAnnotation("ProductVersion", "1.1.1") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b => | |||||
{ | |||||
b.Property<Guid>("EventId") | |||||
.ValueGeneratedOnAdd(); | |||||
b.Property<string>("Content") | |||||
.IsRequired(); | |||||
b.Property<DateTime>("CreationTime"); | |||||
b.Property<string>("EventTypeName") | |||||
.IsRequired(); | |||||
b.Property<int>("State"); | |||||
b.Property<int>("TimesSent"); | |||||
b.HasKey("EventId"); | |||||
b.ToTable("IntegrationEventLog"); | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,34 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using Microsoft.EntityFrameworkCore.Migrations; | |||||
namespace Ordering.API.Infrastructure.IntegrationEventMigrations | |||||
{ | |||||
public partial class IntegrationEventInitial : Migration | |||||
{ | |||||
protected override void Up(MigrationBuilder migrationBuilder) | |||||
{ | |||||
migrationBuilder.CreateTable( | |||||
name: "IntegrationEventLog", | |||||
columns: table => new | |||||
{ | |||||
EventId = table.Column<Guid>(nullable: false), | |||||
Content = table.Column<string>(nullable: false), | |||||
CreationTime = table.Column<DateTime>(nullable: false), | |||||
EventTypeName = table.Column<string>(nullable: false), | |||||
State = table.Column<int>(nullable: false), | |||||
TimesSent = table.Column<int>(nullable: false) | |||||
}, | |||||
constraints: table => | |||||
{ | |||||
table.PrimaryKey("PK_IntegrationEventLog", x => x.EventId); | |||||
}); | |||||
} | |||||
protected override void Down(MigrationBuilder migrationBuilder) | |||||
{ | |||||
migrationBuilder.DropTable( | |||||
name: "IntegrationEventLog"); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,42 @@ | |||||
using System; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||||
using Microsoft.EntityFrameworkCore.Metadata; | |||||
using Microsoft.EntityFrameworkCore.Migrations; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF; | |||||
namespace Ordering.API.Infrastructure.IntegrationEventMigrations | |||||
{ | |||||
[DbContext(typeof(IntegrationEventLogContext))] | |||||
partial class IntegrationEventLogContextModelSnapshot : ModelSnapshot | |||||
{ | |||||
protected override void BuildModel(ModelBuilder modelBuilder) | |||||
{ | |||||
modelBuilder | |||||
.HasAnnotation("ProductVersion", "1.1.1") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.IntegrationEventLogEntry", b => | |||||
{ | |||||
b.Property<Guid>("EventId") | |||||
.ValueGeneratedOnAdd(); | |||||
b.Property<string>("Content") | |||||
.IsRequired(); | |||||
b.Property<DateTime>("CreationTime"); | |||||
b.Property<string>("EventTypeName") | |||||
.IsRequired(); | |||||
b.Property<int>("State"); | |||||
b.Property<int>("TimesSent"); | |||||
b.HasKey("EventId"); | |||||
b.ToTable("IntegrationEventLog"); | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,244 @@ | |||||
using System; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||||
using Microsoft.EntityFrameworkCore.Metadata; | |||||
using Microsoft.EntityFrameworkCore.Migrations; | |||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; | |||||
namespace Ordering.API.Migrations | |||||
{ | |||||
[DbContext(typeof(OrderingContext))] | |||||
[Migration("20170403082405_NoBuyerPropertyInOrder")] | |||||
partial class NoBuyerPropertyInOrder | |||||
{ | |||||
protected override void BuildTargetModel(ModelBuilder modelBuilder) | |||||
{ | |||||
modelBuilder | |||||
.HasAnnotation("ProductVersion", "1.1.0-rtm-22752") | |||||
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq") | |||||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<string>("IdentityGuid") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("IdentityGuid") | |||||
.IsUnique(); | |||||
b.ToTable("buyers","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.HasDefaultValue(1); | |||||
b.Property<string>("Name") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.HasKey("Id"); | |||||
b.ToTable("cardtypes","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq") | |||||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<string>("Alias") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.Property<int>("BuyerId"); | |||||
b.Property<string>("CardHolderName") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.Property<string>("CardNumber") | |||||
.IsRequired() | |||||
.HasMaxLength(25); | |||||
b.Property<int>("CardTypeId"); | |||||
b.Property<DateTime>("Expiration"); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("BuyerId"); | |||||
b.HasIndex("CardTypeId"); | |||||
b.ToTable("paymentmethods","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd(); | |||||
b.Property<string>("City"); | |||||
b.Property<string>("Country"); | |||||
b.Property<string>("State"); | |||||
b.Property<string>("Street"); | |||||
b.Property<string>("ZipCode"); | |||||
b.HasKey("Id"); | |||||
b.ToTable("address","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq") | |||||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<int?>("AddressId"); | |||||
b.Property<int?>("BuyerId"); | |||||
b.Property<DateTime>("OrderDate"); | |||||
b.Property<int>("OrderStatusId"); | |||||
b.Property<int?>("PaymentMethodId"); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("AddressId"); | |||||
b.HasIndex("BuyerId"); | |||||
b.HasIndex("OrderStatusId"); | |||||
b.HasIndex("PaymentMethodId"); | |||||
b.ToTable("orders","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<decimal>("Discount"); | |||||
b.Property<int>("OrderId"); | |||||
b.Property<string>("PictureUrl"); | |||||
b.Property<int>("ProductId"); | |||||
b.Property<string>("ProductName") | |||||
.IsRequired(); | |||||
b.Property<decimal>("UnitPrice"); | |||||
b.Property<int>("Units"); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("OrderId"); | |||||
b.ToTable("orderItems","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.HasDefaultValue(1); | |||||
b.Property<string>("Name") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.HasKey("Id"); | |||||
b.ToTable("orderstatus","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b => | |||||
{ | |||||
b.Property<Guid>("Id") | |||||
.ValueGeneratedOnAdd(); | |||||
b.Property<string>("Name") | |||||
.IsRequired(); | |||||
b.Property<DateTime>("Time"); | |||||
b.HasKey("Id"); | |||||
b.ToTable("requests","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b => | |||||
{ | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer") | |||||
.WithMany("PaymentMethods") | |||||
.HasForeignKey("BuyerId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType") | |||||
.WithMany() | |||||
.HasForeignKey("CardTypeId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => | |||||
{ | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address") | |||||
.WithMany() | |||||
.HasForeignKey("AddressId"); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer") | |||||
.WithMany() | |||||
.HasForeignKey("BuyerId"); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus") | |||||
.WithMany() | |||||
.HasForeignKey("OrderStatusId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", "PaymentMethod") | |||||
.WithMany() | |||||
.HasForeignKey("PaymentMethodId"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b => | |||||
{ | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order") | |||||
.WithMany("OrderItems") | |||||
.HasForeignKey("OrderId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,19 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using Microsoft.EntityFrameworkCore.Migrations; | |||||
namespace Ordering.API.Migrations | |||||
{ | |||||
public partial class NoBuyerPropertyInOrder : Migration | |||||
{ | |||||
protected override void Up(MigrationBuilder migrationBuilder) | |||||
{ | |||||
} | |||||
protected override void Down(MigrationBuilder migrationBuilder) | |||||
{ | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,244 @@ | |||||
using System; | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Infrastructure; | |||||
using Microsoft.EntityFrameworkCore.Metadata; | |||||
using Microsoft.EntityFrameworkCore.Migrations; | |||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; | |||||
namespace Ordering.API.Migrations | |||||
{ | |||||
[DbContext(typeof(OrderingContext))] | |||||
[Migration("20170405110939_NoPaymentMethodPropertyInOrder")] | |||||
partial class NoPaymentMethodPropertyInOrder | |||||
{ | |||||
protected override void BuildTargetModel(ModelBuilder modelBuilder) | |||||
{ | |||||
modelBuilder | |||||
.HasAnnotation("ProductVersion", "1.1.1") | |||||
.HasAnnotation("SqlServer:Sequence:.orderitemseq", "'orderitemseq', '', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:Sequence:ordering.buyerseq", "'buyerseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:Sequence:ordering.orderseq", "'orderseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:Sequence:ordering.paymentseq", "'paymentseq', 'ordering', '1', '10', '', '', 'Int64', 'False'") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "buyerseq") | |||||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<string>("IdentityGuid") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("IdentityGuid") | |||||
.IsUnique(); | |||||
b.ToTable("buyers","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.HasDefaultValue(1); | |||||
b.Property<string>("Name") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.HasKey("Id"); | |||||
b.ToTable("cardtypes","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "paymentseq") | |||||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<string>("Alias") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.Property<int>("BuyerId"); | |||||
b.Property<string>("CardHolderName") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.Property<string>("CardNumber") | |||||
.IsRequired() | |||||
.HasMaxLength(25); | |||||
b.Property<int>("CardTypeId"); | |||||
b.Property<DateTime>("Expiration"); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("BuyerId"); | |||||
b.HasIndex("CardTypeId"); | |||||
b.ToTable("paymentmethods","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd(); | |||||
b.Property<string>("City"); | |||||
b.Property<string>("Country"); | |||||
b.Property<string>("State"); | |||||
b.Property<string>("Street"); | |||||
b.Property<string>("ZipCode"); | |||||
b.HasKey("Id"); | |||||
b.ToTable("address","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "orderseq") | |||||
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<int?>("AddressId"); | |||||
b.Property<int?>("BuyerId"); | |||||
b.Property<DateTime>("OrderDate"); | |||||
b.Property<int>("OrderStatusId"); | |||||
b.Property<int?>("PaymentMethodId"); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("AddressId"); | |||||
b.HasIndex("BuyerId"); | |||||
b.HasIndex("OrderStatusId"); | |||||
b.HasIndex("PaymentMethodId"); | |||||
b.ToTable("orders","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.ValueGeneratedOnAdd() | |||||
.HasAnnotation("SqlServer:HiLoSequenceName", "orderitemseq") | |||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); | |||||
b.Property<decimal>("Discount"); | |||||
b.Property<int>("OrderId"); | |||||
b.Property<string>("PictureUrl"); | |||||
b.Property<int>("ProductId"); | |||||
b.Property<string>("ProductName") | |||||
.IsRequired(); | |||||
b.Property<decimal>("UnitPrice"); | |||||
b.Property<int>("Units"); | |||||
b.HasKey("Id"); | |||||
b.HasIndex("OrderId"); | |||||
b.ToTable("orderItems","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", b => | |||||
{ | |||||
b.Property<int>("Id") | |||||
.HasDefaultValue(1); | |||||
b.Property<string>("Name") | |||||
.IsRequired() | |||||
.HasMaxLength(200); | |||||
b.HasKey("Id"); | |||||
b.ToTable("orderstatus","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b => | |||||
{ | |||||
b.Property<Guid>("Id") | |||||
.ValueGeneratedOnAdd(); | |||||
b.Property<string>("Name") | |||||
.IsRequired(); | |||||
b.Property<DateTime>("Time"); | |||||
b.HasKey("Id"); | |||||
b.ToTable("requests","ordering"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b => | |||||
{ | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer") | |||||
.WithMany("PaymentMethods") | |||||
.HasForeignKey("BuyerId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.CardType", "CardType") | |||||
.WithMany() | |||||
.HasForeignKey("CardTypeId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => | |||||
{ | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address") | |||||
.WithMany() | |||||
.HasForeignKey("AddressId"); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer") | |||||
.WithMany() | |||||
.HasForeignKey("BuyerId"); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus") | |||||
.WithMany() | |||||
.HasForeignKey("OrderStatusId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod") | |||||
.WithMany() | |||||
.HasForeignKey("PaymentMethodId"); | |||||
}); | |||||
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderItem", b => | |||||
{ | |||||
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order") | |||||
.WithMany("OrderItems") | |||||
.HasForeignKey("OrderId") | |||||
.OnDelete(DeleteBehavior.Cascade); | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,19 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using Microsoft.EntityFrameworkCore.Migrations; | |||||
namespace Ordering.API.Migrations | |||||
{ | |||||
public partial class NoPaymentMethodPropertyInOrder : Migration | |||||
{ | |||||
protected override void Up(MigrationBuilder migrationBuilder) | |||||
{ | |||||
} | |||||
protected override void Down(MigrationBuilder migrationBuilder) | |||||
{ | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,19 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Ordering.API.IntegrationEvents.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 OrderStartedIntegrationEvent : IntegrationEvent | |||||
{ | |||||
public string UserId { get; } | |||||
public OrderStartedIntegrationEvent(string userId) => | |||||
UserId = userId; | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace Ordering.API.IntegrationEvents | |||||
{ | |||||
public interface IOrderingIntegrationEventService | |||||
{ | |||||
Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt); | |||||
Task PublishThroughEventBusAsync(IntegrationEvent evt); | |||||
} | |||||
} |
@ -0,0 +1,49 @@ | |||||
using Microsoft.EntityFrameworkCore; | |||||
using Microsoft.EntityFrameworkCore.Storage; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Abstractions; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.EventBus.Events; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Services; | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.IntegrationEventLogEF.Utilities; | |||||
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; | |||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; | |||||
using System; | |||||
using System.Data.Common; | |||||
using System.Threading.Tasks; | |||||
namespace Ordering.API.IntegrationEvents | |||||
{ | |||||
public class OrderingIntegrationEventService : IOrderingIntegrationEventService | |||||
{ | |||||
private readonly Func<DbConnection, IIntegrationEventLogService> _integrationEventLogServiceFactory; | |||||
private readonly IEventBus _eventBus; | |||||
private readonly OrderingContext _orderingContext; | |||||
private readonly IIntegrationEventLogService _eventLogService; | |||||
public OrderingIntegrationEventService (IEventBus eventBus, OrderingContext orderingContext, | |||||
Func<DbConnection, IIntegrationEventLogService> integrationEventLogServiceFactory) | |||||
{ | |||||
_orderingContext = orderingContext ?? throw new ArgumentNullException(nameof(orderingContext)); | |||||
_integrationEventLogServiceFactory = integrationEventLogServiceFactory ?? throw new ArgumentNullException(nameof(integrationEventLogServiceFactory)); | |||||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); | |||||
_eventLogService = _integrationEventLogServiceFactory(_orderingContext.Database.GetDbConnection()); | |||||
} | |||||
public async Task PublishThroughEventBusAsync(IntegrationEvent evt) | |||||
{ | |||||
_eventBus.Publish(evt); | |||||
await _eventLogService.MarkEventAsPublishedAsync(evt); | |||||
} | |||||
public async Task SaveEventAndOrderingContextChangesAsync(IntegrationEvent evt) | |||||
{ | |||||
//Use of an EF Core resiliency strategy when using multiple DbContexts within an explicit BeginTransaction(): | |||||
//See: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency | |||||
await ResilientTransaction.New(_orderingContext) | |||||
.ExecuteAsync(async () => { | |||||
// Achieving atomicity between original ordering database operation and the IntegrationEventLog thanks to a local transaction | |||||
await _orderingContext.SaveChangesAsync(); | |||||
await _eventLogService.SaveEventAsync(evt, _orderingContext.Database.CurrentTransaction.GetDbTransaction()); | |||||
}); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,10 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http; | |||||
using System; | |||||
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure | |||||
{ | |||||
public interface IResilientHttpClientFactory | |||||
{ | |||||
ResilientHttpClient CreateResilientHttpClient(); | |||||
} | |||||
} |
@ -0,0 +1,59 @@ | |||||
using Microsoft.eShopOnContainers.BuildingBlocks.Resilience.Http; | |||||
using Microsoft.Extensions.Logging; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
using Polly; | |||||
using System.Net.Http; | |||||
namespace Microsoft.eShopOnContainers.WebMVC.Infrastructure | |||||
{ | |||||
public class ResilientHttpClientFactory : IResilientHttpClientFactory | |||||
{ | |||||
private readonly ILogger<ResilientHttpClient> _logger; | |||||
public ResilientHttpClientFactory(ILogger<ResilientHttpClient> logger) | |||||
=>_logger = logger; | |||||
public ResilientHttpClient CreateResilientHttpClient() | |||||
=> new ResilientHttpClient(CreatePolicies(), _logger); | |||||
private Policy[] CreatePolicies() | |||||
=> new Policy[] | |||||
{ | |||||
Policy.Handle<HttpRequestException>() | |||||
.WaitAndRetryAsync( | |||||
// number of retries | |||||
6, | |||||
// exponential backofff | |||||
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), | |||||
// on retry | |||||
(exception, timeSpan, retryCount, context) => | |||||
{ | |||||
var msg = $"Retry {retryCount} implemented with Polly's RetryPolicy " + | |||||
$"of {context.PolicyKey} " + | |||||
$"at {context.ExecutionKey}, " + | |||||
$"due to: {exception}."; | |||||
_logger.LogWarning(msg); | |||||
_logger.LogDebug(msg); | |||||
}), | |||||
Policy.Handle<HttpRequestException>() | |||||
.CircuitBreakerAsync( | |||||
// number of exceptions before breaking circuit | |||||
5, | |||||
// time circuit opened before retry | |||||
TimeSpan.FromMinutes(1), | |||||
(exception, duration) => | |||||
{ | |||||
// on circuit opened | |||||
_logger.LogTrace("Circuit breaker opened"); | |||||
}, | |||||
() => | |||||
{ | |||||
// on circuit closed | |||||
_logger.LogTrace("Circuit breaker reset"); | |||||
})}; | |||||
} | |||||
} |
@ -0,0 +1,20 @@ | |||||
using Microsoft.Extensions.HealthChecks; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Threading.Tasks; | |||||
namespace WebStatus.Viewmodels | |||||
{ | |||||
public class NamedCheckResult | |||||
{ | |||||
public string Name { get; } | |||||
public IHealthCheckResult Result { get; } | |||||
public NamedCheckResult(string name, IHealthCheckResult result) | |||||
{ | |||||
Name = name; | |||||
Result = result; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,7 @@ | |||||
{ | |||||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", | |||||
"ExternalCatalogBaseUrl": "http://localhost:5101", | |||||
"IdentityUrl": "http://localhost:5105", | |||||
"isTest": "true", | |||||
"EventBusConnection": "localhost" | |||||
} |
@ -0,0 +1,7 @@ | |||||
{ | |||||
"ConnectionString": "Server=tcp:127.0.0.1,5433;Initial Catalog=Microsoft.eShopOnContainers.Services.CatalogDb;User Id=sa;Password=Pass@word", | |||||
"ExternalCatalogBaseUrl": "http://localhost:5101", | |||||
"IdentityUrl": "http://localhost:5105", | |||||
"isTest": "true", | |||||
"EventBusConnection": "localhost" | |||||
} |