Browse Source

Added domain events in Ordering Api

pull/126/head
Ramón Tomás 8 years ago
parent
commit
c8f0776f1f
25 changed files with 685 additions and 137 deletions
  1. +6
    -0
      eShopOnContainers-ServicesAndWebApps.sln
  2. +11
    -2
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs
  3. +7
    -35
      src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs
  4. +53
    -0
      src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs
  5. +33
    -0
      src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs
  6. +4
    -5
      src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs
  7. +9
    -2
      src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs
  8. +244
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.Designer.cs
  9. +75
    -0
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.cs
  10. +4
    -5
      src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs
  11. +8
    -6
      src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs
  12. +5
    -0
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs
  13. +29
    -5
      src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
  14. +35
    -0
      src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs
  15. +23
    -0
      src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs
  16. +5
    -0
      src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj
  17. +17
    -1
      src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs
  18. +2
    -1
      src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs
  19. +25
    -0
      src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs
  20. +31
    -5
      src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs
  21. +15
    -3
      src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs
  22. +6
    -2
      test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs
  23. +3
    -1
      test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs
  24. +33
    -63
      test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs
  25. +2
    -1
      test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs

+ 6
- 0
eShopOnContainers-ServicesAndWebApps.sln View File

@ -41,6 +41,12 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{621E7211-58D0-45FD-9600-1CB490BD930E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{621E7211-58D0-45FD-9600-1CB490BD930E}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTest", "test\Services\UnitTest\UnitTest.csproj", "{7796F5D8-31FC-45A4-B673-19DE5BA194CF}"
ProjectSection(ProjectDependencies) = postProject
{A579E108-5445-403D-A407-339AC4D1611B} = {A579E108-5445-403D-A407-339AC4D1611B}
{621E7211-58D0-45FD-9600-1CB490BD930E} = {621E7211-58D0-45FD-9600-1CB490BD930E}
{FEA0C318-FFED-4D39-8781-265718CA43DD} = {FEA0C318-FFED-4D39-8781-265718CA43DD}
{F16E3C6A-1C94-4EAB-BE91-099618060B68} = {F16E3C6A-1C94-4EAB-BE91-099618060B68}
EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.csproj", "{A579E108-5445-403D-A407-339AC4D1611B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.csproj", "{A579E108-5445-403D-A407-339AC4D1611B}"
EndProject EndProject


+ 11
- 2
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommand.cs View File

@ -2,6 +2,7 @@
using MediatR; using MediatR;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using System.Collections;
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{ {
@ -51,6 +52,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
[DataMember] [DataMember]
public int CardTypeId { get; private set; } public int CardTypeId { get; private set; }
[DataMember]
public int PaymentId { get; private set; }
[DataMember]
public int BuyerId { get; private set; }
[DataMember] [DataMember]
public IEnumerable<OrderItemDTO> OrderItems => _orderItems; public IEnumerable<OrderItemDTO> OrderItems => _orderItems;
@ -66,7 +73,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
public CreateOrderCommand(string city, string street, string state, string country, string zipcode, public CreateOrderCommand(string city, string street, string state, string country, string zipcode,
string cardNumber, string cardHolderName, DateTime cardExpiration, string cardNumber, string cardHolderName, DateTime cardExpiration,
string cardSecurityNumber, int cardTypeId) : this()
string cardSecurityNumber, int cardTypeId, int paymentId, int buyerId) : this()
{ {
City = city; City = city;
Street = street; Street = street;
@ -75,12 +82,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
ZipCode = zipcode; ZipCode = zipcode;
CardNumber = cardNumber; CardNumber = cardNumber;
CardHolderName = cardHolderName; CardHolderName = cardHolderName;
CardExpiration = cardExpiration;
CardSecurityNumber = cardSecurityNumber; CardSecurityNumber = cardSecurityNumber;
CardTypeId = cardTypeId; CardTypeId = cardTypeId;
CardExpiration = cardExpiration; CardExpiration = cardExpiration;
PaymentId = paymentId;
BuyerId = buyerId;
} }
public class OrderItemDTO public class OrderItemDTO
{ {
public int ProductId { get; set; } public int ProductId { get; set; }


+ 7
- 35
src/Services/Ordering/Ordering.API/Application/Commands/CreateOrderCommandHandler.cs View File

@ -1,6 +1,5 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
{ {
using Domain.AggregatesModel.BuyerAggregate;
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;
@ -24,16 +23,16 @@
public class CreateOrderCommandHandler public class CreateOrderCommandHandler
: IAsyncRequestHandler<CreateOrderCommand, bool> : IAsyncRequestHandler<CreateOrderCommand, bool>
{ {
private readonly IBuyerRepository<Buyer> _buyerRepository;
private readonly IOrderRepository<Order> _orderRepository; private readonly IOrderRepository<Order> _orderRepository;
private readonly IIdentityService _identityService; private readonly IIdentityService _identityService;
private readonly IMediator _mediator;
// Using DI to inject infrastructure persistence Repositories // Using DI to inject infrastructure persistence Repositories
public CreateOrderCommandHandler(IBuyerRepository<Buyer> buyerRepository, IOrderRepository<Order> orderRepository, IIdentityService identityService)
public CreateOrderCommandHandler(IMediator mediator, IOrderRepository<Order> orderRepository, IIdentityService identityService)
{ {
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository)); _orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService)); _identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
} }
public async Task<bool> Handle(CreateOrderCommand message) public async Task<bool> Handle(CreateOrderCommand message)
@ -42,45 +41,18 @@
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root // DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
// methods and constructor so validations, invariants and business logic // methods and constructor so validations, invariants and business logic
// make sure that consistency is preserved across the whole aggregate // make sure that consistency is preserved across the whole aggregate
var cardTypeId = message.CardTypeId != 0 ? message.CardTypeId : 1;
var buyerGuid = _identityService.GetUserIdentity();
var buyer = await _buyerRepository.FindAsync(buyerGuid);
if (buyer == null)
{
buyer = new Buyer(buyerGuid);
}
var payment = buyer.AddPaymentMethod(cardTypeId,
$"Payment Method on {DateTime.UtcNow}",
message.CardNumber,
message.CardSecurityNumber,
message.CardHolderName,
message.CardExpiration);
_buyerRepository.Add(buyer);
await _buyerRepository.UnitOfWork
.SaveChangesAsync();
// Create the Order AggregateRoot
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
// methods and constructor so validations, invariants and business logic
// make sure that consistency is preserved across the whole aggregate
var order = new Order(buyer.Id, payment.Id, new Address(message.Street, message.City, message.State, message.Country, message.ZipCode));
var address = new Address(message.Street, message.City, message.State, message.Country, message.ZipCode);
var order = new Order(address , message.CardTypeId, message.CardNumber, message.CardSecurityNumber, message.CardHolderName, message.CardExpiration);
foreach (var item in message.OrderItems) foreach (var item in message.OrderItems)
{ {
order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units); order.AddOrderItem(item.ProductId, item.ProductName, item.UnitPrice, item.Discount, item.PictureUrl, item.Units);
} }
_orderRepository.Add(order);
_orderRepository.Add(order);
var result = await _orderRepository.UnitOfWork var result = await _orderRepository.UnitOfWork
.SaveChangesAsync();
.SaveEntitiesAsync();
return result > 0; return result > 0;


+ 53
- 0
src/Services/Ordering/Ordering.API/Application/EventHandlers/OrderCreatedEventHandler.cs View File

@ -0,0 +1,53 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using Microsoft.Extensions.Logging;
using Ordering.Domain.Events;
using System;
using System.Threading.Tasks;
namespace Ordering.API.Application.EventHandlers
{
public class OrderCreatedEventHandler : IAsyncNotificationHandler<OrderCreated>
{
private readonly ILoggerFactory _logger;
private readonly IBuyerRepository<Buyer> _buyerRepository;
private readonly IIdentityService _identityService;
public OrderCreatedEventHandler(ILoggerFactory logger, IBuyerRepository<Buyer> buyerRepository, IIdentityService identityService)
{
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
_identityService = identityService ?? throw new ArgumentNullException(nameof(identityService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(OrderCreated orderNotification)
{
var cardTypeId = orderNotification.CardTypeId != 0 ? orderNotification.CardTypeId : 1;
var buyerGuid = _identityService.GetUserIdentity();
var buyer = await _buyerRepository.FindAsync(buyerGuid);
if (buyer == null)
{
buyer = new Buyer(buyerGuid);
}
var payment = buyer.AddPaymentMethod(cardTypeId,
$"Payment Method on {DateTime.UtcNow}",
orderNotification.CardNumber,
orderNotification.CardSecurityNumber,
orderNotification.CardHolderName,
orderNotification.CardExpiration,
orderNotification.Order.Id);
_buyerRepository.Add(buyer);
await _buyerRepository.UnitOfWork
.SaveEntitiesAsync();
_logger.CreateLogger(nameof(OrderCreatedEventHandler)).LogTrace($"A new payment method has been successfully added for orderId: {orderNotification.Order.Id}.");
}
}
}

+ 33
- 0
src/Services/Ordering/Ordering.API/Application/EventHandlers/PaymentMethodCheckedEventHandler.cs View File

@ -0,0 +1,33 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.Extensions.Logging;
using Ordering.Domain.Events;
using System;
using System.Threading.Tasks;
namespace Ordering.API.Application.EventHandlers
{
public class PaymentMethodCheckedEventHandler : IAsyncNotificationHandler<PaymentMethodChecked>
{
private readonly IOrderRepository<Order> _orderRepository;
private readonly ILoggerFactory _logger;
public PaymentMethodCheckedEventHandler(IOrderRepository<Order> orderRepository, ILoggerFactory logger)
{
_orderRepository = orderRepository ?? throw new ArgumentNullException(nameof(orderRepository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(PaymentMethodChecked paymentMethodNotification)
{
var orderToUpdate = await _orderRepository.GetAsync(paymentMethodNotification.OrderId);
orderToUpdate.SetBuyerId(paymentMethodNotification.Buyer.Id);
orderToUpdate.SetPaymentId(paymentMethodNotification.Payment.Id);
await _orderRepository.UnitOfWork
.SaveEntitiesAsync();
_logger.CreateLogger(nameof(PaymentMethodCheckedEventHandler))
.LogTrace($"Order with Id: {paymentMethodNotification.OrderId} has been successfully updated with a new payment method id: { paymentMethodNotification.Payment.Id }");
}
}
}

+ 4
- 5
src/Services/Ordering/Ordering.API/Controllers/OrdersController.cs View File

@ -1,5 +1,4 @@
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
@ -28,19 +27,19 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("new")] [Route("new")]
[HttpPost] [HttpPost]
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand, [FromHeader(Name = "x-requestid")] string requestId)
public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand command, [FromHeader(Name = "x-requestid")] string requestId)
{ {
bool result = false; bool result = false;
if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty) if (Guid.TryParse(requestId, out Guid guid) && guid != Guid.Empty)
{ {
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(createOrderCommand, guid);
var requestCreateOrder = new IdentifiedCommand<CreateOrderCommand, bool>(command, guid);
result = await _mediator.SendAsync(requestCreateOrder); result = await _mediator.SendAsync(requestCreateOrder);
} }
else else
{ {
// If no x-requestid header is found we process the order anyway. This is just temporary to not break existing clients // 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. // that aren't still updated. When all clients were updated this could be removed.
result = await _mediator.SendAsync(createOrderCommand);
result = await _mediator.SendAsync(command);
} }
if (result) if (result)
@ -82,7 +81,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
var cardTypes = await _orderQueries.GetCardTypes(); var cardTypes = await _orderQueries.GetCardTypes();
return Ok(cardTypes); return Ok(cardTypes);
}
}
} }
} }


+ 9
- 2
src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs View File

@ -3,6 +3,8 @@ using Autofac.Core;
using MediatR; using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators; using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators;
using Ordering.API.Application.EventHandlers;
using Ordering.Domain.Events;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -14,12 +16,17 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly) builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly)
.AsImplementedInterfaces();
.AsImplementedInterfaces();
builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly) builder.RegisterAssemblyTypes(typeof(CreateOrderCommand).GetTypeInfo().Assembly)
.As(o => o.GetInterfaces() .As(o => o.GetInterfaces()
.Where(i => i.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>))) .Where(i => i.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>)))
.Select(i => new KeyedService("IAsyncRequestHandler", i))); .Select(i => new KeyedService("IAsyncRequestHandler", i)));
builder
.RegisterAssemblyTypes(typeof(OrderCreatedEventHandler).GetTypeInfo().Assembly)
.Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>)))
.AsImplementedInterfaces();
builder.Register<SingleInstanceFactory>(context => builder.Register<SingleInstanceFactory>(context =>
{ {
@ -37,7 +44,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
builder.RegisterGenericDecorator(typeof(LogDecorator<,>), builder.RegisterGenericDecorator(typeof(LogDecorator<,>),
typeof(IAsyncRequestHandler<,>), typeof(IAsyncRequestHandler<,>),
"IAsyncRequestHandler");
"IAsyncRequestHandler");
} }
} }
} }

+ 244
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.Designer.cs View File

@ -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("20170313100034_Domain_events")]
partial class Domain_events
{
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", "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);
});
}
}
}

+ 75
- 0
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/20170313100034_Domain_events.cs View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ordering.API.Migrations
{
public partial class Domain_events : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_orders_buyers_BuyerId",
schema: "ordering",
table: "orders");
migrationBuilder.AlterColumn<int>(
name: "PaymentMethodId",
schema: "ordering",
table: "orders",
nullable: true,
oldClrType: typeof(int));
migrationBuilder.AlterColumn<int>(
name: "BuyerId",
schema: "ordering",
table: "orders",
nullable: true,
oldClrType: typeof(int));
migrationBuilder.AddForeignKey(
name: "FK_orders_buyers_BuyerId",
schema: "ordering",
table: "orders",
column: "BuyerId",
principalSchema: "ordering",
principalTable: "buyers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_orders_buyers_BuyerId",
schema: "ordering",
table: "orders");
migrationBuilder.AlterColumn<int>(
name: "PaymentMethodId",
schema: "ordering",
table: "orders",
nullable: false,
oldClrType: typeof(int),
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "BuyerId",
schema: "ordering",
table: "orders",
nullable: false,
oldClrType: typeof(int),
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_orders_buyers_BuyerId",
schema: "ordering",
table: "orders",
column: "BuyerId",
principalSchema: "ordering",
principalTable: "buyers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

+ 4
- 5
src/Services/Ordering/Ordering.API/Infrastructure/Migrations/OrderingContextModelSnapshot.cs View File

@ -119,13 +119,13 @@ namespace Ordering.API.Migrations
b.Property<int?>("AddressId"); b.Property<int?>("AddressId");
b.Property<int>("BuyerId");
b.Property<int?>("BuyerId");
b.Property<DateTime>("OrderDate"); b.Property<DateTime>("OrderDate");
b.Property<int>("OrderStatusId"); b.Property<int>("OrderStatusId");
b.Property<int>("PaymentMethodId");
b.Property<int?>("PaymentMethodId");
b.HasKey("Id"); b.HasKey("Id");
@ -183,7 +183,7 @@ namespace Ordering.API.Migrations
b.ToTable("orderstatus","ordering"); b.ToTable("orderstatus","ordering");
}); });
modelBuilder.Entity("Ordering.Infrastructure.ClientRequest", b =>
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.ClientRequest", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
@ -219,8 +219,7 @@ namespace Ordering.API.Migrations
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer") b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
.WithMany() .WithMany()
.HasForeignKey("BuyerId")
.OnDelete(DeleteBehavior.Cascade);
.HasForeignKey("BuyerId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus") b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.OrderStatus", "OrderStatus")
.WithMany() .WithMany()


+ 8
- 6
src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs View File

@ -1,6 +1,6 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Domain.Events;
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -11,7 +11,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
{ {
public string IdentityGuid { get; private set; } public string IdentityGuid { get; private set; }
private List<PaymentMethod> _paymentMethods;
private List<PaymentMethod> _paymentMethods;
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly(); public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
@ -22,16 +22,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
public Buyer(string identity) : this() public Buyer(string identity) : this()
{ {
IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity)); IdentityGuid = !string.IsNullOrWhiteSpace(identity) ? identity : throw new ArgumentNullException(nameof(identity));
} }
public PaymentMethod AddPaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration)
public PaymentMethod AddPaymentMethod(
int cardTypeId, string alias, string cardNumber,
string securityNumber, string cardHolderName, DateTime expiration, int orderId)
{ {
var existingPayment = _paymentMethods.Where(p => p.IsEqualTo(cardTypeId, cardNumber, expiration)) var existingPayment = _paymentMethods.Where(p => p.IsEqualTo(cardTypeId, cardNumber, expiration))
.SingleOrDefault(); .SingleOrDefault();
if (existingPayment != null) if (existingPayment != null)
{ {
AddEvent(new PaymentMethodChecked(this, existingPayment, orderId));
return existingPayment; return existingPayment;
} }
else else
@ -39,9 +41,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration); var payment = new PaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
_paymentMethods.Add(payment); _paymentMethods.Add(payment);
AddEvent(new PaymentMethodChecked(this, payment, orderId));
return payment; return payment;
} }
}
}
} }
} }

+ 5
- 0
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/IOrderRepository.cs View File

@ -1,4 +1,5 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
{ {
@ -8,5 +9,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public interface IOrderRepository<T> : IRepository<T> where T : IAggregateRoot public interface IOrderRepository<T> : IRepository<T> where T : IAggregateRoot
{ {
Order Add(Order order); Order Add(Order order);
Task<Order> GetAsync(int orderId);
void Update(Order order);
} }
} }

+ 29
- 5
src/Services/Ordering/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs View File

@ -1,5 +1,7 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Domain.Events;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -17,7 +19,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public Address Address { get; private set; } public Address Address { get; private set; }
public Buyer Buyer { get; private set; } public Buyer Buyer { get; private set; }
private int _buyerId;
private int? _buyerId;
public OrderStatus OrderStatus { get; private set; } public OrderStatus OrderStatus { get; private set; }
private int _orderStatusId; private int _orderStatusId;
@ -36,11 +38,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx //https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
public PaymentMethod PaymentMethod { get; private set; } public PaymentMethod PaymentMethod { get; private set; }
private int _paymentMethodId;
private int? _paymentMethodId;
protected Order() { } protected Order() { }
public Order(int buyerId, int paymentMethodId, Address address)
public Order(Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null)
{ {
_orderItems = new List<OrderItem>(); _orderItems = new List<OrderItem>();
_buyerId = buyerId; _buyerId = buyerId;
@ -48,6 +51,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
_orderStatusId = OrderStatus.InProcess.Id; _orderStatusId = OrderStatus.InProcess.Id;
_orderDate = DateTime.UtcNow; _orderDate = DateTime.UtcNow;
Address = address; Address = address;
AddCreatedOrderEvent(cardTypeId, cardNumber,
cardSecurityNumber, cardHolderName, cardExpiration);
} }
// DDD Patterns comment // DDD Patterns comment
@ -74,9 +79,28 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
//add validated new order item //add validated new order item
var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units); var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
_orderItems.Add(orderItem); _orderItems.Add(orderItem);
} }
} }
public void SetPaymentId(int id)
{
_paymentMethodId = id;
}
public void SetBuyerId(int id)
{
_buyerId = id;
}
private void AddCreatedOrderEvent(int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName, DateTime cardExpiration)
{
var @orderCreatedEvent = new OrderCreated(
this, cardTypeId, cardNumber, cardSecurityNumber,
cardHolderName, cardExpiration);
AddEvent(@orderCreatedEvent);
}
} }
} }

+ 35
- 0
src/Services/Ordering/Ordering.Domain/Events/OrderCreated.cs View File

@ -0,0 +1,35 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ordering.Domain.Events
{
/// <summary>
/// Event used when an order is created
/// </summary>
public class OrderCreated
: IAsyncNotification
{
public int CardTypeId { get; private set; }
public string CardNumber { get; private set; }
public string CardSecurityNumber { get; private set; }
public string CardHolderName { get; private set; }
public DateTime CardExpiration { get; private set; }
public Order Order { get; private set; }
public OrderCreated(Order order,
int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName,
DateTime cardExpiration)
{
Order = order;
CardTypeId = cardTypeId;
CardNumber = cardNumber;
CardSecurityNumber = cardSecurityNumber;
CardHolderName = cardHolderName;
CardExpiration = cardExpiration;
}
}
}

+ 23
- 0
src/Services/Ordering/Ordering.Domain/Events/PaymentMethodChecked.cs View File

@ -0,0 +1,23 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using System;
using System.Collections.Generic;
using System.Text;
namespace Ordering.Domain.Events
{
public class PaymentMethodChecked
: IAsyncNotification
{
public Buyer Buyer { get; private set; }
public PaymentMethod Payment { get; private set; }
public int OrderId { get; private set; }
public PaymentMethodChecked(Buyer buyer, PaymentMethod payment, int orderId)
{
Buyer = buyer;
Payment = payment;
OrderId = orderId;
}
}
}

+ 5
- 0
src/Services/Ordering/Ordering.Domain/Ordering.Domain.csproj View File

@ -11,4 +11,9 @@
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="2.1.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="1.1.0" />
</ItemGroup>
</Project> </Project>

+ 17
- 1
src/Services/Ordering/Ordering.Domain/SeedWork/Entity.cs View File

@ -1,7 +1,8 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
{ {
using System; using System;
using MediatR;
using System.Collections.Generic;
public abstract class Entity public abstract class Entity
{ {
@ -9,6 +10,8 @@
int? _requestedHashCode; int? _requestedHashCode;
int _Id; int _Id;
private List<IAsyncNotification> _events;
public virtual int Id public virtual int Id
{ {
get get
@ -21,6 +24,19 @@
} }
} }
public List<IAsyncNotification> Events => _events;
public void AddEvent(IAsyncNotification eventItem)
{
_events = _events ?? new List<IAsyncNotification>();
_events.Add(eventItem);
}
public void RemoveEvent(IAsyncNotification eventItem)
{
if (_events is null) return;
_events.Remove(eventItem);
}
public bool IsTransient() public bool IsTransient()
{ {
return this.Id == default(Int32); return this.Id == default(Int32);


+ 2
- 1
src/Services/Ordering/Ordering.Domain/SeedWork/IUnitOfWork.cs View File

@ -5,7 +5,8 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
{ {
public interface IUnitOfWork : IDisposable public interface IUnitOfWork : IDisposable
{
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
Task<int> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken));
} }
} }

+ 25
- 0
src/Services/Ordering/Ordering.Infrastructure/MediatorExtension.cs View File

@ -0,0 +1,25 @@
using MediatR;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
using System.Linq;
using System.Threading.Tasks;
namespace Ordering.Infrastructure
{
public static class MediatorExtension
{
public static async Task RaiseDomainEventsAsync(this IMediator mediator, OrderingContext ctx)
{
var domainEntities = ctx.ChangeTracker.Entries<Entity>().Where(x => x.Entity.Events != null && x.Entity.Events.Any());
var domainEvents = domainEntities.SelectMany(x => x.Entity.Events).ToList();
domainEntities.ToList().ForEach(entity => entity.Entity.Events.Clear());
var tasks = domainEvents
.Select(async (domainEvent) => {
await mediator.PublishAsync(domainEvent);
});
await Task.WhenAll(tasks);
}
}
}

+ 31
- 5
src/Services/Ordering/Ordering.Infrastructure/OrderingContext.cs View File

@ -1,10 +1,14 @@
using Microsoft.EntityFrameworkCore;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate; using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using Ordering.Infrastructure;
using System; using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
{ {
@ -26,7 +30,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
public DbSet<OrderStatus> OrderStatus { get; set; } public DbSet<OrderStatus> OrderStatus { get; set; }
public OrderingContext(DbContextOptions options) : base(options) { }
private readonly IMediator _mediator;
public OrderingContext(DbContextOptions options, IMediator mediator) : base(options)
{
_mediator = mediator;
}
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
@ -38,7 +47,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
modelBuilder.Entity<OrderItem>(ConfigureOrderItems); modelBuilder.Entity<OrderItem>(ConfigureOrderItems);
modelBuilder.Entity<CardType>(ConfigureCardTypes); modelBuilder.Entity<CardType>(ConfigureCardTypes);
modelBuilder.Entity<OrderStatus>(ConfigureOrderStatus); modelBuilder.Entity<OrderStatus>(ConfigureOrderStatus);
modelBuilder.Entity<Buyer>(ConfigureBuyer);
modelBuilder.Entity<Buyer>(ConfigureBuyer);
} }
private void ConfigureRequests(EntityTypeBuilder<ClientRequest> requestConfiguration) private void ConfigureRequests(EntityTypeBuilder<ClientRequest> requestConfiguration)
@ -65,6 +74,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
buyerConfiguration.HasKey(b => b.Id); buyerConfiguration.HasKey(b => b.Id);
buyerConfiguration.Ignore(b => b.Events);
buyerConfiguration.Property(b => b.Id) buyerConfiguration.Property(b => b.Id)
.ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA); .ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA);
@ -91,6 +102,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
paymentConfiguration.HasKey(b => b.Id); paymentConfiguration.HasKey(b => b.Id);
paymentConfiguration.Ignore(b => b.Events);
paymentConfiguration.Property(b => b.Id) paymentConfiguration.Property(b => b.Id)
.ForSqlServerUseSequenceHiLo("paymentseq", DEFAULT_SCHEMA); .ForSqlServerUseSequenceHiLo("paymentseq", DEFAULT_SCHEMA);
@ -126,13 +139,15 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
orderConfiguration.HasKey(o => o.Id); orderConfiguration.HasKey(o => o.Id);
orderConfiguration.Ignore(b => b.Events);
orderConfiguration.Property(o => o.Id) orderConfiguration.Property(o => o.Id)
.ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA); .ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA);
orderConfiguration.Property<DateTime>("OrderDate").IsRequired(); orderConfiguration.Property<DateTime>("OrderDate").IsRequired();
orderConfiguration.Property<int>("BuyerId").IsRequired();
orderConfiguration.Property<int?>("BuyerId").IsRequired(false);
orderConfiguration.Property<int>("OrderStatusId").IsRequired(); orderConfiguration.Property<int>("OrderStatusId").IsRequired();
orderConfiguration.Property<int>("PaymentMethodId").IsRequired();
orderConfiguration.Property<int?>("PaymentMethodId").IsRequired(false);
var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems)); var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));
// DDD Patterns comment: // DDD Patterns comment:
@ -142,10 +157,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
orderConfiguration.HasOne(o => o.PaymentMethod) orderConfiguration.HasOne(o => o.PaymentMethod)
.WithMany() .WithMany()
.HasForeignKey("PaymentMethodId") .HasForeignKey("PaymentMethodId")
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict); .OnDelete(DeleteBehavior.Restrict);
orderConfiguration.HasOne(o => o.Buyer) orderConfiguration.HasOne(o => o.Buyer)
.WithMany() .WithMany()
.IsRequired(false)
.HasForeignKey("BuyerId"); .HasForeignKey("BuyerId");
orderConfiguration.HasOne(o => o.OrderStatus) orderConfiguration.HasOne(o => o.OrderStatus)
@ -159,6 +176,8 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
orderItemConfiguration.HasKey(o => o.Id); orderItemConfiguration.HasKey(o => o.Id);
orderItemConfiguration.Ignore(b => b.Events);
orderItemConfiguration.Property(o => o.Id) orderItemConfiguration.Property(o => o.Id)
.ForSqlServerUseSequenceHiLo("orderitemseq"); .ForSqlServerUseSequenceHiLo("orderitemseq");
@ -215,5 +234,12 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
.HasMaxLength(200) .HasMaxLength(200)
.IsRequired(); .IsRequired();
} }
public async Task<int> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var result = await base.SaveChangesAsync();
await _mediator.RaiseDomainEventsAsync(this);
return result;
}
} }
} }

+ 15
- 3
src/Services/Ordering/Ordering.Infrastructure/Repositories/OrderRepository.cs View File

@ -1,6 +1,8 @@
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate;
using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork; using Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork;
using System; using System;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{ {
@ -24,8 +26,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor
public Order Add(Order order) public Order Add(Order order)
{ {
return _context.Orders.Add(order)
.Entity;
return _context.Orders.Add(order).Entity;
}
public async Task<Order> GetAsync(int orderId)
{
return await _context.Orders.FindAsync(orderId);
}
public void Update(Order order)
{
_context.Entry(order).State = EntityState.Modified;
} }
} }
} }

+ 6
- 2
test/Services/FunctionalTests/Services/Ordering/OrderingScenarios.cs View File

@ -75,7 +75,9 @@
country: "USA", country: "USA",
state: "WA", state: "WA",
street: "One way", street: "One way",
zipcode: "zipcode"
zipcode: "zipcode",
paymentId: 1,
buyerId: 3
); );
order.AddOrderItem(new OrderItemDTO() order.AddOrderItem(new OrderItemDTO()
@ -101,7 +103,9 @@
country: "USA", country: "USA",
state: "WA", state: "WA",
street: "One way", street: "One way",
zipcode: "zipcode"
zipcode: "zipcode",
paymentId: 1,
buyerId: 3
); );
return JsonConvert.SerializeObject(order); return JsonConvert.SerializeObject(order);


+ 3
- 1
test/Services/UnitTest/Ordering/Application/IdentifierCommandHandlerTest.cs View File

@ -79,7 +79,9 @@ namespace UnitTest.Ordering.Application
cardExpiration: args != null && args.ContainsKey("cardExpiration") ? (DateTime)args["cardExpiration"] : DateTime.MinValue, cardExpiration: args != null && args.ContainsKey("cardExpiration") ? (DateTime)args["cardExpiration"] : DateTime.MinValue,
cardSecurityNumber: args != null && args.ContainsKey("cardSecurityNumber") ? (string)args["cardSecurityNumber"] : "123", cardSecurityNumber: args != null && args.ContainsKey("cardSecurityNumber") ? (string)args["cardSecurityNumber"] : "123",
cardHolderName: args != null && args.ContainsKey("cardHolderName") ? (string)args["cardHolderName"] : "XXX", cardHolderName: args != null && args.ContainsKey("cardHolderName") ? (string)args["cardHolderName"] : "XXX",
cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0);
cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0,
paymentId: args != null && args.ContainsKey("paymentId") ? (int)args["paymentId"] : 0,
buyerId: args != null && args.ContainsKey("buyerId") ? (int)args["buyerId"] : 0);
} }
} }
} }

+ 33
- 63
test/Services/UnitTest/Ordering/Application/NewOrderCommandHandlerTest.cs View File

@ -10,21 +10,22 @@ using System.Threading.Tasks;
namespace UnitTest.Ordering.Application namespace UnitTest.Ordering.Application
{ {
using MediatR;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Xunit; using Xunit;
public class NewOrderRequestHandlerTest public class NewOrderRequestHandlerTest
{ {
private readonly Mock<IBuyerRepository<Buyer>> _buyerRepositoryMock;
private readonly Mock<IOrderRepository<Order>> _orderRepositoryMock; private readonly Mock<IOrderRepository<Order>> _orderRepositoryMock;
private readonly Mock<IIdentityService> _identityServiceMock; private readonly Mock<IIdentityService> _identityServiceMock;
private readonly Mock<IMediator> _mediator;
public NewOrderRequestHandlerTest() public NewOrderRequestHandlerTest()
{ {
_buyerRepositoryMock = new Mock<IBuyerRepository<Buyer>>();
_orderRepositoryMock = new Mock<IOrderRepository<Order>>(); _orderRepositoryMock = new Mock<IOrderRepository<Order>>();
_identityServiceMock = new Mock<IIdentityService>(); _identityServiceMock = new Mock<IIdentityService>();
_mediator = new Mock<IMediator>();
} }
[Fact] [Fact]
@ -37,21 +38,15 @@ namespace UnitTest.Ordering.Application
{ ["cardExpiration"] = DateTime.Now.AddYears(1) }); { ["cardExpiration"] = DateTime.Now.AddYears(1) });
// Arrange // Arrange
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId))
.Returns(Task.FromResult<Buyer>(FakeBuyer()));
_orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny<int>()))
.Returns(Task.FromResult<Order>(FakeOrder()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1));
_orderRepositoryMock.Setup(or => or.Add(FakeOrder()))
.Returns(FakeOrder());
_orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
_orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1)); .Returns(Task.FromResult(1));
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
//Act //Act
var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var result = await handler.Handle(fakeOrderCmd); var result = await handler.Handle(fakeOrderCmd);
//Assert //Assert
@ -66,19 +61,16 @@ namespace UnitTest.Ordering.Application
var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary<string, object> var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary<string, object>
{ ["cardExpiration"] = DateTime.Now.AddYears(1) }); { ["cardExpiration"] = DateTime.Now.AddYears(1) });
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId))
.Returns(Task.FromResult<Buyer>(FakeBuyer()));
_orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny<int>()))
.Returns(Task.FromResult<Order>(FakeOrder()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
_orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1)); .Returns(Task.FromResult(1));
_orderRepositoryMock.Setup(or => or.Add(FakeOrder())).Returns(FakeOrder());
_orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(0));
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
//Act //Act
var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var result = await handler.Handle(fakeOrderCmd); var result = await handler.Handle(fakeOrderCmd);
//Assert //Assert
@ -95,21 +87,15 @@ namespace UnitTest.Ordering.Application
{ ["cardExpiration"] = DateTime.Now.AddYears(-1) }); { ["cardExpiration"] = DateTime.Now.AddYears(-1) });
// Arrange // Arrange
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId))
.Returns(Task.FromResult<Buyer>(FakeBuyer()));
_orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny<int>()))
.Returns(Task.FromResult<Order>(FakeOrder()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1));
_orderRepositoryMock.Setup(or => or.Add(FakeOrder()))
.Returns(FakeOrder());
_orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
_orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1)); .Returns(Task.FromResult(1));
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
//Act //Act
var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
//Assert //Assert
await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd)); await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd));
@ -128,21 +114,15 @@ namespace UnitTest.Ordering.Application
}); });
// Arrange // Arrange
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId))
.Returns(Task.FromResult<Buyer>(FakeBuyer()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1));
_orderRepositoryMock.Setup(or => or.Add(FakeOrder()))
.Returns(FakeOrder());
_orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny<int>()))
.Returns(Task.FromResult<Order>(FakeOrder()));
_orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
_orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1)); .Returns(Task.FromResult(1));
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
//Act //Act
var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
//Assert //Assert
await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd)); await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd));
@ -161,21 +141,15 @@ namespace UnitTest.Ordering.Application
}); });
// Arrange // Arrange
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId))
.Returns(Task.FromResult<Buyer>(FakeBuyer()));
_orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny<int>()))
.Returns(Task.FromResult<Order>(FakeOrder()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1));
_orderRepositoryMock.Setup(or => or.Add(FakeOrder()))
.Returns(FakeOrder());
_orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
_orderRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1)); .Returns(Task.FromResult(1));
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId); _identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
//Act //Act
var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
//Assert //Assert
await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd)); await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd));
@ -185,7 +159,7 @@ namespace UnitTest.Ordering.Application
public async Task Handle_throws_exception_when_no_cardNumber() public async Task Handle_throws_exception_when_no_cardNumber()
{ {
var buyerId = "1234";
var orderId = "1234";
var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary<string, object> var fakeOrderCmd = FakeOrderRequestWithBuyer(new Dictionary<string, object>
{ {
@ -194,21 +168,15 @@ namespace UnitTest.Ordering.Application
}); });
// Arrange // Arrange
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(buyerId))
.Returns(Task.FromResult<Buyer>(FakeBuyer()));
_orderRepositoryMock.Setup(orderRepo => orderRepo.GetAsync(It.IsAny<int>()))
.Returns(Task.FromResult<Order>(FakeOrder()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
_orderRepositoryMock.Setup(orderRepo => orderRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1)); .Returns(Task.FromResult(1));
_orderRepositoryMock.Setup(or => or.Add(FakeOrder()))
.Returns(FakeOrder());
_orderRepositoryMock.Setup(or => or.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
.Returns(Task.FromResult(1));
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(buyerId);
_identityServiceMock.Setup(svc => svc.GetUserIdentity()).Returns(orderId);
//Act //Act
var handler = new CreateOrderCommandHandler(_buyerRepositoryMock.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
var handler = new CreateOrderCommandHandler(_mediator.Object, _orderRepositoryMock.Object, _identityServiceMock.Object);
//Assert //Assert
await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd)); await Assert.ThrowsAsync<ArgumentException>(async () => await handler.Handle(fakeOrderCmd));
@ -228,7 +196,7 @@ namespace UnitTest.Ordering.Application
private Order FakeOrder() private Order FakeOrder()
{ {
return new Order(1, 1, new Address("street", "city", "state", "country", "zipcode"));
return new Order(new Address("street", "city", "state", "country", "zipcode"), 1, "12", "111", "fakeName", DateTime.Now.AddYears(1));
} }
private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary<string, object> args = null) private CreateOrderCommand FakeOrderRequestWithBuyer(Dictionary<string, object> args = null)
@ -243,7 +211,9 @@ namespace UnitTest.Ordering.Application
cardExpiration: args != null && args.ContainsKey("cardExpiration") ? (DateTime)args["cardExpiration"] : DateTime.MinValue, cardExpiration: args != null && args.ContainsKey("cardExpiration") ? (DateTime)args["cardExpiration"] : DateTime.MinValue,
cardSecurityNumber: args != null && args.ContainsKey("cardSecurityNumber") ? (string)args["cardSecurityNumber"] : "123", cardSecurityNumber: args != null && args.ContainsKey("cardSecurityNumber") ? (string)args["cardSecurityNumber"] : "123",
cardHolderName: args != null && args.ContainsKey("cardHolderName") ? (string)args["cardHolderName"] : "XXX", cardHolderName: args != null && args.ContainsKey("cardHolderName") ? (string)args["cardHolderName"] : "XXX",
cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0);
cardTypeId: args != null && args.ContainsKey("cardTypeId") ? (int)args["cardTypeId"] : 0,
paymentId: args != null && args.ContainsKey("paymentId") ? (int)args["paymentId"] : 0,
buyerId: args != null && args.ContainsKey("buyerId") ? (int)args["buyerId"] : 0);
} }
} }
} }

+ 2
- 1
test/Services/UnitTest/Ordering/Domain/BuyerAggregateTest.cs View File

@ -40,11 +40,12 @@ public class BuyerAggregateTest
var securityNumber = "1234"; var securityNumber = "1234";
var cardHolderName = "FakeHolderNAme"; var cardHolderName = "FakeHolderNAme";
var expiration = DateTime.Now.AddYears(1); var expiration = DateTime.Now.AddYears(1);
var orderId = 1;
var identity = new Guid().ToString(); var identity = new Guid().ToString();
var fakeBuyerItem = new Buyer(identity); var fakeBuyerItem = new Buyer(identity);
//Act //Act
var result = fakeBuyerItem.AddPaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration);
var result = fakeBuyerItem.AddPaymentMethod(cardTypeId, alias, cardNumber, securityNumber, cardHolderName, expiration, orderId);
//Assert //Assert
Assert.NotNull(result); Assert.NotNull(result);


Loading…
Cancel
Save