Merge branch 'idempotent-command'
This commit is contained in:
		
						commit
						b1b3abfcff
					
				
							
								
								
									
										9
									
								
								docker-compose-external.override.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								docker-compose-external.override.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | version: '2' | ||||||
|  | 
 | ||||||
|  | services: | ||||||
|  |   sql.data: | ||||||
|  |     environment: | ||||||
|  |       - SA_PASSWORD=Pass@word | ||||||
|  |       - ACCEPT_EULA=Y | ||||||
|  |     ports: | ||||||
|  |       - "5433:1433" | ||||||
							
								
								
									
										10
									
								
								docker-compose-external.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docker-compose-external.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | version: '2' | ||||||
|  | 
 | ||||||
|  | services: | ||||||
|  |   sql.data: | ||||||
|  |     image: microsoft/mssql-server-linux | ||||||
|  | 
 | ||||||
|  |   basket.data: | ||||||
|  |     image: redis | ||||||
|  |     ports: | ||||||
|  |       - "6379:6379" | ||||||
| @ -49,7 +49,7 @@ | |||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0-msbuild3-final" /> |     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ | |||||||
|     using Microsoft.Extensions.Logging; |     using Microsoft.Extensions.Logging; | ||||||
|     using System; |     using System; | ||||||
|     using System.IO; |     using System.IO; | ||||||
|  |     using System.Reflection; | ||||||
|     using System.Threading; |     using System.Threading; | ||||||
|     using System.Threading.Tasks; |     using System.Threading.Tasks; | ||||||
| 
 | 
 | ||||||
| @ -28,7 +29,7 @@ | |||||||
| 
 | 
 | ||||||
|             if (env.IsDevelopment()) |             if (env.IsDevelopment()) | ||||||
|             { |             { | ||||||
|                 builder.AddUserSecrets(); |                 builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             builder.AddEnvironmentVariables(); |             builder.AddEnvironmentVariables(); | ||||||
|  | |||||||
| @ -4,9 +4,23 @@ | |||||||
|     using Domain.AggregatesModel.OrderAggregate; |     using Domain.AggregatesModel.OrderAggregate; | ||||||
|     using MediatR; |     using MediatR; | ||||||
|     using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; |     using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services; | ||||||
|  |     using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories; | ||||||
|     using System; |     using System; | ||||||
|     using System.Threading.Tasks; |     using System.Threading.Tasks; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     public class CreateOrderCommandIdentifiedHandler : IdentifierCommandHandler<CreateOrderCommand, bool> | ||||||
|  |     { | ||||||
|  |         public CreateOrderCommandIdentifiedHandler(IMediator mediator, IRequestManager requestManager) : base(mediator, requestManager) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected override bool CreateResultForDuplicateRequest() | ||||||
|  |         { | ||||||
|  |             return true;                // Ignore duplicate requests for creating order. | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public class CreateOrderCommandHandler |     public class CreateOrderCommandHandler | ||||||
|         : IAsyncRequestHandler<CreateOrderCommand, bool> |         : IAsyncRequestHandler<CreateOrderCommand, bool> | ||||||
|     { |     { | ||||||
|  | |||||||
| @ -0,0 +1,20 @@ | |||||||
|  | using MediatR; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | ||||||
|  | { | ||||||
|  |     public class IdentifiedCommand<T, R> : IAsyncRequest<R> | ||||||
|  |         where T : IAsyncRequest<R> | ||||||
|  |     { | ||||||
|  |         public T Command { get; } | ||||||
|  |         public Guid Id { get; } | ||||||
|  |         public IdentifiedCommand(T command, Guid id) | ||||||
|  |         { | ||||||
|  |             Command = command; | ||||||
|  |             Id = id; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,60 @@ | |||||||
|  | using MediatR; | ||||||
|  | using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands | ||||||
|  | { | ||||||
|  |     /// <summary> | ||||||
|  |     /// Provides a base implementation for handling duplicate request and ensuring idempotent updates, in the cases where | ||||||
|  |     /// a requestid sent by client is used to detect duplicate requests. | ||||||
|  |     /// </summary> | ||||||
|  |     /// <typeparam name="T">Type of the command handler that performs the operation if request is not duplicated</typeparam> | ||||||
|  |     /// <typeparam name="R">Return value of the inner command handler</typeparam> | ||||||
|  |     public class IdentifierCommandHandler<T, R> : IAsyncRequestHandler<IdentifiedCommand<T, R>, R> | ||||||
|  |         where T : IAsyncRequest<R> | ||||||
|  |     { | ||||||
|  |         private readonly IMediator _mediator; | ||||||
|  |         private readonly IRequestManager _requestManager; | ||||||
|  | 
 | ||||||
|  |         public IdentifierCommandHandler(IMediator mediator, IRequestManager requestManager) | ||||||
|  |         { | ||||||
|  |             _mediator = mediator; | ||||||
|  |             _requestManager = requestManager; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// Creates the result value to return if a previous request was found | ||||||
|  |         /// </summary> | ||||||
|  |         /// <returns></returns> | ||||||
|  |         protected virtual R CreateResultForDuplicateRequest() | ||||||
|  |         { | ||||||
|  |             return default(R); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /// <summary> | ||||||
|  |         /// This method handles the command. It just ensures that no other request exists with the same ID, and if this is the case | ||||||
|  |         /// just enqueues the original inner command. | ||||||
|  |         /// </summary> | ||||||
|  |         /// <param name="message">IdentifiedCommand which contains both original command & request ID</param> | ||||||
|  |         /// <returns>Return value of inner command or default value if request same ID was found</returns> | ||||||
|  |         public async Task<R> Handle(IdentifiedCommand<T, R> message) | ||||||
|  |         { | ||||||
|  |             var alreadyExists = await _requestManager.ExistAsync(message.Id); | ||||||
|  |             if (alreadyExists) | ||||||
|  |             { | ||||||
|  |                 return CreateResultForDuplicateRequest(); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 await _requestManager.CreateRequestForCommandAsync<T>(message.Id); | ||||||
|  |                 var result = await _mediator.SendAsync(message.Command); | ||||||
|  |                 return result; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -28,9 +28,21 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers | |||||||
| 
 | 
 | ||||||
|         [Route("new")] |         [Route("new")] | ||||||
|         [HttpPost] |         [HttpPost] | ||||||
|         public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand) |         public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand, [FromHeader(Name = "x-requestid")] string requestId) | ||||||
|         { |         { | ||||||
|             var result = await _mediator.SendAsync(createOrderCommand); |             bool result = false; | ||||||
|  |             if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) | ||||||
|  |             { | ||||||
|  |                 var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, guid); | ||||||
|  |                 result = await _mediator.SendAsync(requestCreateOrder); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 // If no x-requestid header is found we process the order anyway. This is just temporary to not break existing clients | ||||||
|  |                 // that aren't still updated. When all clients were updated this could be removed. | ||||||
|  |                 result = await _mediator.SendAsync(createOrderCommand); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             if (result) |             if (result) | ||||||
|             { |             { | ||||||
|                 return Ok(); |                 return Ok(); | ||||||
|  | |||||||
| @ -33,6 +33,10 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof | |||||||
|             builder.RegisterType<OrderRepository>() |             builder.RegisterType<OrderRepository>() | ||||||
|                 .As<IOrderRepository<Order>>() |                 .As<IOrderRepository<Order>>() | ||||||
|                 .InstancePerLifetimeScope(); |                 .InstancePerLifetimeScope(); | ||||||
|  | 
 | ||||||
|  |             builder.RegisterType<RequestManager>() | ||||||
|  |                .As<IRequestManager>() | ||||||
|  |                .InstancePerLifetimeScope(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										245
									
								
								src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170303085729_RequestsTable.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,245 @@ | |||||||
|  | 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("20170303085729_RequestsTable")] | ||||||
|  |     partial class RequestsTable | ||||||
|  |     { | ||||||
|  |         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("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", "Buyer") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("BuyerId") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade); | ||||||
|  | 
 | ||||||
|  |                     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,33 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | 
 | ||||||
|  | namespace Ordering.API.Migrations | ||||||
|  | { | ||||||
|  |     public partial class RequestsTable : Migration | ||||||
|  |     { | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "requests", | ||||||
|  |                 schema: "ordering", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     Id = table.Column<Guid>(nullable: false), | ||||||
|  |                     Name = table.Column<string>(nullable: false), | ||||||
|  |                     Time = table.Column<DateTime>(nullable: false) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("PK_requests", x => x.Id); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "requests", | ||||||
|  |                 schema: "ordering"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -183,6 +183,21 @@ namespace Ordering.API.Migrations | |||||||
|                     b.ToTable("orderstatus","ordering"); |                     b.ToTable("orderstatus","ordering"); | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|  |             modelBuilder.Entity("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 => |             modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.PaymentMethod", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer") |                     b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer") | ||||||
|  | |||||||
| @ -51,10 +51,11 @@ | |||||||
|     <PackageReference Include="System.Reflection" Version="4.3.0" /> |     <PackageReference Include="System.Reflection" Version="4.3.0" /> | ||||||
|     <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.0.1-rc3" /> |     <PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.0.1-rc3" /> | ||||||
|     <PackageReference Include="Dapper" Version="1.50.2" /> |     <PackageReference Include="Dapper" Version="1.50.2" /> | ||||||
|  |     <PackageReference Include="System.ValueTuple" Version="4.3.0" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.1.0-preview4-final" /> |     <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
| 
 | 
 | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ | |||||||
| 
 | 
 | ||||||
|             if (env.IsDevelopment()) |             if (env.IsDevelopment()) | ||||||
|             { |             { | ||||||
|                 builder.AddUserSecrets(); |                 builder.AddUserSecrets(typeof(Startup).GetTypeInfo().Assembly); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             builder.AddEnvironmentVariables(); |             builder.AddEnvironmentVariables(); | ||||||
| @ -82,7 +82,7 @@ | |||||||
| 
 | 
 | ||||||
|             // Add application services. |             // Add application services. | ||||||
|             services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); |             services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); | ||||||
|             services.AddTransient<IIdentityService,IdentityService>(); |             services.AddTransient<IIdentityService, IdentityService>(); | ||||||
| 
 | 
 | ||||||
|             services.AddOptions(); |             services.AddOptions(); | ||||||
| 
 | 
 | ||||||
| @ -92,7 +92,7 @@ | |||||||
|             container.Populate(services); |             container.Populate(services); | ||||||
| 
 | 
 | ||||||
|             container.RegisterModule(new MediatorModule()); |             container.RegisterModule(new MediatorModule()); | ||||||
|             container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"] )); |             container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"])); | ||||||
| 
 | 
 | ||||||
|             return new AutofacServiceProvider(container.Build()); |             return new AutofacServiceProvider(container.Build()); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O | |||||||
|         public OrderStatus OrderStatus { get; private set; } |         public OrderStatus OrderStatus { get; private set; } | ||||||
|         private int _orderStatusId; |         private int _orderStatusId; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         // DDD Patterns comment |         // DDD Patterns comment | ||||||
|         // Using a private collection field, better for DDD Aggregate's encapsulation |         // Using a private collection field, better for DDD Aggregate's encapsulation | ||||||
|         // so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection, |         // so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection, | ||||||
| @ -46,7 +47,6 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O | |||||||
|             _paymentMethodId = paymentMethodId; |             _paymentMethodId = paymentMethodId; | ||||||
|             _orderStatusId = OrderStatus.InProcess.Id; |             _orderStatusId = OrderStatus.InProcess.Id; | ||||||
|             _orderDate = DateTime.UtcNow; |             _orderDate = DateTime.UtcNow; | ||||||
| 
 |  | ||||||
|             Address = address; |             Address = address; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -0,0 +1,13 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Text; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure | ||||||
|  | { | ||||||
|  |     public class ClientRequest | ||||||
|  |     { | ||||||
|  |         public Guid Id { get; set; } | ||||||
|  |         public string Name { get; set; } | ||||||
|  |         public DateTime Time { get; set; } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -30,6 +30,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure | |||||||
| 
 | 
 | ||||||
|         protected override void OnModelCreating(ModelBuilder modelBuilder) |         protected override void OnModelCreating(ModelBuilder modelBuilder) | ||||||
|         { |         { | ||||||
|  | 
 | ||||||
|  |             modelBuilder.Entity<ClientRequest>(ConfigureRequests); | ||||||
|             modelBuilder.Entity<Address>(ConfigureAddress); |             modelBuilder.Entity<Address>(ConfigureAddress); | ||||||
|             modelBuilder.Entity<PaymentMethod>(ConfigurePayment); |             modelBuilder.Entity<PaymentMethod>(ConfigurePayment); | ||||||
|             modelBuilder.Entity<Order>(ConfigureOrder); |             modelBuilder.Entity<Order>(ConfigureOrder); | ||||||
| @ -39,6 +41,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure | |||||||
|             modelBuilder.Entity<Buyer>(ConfigureBuyer); |             modelBuilder.Entity<Buyer>(ConfigureBuyer); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         private void ConfigureRequests(EntityTypeBuilder<ClientRequest> requestConfiguration) | ||||||
|  |         { | ||||||
|  |             requestConfiguration.ToTable("requests", DEFAULT_SCHEMA); | ||||||
|  |             requestConfiguration.HasKey(cr => cr.Id); | ||||||
|  |             requestConfiguration.Property(cr => cr.Name).IsRequired(); | ||||||
|  |             requestConfiguration.Property(cr => cr.Time).IsRequired(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         void ConfigureAddress(EntityTypeBuilder<Address> addressConfiguration) |         void ConfigureAddress(EntityTypeBuilder<Address> addressConfiguration) | ||||||
|         { |         { | ||||||
|             addressConfiguration.ToTable("address", DEFAULT_SCHEMA); |             addressConfiguration.ToTable("address", DEFAULT_SCHEMA); | ||||||
|  | |||||||
| @ -0,0 +1,13 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories | ||||||
|  | { | ||||||
|  |     public interface IRequestManager | ||||||
|  |     { | ||||||
|  |         Task<bool> ExistAsync(Guid id); | ||||||
|  |         Task CreateRequestForCommandAsync<T>(Guid id); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,42 @@ | |||||||
|  | using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | 
 | ||||||
|  | namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories | ||||||
|  | { | ||||||
|  |     public class RequestManager : IRequestManager | ||||||
|  |     { | ||||||
|  |         private readonly OrderingContext _context; | ||||||
|  |         public RequestManager(OrderingContext ctx) | ||||||
|  |         { | ||||||
|  |             _context = ctx; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         public async Task<bool> ExistAsync(Guid id) | ||||||
|  |         { | ||||||
|  |             var request = await _context.FindAsync<ClientRequest>(id); | ||||||
|  |             return request != null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public async Task CreateRequestForCommandAsync<T>(Guid id) | ||||||
|  |         { | ||||||
|  | 
 | ||||||
|  |             var exists = await ExistAsync(id); | ||||||
|  |             var request = exists ?  | ||||||
|  |                 throw new Exception($"Request with {id} already exists") :  | ||||||
|  |                 new ClientRequest() | ||||||
|  |                 { | ||||||
|  |                     Id = id, | ||||||
|  |                     Name = typeof(T).Name, | ||||||
|  |                     Time = DateTime.UtcNow | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |             _context.Add(request); | ||||||
|  |             await _context.SaveChangesAsync(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -55,6 +55,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Models | |||||||
| 
 | 
 | ||||||
|         public List<OrderItem> OrderItems { get; } |         public List<OrderItem> OrderItems { get; } | ||||||
| 
 | 
 | ||||||
|  |         [Required] | ||||||
|  |         public Guid RequestId { get; set; } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         public void CardExpirationShortFormat() |         public void CardExpirationShortFormat() | ||||||
|         { |         { | ||||||
|             CardExpirationShort = CardExpiration.ToString("MM/yy"); |             CardExpirationShort = CardExpiration.ToString("MM/yy"); | ||||||
|  | |||||||
| @ -79,7 +79,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services | |||||||
| 
 | 
 | ||||||
|             _apiClient = new HttpClient(); |             _apiClient = new HttpClient(); | ||||||
|             _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); |             _apiClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); | ||||||
| 
 |             _apiClient.DefaultRequestHeaders.Add("x-requestid", order.RequestId.ToString()); | ||||||
|             var ordersUrl = $"{_remoteServiceBaseUrl}/new"; |             var ordersUrl = $"{_remoteServiceBaseUrl}/new"; | ||||||
|             order.CardTypeId = 1; |             order.CardTypeId = 1; | ||||||
|             order.CardExpirationApiFormat(); |             order.CardExpirationApiFormat(); | ||||||
|  | |||||||
| @ -89,6 +89,7 @@ | |||||||
|             </div> |             </div> | ||||||
|         </section> |         </section> | ||||||
|         <input asp-for="ZipCode" type="hidden" /> |         <input asp-for="ZipCode" type="hidden" /> | ||||||
|  |         <input asp-for="RequestId" type="hidden" value="@Guid.NewGuid().ToString()"/> | ||||||
|     </form> |     </form> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								src/Web/WebSPA/Client/guid.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/Web/WebSPA/Client/guid.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | export class Guid { | ||||||
|  |     static newGuid() { | ||||||
|  |         return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | ||||||
|  |             var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | ||||||
|  |             return v.toString(16); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -44,7 +44,7 @@ export class OrdersService { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     postOrder(item): Observable<boolean> { |     postOrder(item): Observable<boolean> { | ||||||
|         return this.service.post(this.ordersUrl + '/api/v1/orders/new', item).map((response: Response) => { |         return this.service.postWithId(this.ordersUrl + '/api/v1/orders/new', item).map((response: Response) => { | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import 'rxjs/add/operator/map'; | |||||||
| import 'rxjs/add/operator/catch'; | import 'rxjs/add/operator/catch'; | ||||||
| 
 | 
 | ||||||
| import { SecurityService } from './security.service'; | import { SecurityService } from './security.service'; | ||||||
|  | import { Guid } from '../../../guid'; | ||||||
| 
 | 
 | ||||||
| @Injectable() | @Injectable() | ||||||
| export class DataService { | export class DataService { | ||||||
| @ -28,13 +29,25 @@ export class DataService { | |||||||
|         }).catch(this.handleError); |         }).catch(this.handleError); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     postWithId(url: string, data: any, params?: any): Observable<Response> { | ||||||
|  |         return this.doPost(url, data, true, params); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     post(url: string, data: any, params?: any): Observable<Response> { |     post(url: string, data: any, params?: any): Observable<Response> { | ||||||
|  |         return this.doPost(url, data, false, params); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private doPost(url: string, data: any, needId: boolean, params?: any): Observable<Response> { | ||||||
|         let options: RequestOptionsArgs = {}; |         let options: RequestOptionsArgs = {}; | ||||||
| 
 | 
 | ||||||
|         if (this.securityService) { |  | ||||||
|         options.headers = new Headers(); |         options.headers = new Headers(); | ||||||
|  |         if (this.securityService) { | ||||||
|             options.headers.append('Authorization', 'Bearer ' + this.securityService.GetToken()); |             options.headers.append('Authorization', 'Bearer ' + this.securityService.GetToken()); | ||||||
|         } |         } | ||||||
|  |         if (needId) { | ||||||
|  |             let guid = Guid.newGuid(); | ||||||
|  |             options.headers.append('x-requestid', guid); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return this.http.post(url, data, options).map( |         return this.http.post(url, data, options).map( | ||||||
|             (res: Response) => { |             (res: Response) => { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user