Skip to content

Commit f9b1548

Browse files
committed
Idempotent updates based on requestid
1 parent dba9351 commit f9b1548

27 files changed

Lines changed: 536 additions & 65 deletions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: '2'
2+
3+
services:
4+
sql.data:
5+
environment:
6+
- SA_PASSWORD=Pass@word
7+
- ACCEPT_EULA=Y
8+
ports:
9+
- "5433:1433"

docker-compose-external.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version: '2'
2+
3+
services:
4+
sql.data:
5+
image: microsoft/mssql-server-linux
6+
7+
basket.data:
8+
image: redis
9+
ports:
10+
- "6379:6379"

eShopOnContainers-ServicesAndWebApps.sln

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@ Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-co
4848
EndProject
4949
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSPA", "src\Web\WebSPA\WebSPA.csproj", "{F16E3C6A-1C94-4EAB-BE91-099618060B68}"
5050
EndProject
51-
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose-windows", "docker-compose-windows.dcproj", "{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}"
52-
EndProject
5351
Global
5452
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5553
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -496,54 +494,6 @@ Global
496494
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x64.Build.0 = Release|Any CPU
497495
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x86.ActiveCfg = Release|Any CPU
498496
{F16E3C6A-1C94-4EAB-BE91-099618060B68}.Release|x86.Build.0 = Release|Any CPU
499-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
500-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
501-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
502-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
503-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
504-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
505-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
506-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
507-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
508-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
509-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
510-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
511-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
512-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|Any CPU.Build.0 = Debug|Any CPU
513-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|ARM.ActiveCfg = Debug|Any CPU
514-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|ARM.Build.0 = Debug|Any CPU
515-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
516-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhone.Build.0 = Debug|Any CPU
517-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
518-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
519-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x64.ActiveCfg = Debug|Any CPU
520-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x64.Build.0 = Debug|Any CPU
521-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x86.ActiveCfg = Debug|Any CPU
522-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.AppStore|x86.Build.0 = Debug|Any CPU
523-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
524-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|Any CPU.Build.0 = Debug|Any CPU
525-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|ARM.ActiveCfg = Debug|Any CPU
526-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|ARM.Build.0 = Debug|Any CPU
527-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
528-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhone.Build.0 = Debug|Any CPU
529-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
530-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
531-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x64.ActiveCfg = Debug|Any CPU
532-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x64.Build.0 = Debug|Any CPU
533-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x86.ActiveCfg = Debug|Any CPU
534-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Debug|x86.Build.0 = Debug|Any CPU
535-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|Any CPU.ActiveCfg = Release|Any CPU
536-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|Any CPU.Build.0 = Release|Any CPU
537-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|ARM.ActiveCfg = Release|Any CPU
538-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|ARM.Build.0 = Release|Any CPU
539-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhone.ActiveCfg = Release|Any CPU
540-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhone.Build.0 = Release|Any CPU
541-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
542-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
543-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x64.ActiveCfg = Release|Any CPU
544-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x64.Build.0 = Release|Any CPU
545-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x86.ActiveCfg = Release|Any CPU
546-
{2EDE831A-98F5-4F23-B2DB-7E9DC935766A}.Release|x86.Build.0 = Release|Any CPU
547497
EndGlobalSection
548498
GlobalSection(SolutionProperties) = preSolution
549499
HideSolutionNode = FALSE

src/Services/Catalog/Catalog.API/Catalog.API.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
</ItemGroup>
5050

5151
<ItemGroup>
52-
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0-msbuild3-final" />
52+
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
5353
</ItemGroup>
5454

5555
<ItemGroup>

src/Services/Catalog/Catalog.API/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Microsoft.Extensions.Logging;
1313
using System;
1414
using System.IO;
15+
using System.Reflection;
1516
using System.Threading;
1617
using System.Threading.Tasks;
1718

@@ -28,7 +29,7 @@ public Startup(IHostingEnvironment env)
2829

2930
if (env.IsDevelopment())
3031
{
31-
builder.AddUserSecrets();
32+
builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly);
3233
}
3334

3435
builder.AddEnvironmentVariables();

src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public CreateOrderCommand()
6464
_orderItems = new List<OrderItemDTO>();
6565
}
6666

67-
public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
67+
public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
6868
string cardNumber, string cardHolderName, DateTime cardExpiration,
6969
string cardSecurityNumber, int cardTypeId) : this()
7070
{

src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,23 @@
44
using Domain.AggregatesModel.OrderAggregate;
55
using MediatR;
66
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
7+
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories;
78
using System;
89
using System.Threading.Tasks;
910

11+
12+
public class CreateOrderCommandIdentifiedHandler : IdentifierCommandHandler<CreateOrderCommand, bool>
13+
{
14+
public CreateOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager)
15+
{
16+
}
17+
18+
protected override bool CreateResultForDuplicateRequest()
19+
{
20+
return true; // Ignore duplicate requests for creating order.
21+
}
22+
}
23+
1024
public class CreateOrderCommandHandler
1125
: IAsyncRequestHandler<CreateOrderCommand, bool>
1226
{
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using MediatR;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
7+
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
8+
{
9+
public class IdentifiedCommand<T, R> : IAsyncRequest<R>
10+
where T : IAsyncRequest<R>
11+
{
12+
public T Command { get; }
13+
public Guid Id { get; }
14+
public IdentifiedCommand(T command, Guid id)
15+
{
16+
Command = command;
17+
Id = id;
18+
}
19+
}
20+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using MediatR;
2+
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
9+
{
10+
/// <summary>
11+
/// Provides a base implementation for handling duplicate request and ensuring idempotent updates, in the cases where
12+
/// a requestid sent by client is used to detect duplicate requests.
13+
/// </summary>
14+
/// <typeparam name="T">Type of the command handler that performs the operation if request is not duplicated</typeparam>
15+
/// <typeparam name="R">Return value of the inner command handler</typeparam>
16+
public class IdentifierCommandHandler<T, R> : IAsyncRequestHandler<IdentifiedCommand<T, R>, R>
17+
where T : IAsyncRequest<R>
18+
{
19+
private readonly IMediator _mediator;
20+
private readonly IRequestManager _requestManager;
21+
22+
public IdentifierCommandHandler(IMediator mediator, IRequestManager requestManager)
23+
{
24+
_mediator = mediator;
25+
_requestManager = requestManager;
26+
}
27+
28+
/// <summary>
29+
/// Creates the result value to return if a previous request was found
30+
/// </summary>
31+
/// <returns></returns>
32+
protected virtual R CreateResultForDuplicateRequest()
33+
{
34+
return default(R);
35+
}
36+
37+
/// <summary>
38+
/// This method handles the command. It just ensures that no other request exists with the same ID, and if this is the case
39+
/// just enqueues the original inner command.
40+
/// </summary>
41+
/// <param name="message">IdentifiedCommand which contains both original command & request ID</param>
42+
/// <returns>Return value of inner command or default value if request same ID was found</returns>
43+
public async Task<R> Handle(IdentifiedCommand<T, R> message)
44+
{
45+
var alreadyExists = await _requestManager.ExistAsync(message.Id);
46+
if (alreadyExists)
47+
{
48+
return CreateResultForDuplicateRequest();
49+
}
50+
else
51+
{
52+
await _requestManager.CreateRequestForCommandAsync<T>(message.Id);
53+
var result = await _mediator.SendAsync(message.Command);
54+
return result;
55+
}
56+
}
57+
}
58+
59+
60+
}

src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@ public OrdersController(IMediator mediator, IOrderQueries orderQueries, IIdentit
2828

2929
[Route("new")]
3030
[HttpPost]
31-
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand)
31+
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand, [FromHeader(Name = "x-requestid")] string requestId)
3232
{
33-
var result = await _mediator.SendAsync(createOrderCommand);
33+
bool result = false;
34+
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
35+
{
36+
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, guid);
37+
result = await _mediator.SendAsync(requestCreateOrder);
38+
}
3439
if (result)
3540
{
3641
return Ok();

0 commit comments

Comments
 (0)