Skip to content

Instantly share code, notes, and snippets.

@davepcallan
Last active December 18, 2024 00:15
Show Gist options
  • Save davepcallan/6ec66f9a7ad77d9ac10a68daac6b57a8 to your computer and use it in GitHub Desktop.
Save davepcallan/6ec66f9a7ad77d9ac10a68daac6b57a8 to your computer and use it in GitHub Desktop.
Vertical Slice Architecture example - UseCases -> CreateOrder.cs
// 1. Order Entity
namespace VerticalSliceExample.Domain;
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Dictionary<int, int> Products { get; set; } = new(); // ProductId -> Quantity
public decimal TotalAmount { get; private set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public void CalculateTotal(Dictionary<int, decimal> productPrices)
{
TotalAmount = 0;
foreach (var product in Products)
{
if (productPrices.TryGetValue(product.Key, out var price))
{
TotalAmount += price * product.Value;
}
else
{
throw new InvalidOperationException($"Product with ID {product.Key} not found in pricing data.");
}
}
}
}
// 2. DbContext
namespace VerticalSliceExample.Persistence;
using Microsoft.EntityFrameworkCore;
using VerticalSliceExample.Domain;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<Order> Orders { get; set; }
}
// 3. Use Case: CreateOrder
namespace VerticalSliceExample.UseCases.CreateOrder;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using VerticalSliceExample.Domain;
using VerticalSliceExample.Persistence;
public record Request(int CustomerId, Dictionary<int, int> Products) : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (CustomerId <= 0)
yield return new ValidationResult("CustomerId must be greater than zero.", new[] { nameof(CustomerId) });
if (Products == null || Products.Count == 0)
yield return new ValidationResult("At least one product must be included in the order.", new[] { nameof(Products) });
foreach (var (productId, quantity) in Products)
{
if (productId <= 0)
yield return new ValidationResult("Product ID must be greater than zero.", new[] { nameof(Products) });
if (quantity <= 0)
yield return new ValidationResult($"Quantity for product {productId} must be greater than zero.", new[] { nameof(Products) });
}
}
}
public record Response(int OrderId, decimal TotalAmount, string Message);
[ApiController]
[Route("api/orders")]
public class CreateOrderController(AppDbContext dbContext, ILogger<CreateOrderController> logger) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] Request request)
{
// Validate the incoming request
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Mocked product prices (in real-world, fetch from Product table or service)
var productPrices = new Dictionary<int, decimal>
{
{ 1, 100.00m }, // Product ID -> Price
{ 2, 50.00m },
{ 3, 20.00m }
};
try
{
// Create and validate the Order
var order = new Order
{
CustomerId = request.CustomerId,
Products = request.Products
};
order.CalculateTotal(productPrices); // Calculate total price
// Persist the order
dbContext.Orders.Add(order);
await dbContext.SaveChangesAsync();
// Return response
var response = new Response(order.Id, order.TotalAmount, "Order created successfully.");
return CreatedAtAction(nameof(CreateOrder), new { id = order.Id }, response);
}
catch (Exception ex) // Catching a more generic exception
{
// Log the error
logger.LogError(ex, "An error occurred while creating the order.");
// Return a generic error message (don't expose internal details)
return StatusCode(500, new { Message = "An unexpected error occurred. Please try again later." });
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment