Skip to content

Commit

Permalink
Update OrderQueries to use linq queries (dotnet#115)
Browse files Browse the repository at this point in the history
* Add public get methods for GetOrderDate and GetDescription

* Make query more idiomatic

As mentioned in issue dotnet#23

* Remove unused MapOrderItems

* Make  CardTypesQuery more idomatic

* Make buyerId a auto property

Make buyerId a auto property in oder to use buyerid inside linq queries.

* GetOrdersFromUserAsync to linq
Make GetOrdersFromUserAsync more idomatic and remove unused NpgsqlDataSource

* - refactor: converted private fields to properties on Order and OrderItem
- don't use repository any more

* Add buyer navigation property

* Introduce SetPaymentMethodVerified method on Order to better align with business operations

* Convert GetTotal to expression body and remove unnecessary HasColumnName.

* Merge enum Orderstatus changes

* Remove explicit config and Add private setters so implicit configuration from ef core will work
  • Loading branch information
AlmarAubel authored Feb 8, 2024
1 parent 3b49f61 commit 6603215
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ public static OrderDraftDTO FromOrder(Order order)
{
OrderItems = order.OrderItems.Select(oi => new OrderItemDTO
{
Discount = oi.GetCurrentDiscount(),
Discount = oi.Discount,
ProductId = oi.ProductId,
UnitPrice = oi.GetUnitPrice(),
PictureUrl = oi.GetPictureUri(),
Units = oi.GetUnits(),
ProductName = oi.GetOrderItemProductName()
UnitPrice = oi.UnitPrice,
PictureUrl = oi.PictureUrl,
Units = oi.Units,
ProductName = oi.ProductName
}),
Total = order.GetTotal()
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task Handle(OrderCancelledDomainEvent domainEvent, CancellationToke
OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.Order.Id, OrderStatus.Cancelled);

var order = await _orderRepository.GetAsync(domainEvent.Order.Id);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value);
var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value);

var integrationEvent = new OrderStatusChangedToCancelledIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid);
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task Handle(OrderShippedDomainEvent domainEvent, CancellationToken
OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.Order.Id, OrderStatus.Shipped);

var order = await _orderRepository.GetAsync(domainEvent.Order.Id);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value);
var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value);

var integrationEvent = new OrderStatusChangedToShippedIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid);
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public async Task Handle(OrderStatusChangedToAwaitingValidationDomainEvent domai
OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, OrderStatus.AwaitingValidation);

var order = await _orderRepository.GetAsync(domainEvent.OrderId);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value);
var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value);

var orderStockList = domainEvent.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.Units));

var integrationEvent = new OrderStatusChangedToAwaitingValidationIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid, orderStockList);
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ public async Task Handle(OrderStatusChangedToPaidDomainEvent domainEvent, Cancel
OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, OrderStatus.Paid);

var order = await _orderRepository.GetAsync(domainEvent.OrderId);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value);
var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value);

var orderStockList = domainEvent.OrderItems
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.GetUnits()));
.Select(orderItem => new OrderStockItem(orderItem.ProductId, orderItem.Units));

var integrationEvent = new OrderStatusChangedToPaidIntegrationEvent(
domainEvent.OrderId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task Handle(OrderStatusChangedToStockConfirmedDomainEvent domainEve
OrderingApiTrace.LogOrderStatusUpdated(_logger, domainEvent.OrderId, OrderStatus.StockConfirmed);

var order = await _orderRepository.GetAsync(domainEvent.OrderId);
var buyer = await _buyerRepository.FindByIdAsync(order.GetBuyerId.Value);
var buyer = await _buyerRepository.FindByIdAsync(order.BuyerId.Value);

var integrationEvent = new OrderStatusChangedToStockConfirmedIntegrationEvent(order.Id, order.OrderStatus, buyer.Name, buyer.IdentityGuid);
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public UpdateOrderWhenBuyerAndPaymentMethodVerifiedDomainEventHandler(
public async Task Handle(BuyerAndPaymentMethodVerifiedDomainEvent domainEvent, CancellationToken cancellationToken)
{
var orderToUpdate = await _orderRepository.GetAsync(domainEvent.OrderId);
orderToUpdate.SetBuyerId(domainEvent.Buyer.Id);
orderToUpdate.SetPaymentId(domainEvent.Payment.Id);
orderToUpdate.SetPaymentMethodVerified(domainEvent.Buyer.Id, domainEvent.Payment.Id);
OrderingApiTrace.LogOrderPaymentMethodUpdated(_logger, domainEvent.OrderId, nameof(domainEvent.Payment), domainEvent.Payment.Id);
}
}
108 changes: 39 additions & 69 deletions src/Ordering.API/Application/Queries/OrderQueries.cs
Original file line number Diff line number Diff line change
@@ -1,83 +1,53 @@
namespace eShop.Ordering.API.Application.Queries;

public class OrderQueries(NpgsqlDataSource dataSource)
public class OrderQueries(OrderingContext context)
: IOrderQueries
{
public async Task<Order> GetOrderAsync(int id)
{
using var connection = dataSource.OpenConnection();

var result = await connection.QueryAsync<dynamic>("""
SELECT o."Id" AS ordernumber, o."OrderDate" AS date, o."Description" AS description, o."Address_City" AS city,
o."Address_Country" AS country, o."Address_State" AS state, o."Address_Street" AS street,
o."Address_ZipCode" AS zipcode, o."OrderStatus" AS status, oi."ProductName" AS productname, oi."Units" AS units,
oi."UnitPrice" AS unitprice, oi."PictureUrl" AS pictureurl
FROM ordering.Orders AS o
LEFT JOIN ordering."orderItems" AS oi ON o."Id" = oi."OrderId"
WHERE o."Id" = @id
""",
new { id });

if (result.AsList().Count == 0)
var order = await context.Orders
.Include(o => o.OrderItems)
.FirstOrDefaultAsync(o => o.Id == id);

if (order is null)
throw new KeyNotFoundException();

return MapOrderItems(result);
return new Order
{
ordernumber = order.Id,
date = order.OrderDate,
description = order.Description,
city = order.Address.City,
country = order.Address.Country,
state = order.Address.State,
street = order.Address.Street,
zipcode = order.Address.ZipCode,
status = order.OrderStatus.ToString(),
total = order.GetTotal(),
orderitems = order.OrderItems.Select(oi => new Orderitem
{
productname = oi.ProductName,
units = oi.Units,
unitprice = (double)oi.UnitPrice,
pictureurl = oi.PictureUrl
}).ToList()
};
}

public async Task<IEnumerable<OrderSummary>> GetOrdersFromUserAsync(string userId)
{
using var connection = dataSource.OpenConnection();

return await connection.QueryAsync<OrderSummary>("""
SELECT o."Id" AS ordernumber, o."OrderDate" AS date, o."OrderStatus" AS status, SUM(oi."Units" * oi."UnitPrice") AS total
FROM ordering.orders AS o
LEFT JOIN ordering."orderItems" AS oi ON o."Id" = oi."OrderId"
LEFT JOIN ordering.buyers AS ob ON o."BuyerId" = ob."Id"
WHERE ob."IdentityGuid" = @userId
GROUP BY o."Id", o."OrderDate", o."OrderStatus"
ORDER BY o."Id"
""",
new { userId });
}

public async Task<IEnumerable<CardType>> GetCardTypesAsync()
{
using var connection = dataSource.OpenConnection();

return await connection.QueryAsync<CardType>("SELECT * FROM ordering.cardtypes");
}

private Order MapOrderItems(dynamic result)
{
var order = new Order
{
ordernumber = result[0].ordernumber,
date = result[0].date,
status = result[0].status,
description = result[0].description,
street = result[0].street,
city = result[0].city,
state = result[0].state,
zipcode = result[0].zipcode,
country = result[0].country,
orderitems = new List<Orderitem>(),
total = 0
};

foreach (dynamic item in result)
{
var orderitem = new Orderitem
return await context.Orders
.Where(o => o.Buyer.IdentityGuid == userId)
.Select(o => new OrderSummary
{
productname = item.productname,
units = item.units,
unitprice = (double)item.unitprice,
pictureurl = item.pictureurl
};

order.total += item.units * item.unitprice;
order.orderitems.Add(orderitem);
}

return order;
}
ordernumber = o.Id,
date = o.OrderDate,
status = o.OrderStatus.ToString(),
total =(double) o.OrderItems.Sum(oi => oi.UnitPrice* oi.Units)
})
.ToListAsync();
}

public async Task<IEnumerable<CardType>> GetCardTypesAsync() =>
await context.CardTypes.Select(c=> new CardType { Id = c.Id, Name = c.Name }).ToListAsync();
}
61 changes: 25 additions & 36 deletions src/Ordering.Domain/AggregatesModel/OrderAggregate/Order.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,19 @@ namespace eShop.Ordering.Domain.AggregatesModel.OrderAggregate;
public class Order
: Entity, IAggregateRoot
{
// DDD Patterns comment
// Using private fields, allowed since EF Core 1.1, is a much better encapsulation
// aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
private DateTime _orderDate;
public DateTime OrderDate { get; private set; }

// Address is a Value Object pattern example persisted as EF Core 2.0 owned entity
[Required]
public Address Address { get; private set; }

public int? GetBuyerId => _buyerId;
private int? _buyerId;
public int? BuyerId { get; private set; }

public OrderStatus OrderStatus { get; private set; }
public Buyer Buyer { get; }

private string _description;
public OrderStatus OrderStatus { get; private set; }

public string Description { get; private set; }

// Draft orders have this set to true. Currently we don't check anywhere the draft status of an Order, but we could do it if needed
#pragma warning disable CS0414 // The field 'Order._isDraft' is assigned but its value is never used
Expand All @@ -31,9 +29,10 @@ public class Order
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
// but only through the method OrderAggregateRoot.AddOrderItem() which includes behavior.
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;

public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();

private int? _paymentMethodId;
public int? PaymentId { get; private set; }

public static Order NewDraft()
{
Expand All @@ -53,10 +52,10 @@ protected Order()
public Order(string userId, string userName, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null) : this()
{
_buyerId = buyerId;
_paymentMethodId = paymentMethodId;
BuyerId = buyerId;
PaymentId = paymentMethodId;
OrderStatus = OrderStatus.Submitted;
_orderDate = DateTime.UtcNow;
OrderDate = DateTime.UtcNow;
Address = address;

// Add the OrderStarterDomainEvent to the domain events collection
Expand All @@ -71,14 +70,12 @@ public Order(string userId, string userName, Address address, int cardTypeId, st
// in order to maintain consistency between the whole Aggregate.
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1)
{
var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId)
.SingleOrDefault();
var existingOrderForProduct = _orderItems.SingleOrDefault(o => o.ProductId == productId);

if (existingOrderForProduct != null)
{
//if previous line exist modify it with higher discount and units..

if (discount > existingOrderForProduct.GetCurrentDiscount())
if (discount > existingOrderForProduct.Discount)
{
existingOrderForProduct.SetNewDiscount(discount);
}
Expand All @@ -88,22 +85,17 @@ public void AddOrderItem(int productId, string productName, decimal unitPrice, d
else
{
//add validated new order item

var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
_orderItems.Add(orderItem);
}
}

public void SetPaymentId(int id)
public void SetPaymentMethodVerified(int buyerId, int paymentId)
{
_paymentMethodId = id;
BuyerId = buyerId;
PaymentId = paymentId;
}

public void SetBuyerId(int id)
{
_buyerId = id;
}


public void SetAwaitingValidationStatus()
{
if (OrderStatus == OrderStatus.Submitted)
Expand All @@ -120,7 +112,7 @@ public void SetStockConfirmedStatus()
AddDomainEvent(new OrderStatusChangedToStockConfirmedDomainEvent(Id));

OrderStatus = OrderStatus.StockConfirmed;
_description = "All the items were confirmed with available stock.";
Description = "All the items were confirmed with available stock.";
}
}

Expand All @@ -131,7 +123,7 @@ public void SetPaidStatus()
AddDomainEvent(new OrderStatusChangedToPaidDomainEvent(Id, OrderItems));

OrderStatus = OrderStatus.Paid;
_description = "The payment was performed at a simulated \"American Bank checking bank account ending on XX35071\"";
Description = "The payment was performed at a simulated \"American Bank checking bank account ending on XX35071\"";
}
}

Expand All @@ -143,7 +135,7 @@ public void SetShippedStatus()
}

OrderStatus = OrderStatus.Shipped;
_description = "The order was shipped.";
Description = "The order was shipped.";
AddDomainEvent(new OrderShippedDomainEvent(this));
}

Expand All @@ -156,7 +148,7 @@ public void SetCancelledStatus()
}

OrderStatus = OrderStatus.Cancelled;
_description = $"The order was cancelled.";
Description = $"The order was cancelled.";
AddDomainEvent(new OrderCancelledDomainEvent(this));
}

Expand All @@ -168,10 +160,10 @@ public void SetCancelledStatusWhenStockIsRejected(IEnumerable<int> orderStockRej

var itemsStockRejectedProductNames = OrderItems
.Where(c => orderStockRejectedItems.Contains(c.ProductId))
.Select(c => c.GetOrderItemProductName());
.Select(c => c.ProductName);

var itemsStockRejectedDescription = string.Join(", ", itemsStockRejectedProductNames);
_description = $"The product items don't have stock: ({itemsStockRejectedDescription}).";
Description = $"The product items don't have stock: ({itemsStockRejectedDescription}).";
}
}

Expand All @@ -190,8 +182,5 @@ private void StatusChangeException(OrderStatus orderStatusToChange)
throw new OrderingDomainException($"Is not possible to change the order status from {OrderStatus} to {orderStatusToChange}.");
}

public decimal GetTotal()
{
return _orderItems.Sum(o => o.GetUnits() * o.GetUnitPrice());
}
public decimal GetTotal() => _orderItems.Sum(o => o.Units * o.UnitPrice);
}
Loading

0 comments on commit 6603215

Please sign in to comment.