Vertical Slice Architecture in ASP.NET Core
Introduction
Most of us are familiar with clean architecture, nowadays, this approach called traditional layered architecture.
A traditional layered or onion architecture organizes code based on technical concerns in different layers. In this approach, each layer has individual responsibility in the system.
The layers then depend on each other, and they can collaborate with each other.
In web applications, the following layers might apply:
Onion architecture approach
Traditional layered architecture problems
Vertical Slice Architecture
A vertical slice architecture is an architectural pattern that organize code by features instead of technical patterns.
Vertical slice architecture
As you can see in the picture, the idea of vertical slice architecture is about grouping the code based on business functionalities, and it will put all relevant codes together.
In layered architecture the goal is to think about horizontal layers but in now we need to think about vertical layers, in this case we need to put every thing as a feature, that means we don’t need shared layers like repositories, services, infrastructure, and even controllers, we just need to focus on features.
Understanding Vertical Slice Architecture in .NET Core: A Pizza Ordering Example
Imagine you're building an online pizza ordering website. Traditionally, you might organize your code into layers:
This approach works, but it can lead to scattered code and make it harder to see how specific features work. Enter Vertical Slice Architecture!
Slicing the Pizza: Organizing by Features
Instead of horizontal layers, vertical slices group code by features. Each slice is a self-contained unit representing a complete functionality, like "Order Pizza" or "Manage Orders."
Here's how our pizza delivery app might be organized with vertical slices:
/PizzaDelivery
/Features
/PlaceOrder
PlaceOrderCommand.cs
PlaceOrderHandler.cs
PlaceOrderValidator.cs
/UpdateOrderStatus
UpdateOrderStatusCommand.cs
UpdateOrderStatusHandler.cs
/ListOrders
ListOrdersQuery.cs
ListOrdersHandler.cs
/Controllers
PizzaController.cs
PizzaDelivery.csproj
Notice how each feature slice contains all the code it needs to function independently: controllers for handling user interaction, services for business logic, and repositories for data access.
Benefits of Vertical Slices
Vertical slice architecture offers several advantages:
Let's Order a Pizza!
Let's look at a simplified example of the "Pizza Delivery" slice:
领英推荐
PlaceOrderCommand.cs
public class PlaceOrderCommand
{
public string CustomerName { get; set; }
public List<string> PizzaItems { get; set; }
}
This class defines a data structure (DTO - Data Transfer Object) that represents the information needed to place a new pizza order. It includes properties such as CustomerName and PizzaItems.
PlaceOrderHandler.cs
public class PlaceOrderHandler
{
public Task Handle(PlaceOrderCommand command)
{
// Implement the logic to place a new pizza order in the database
// You can use a repository or an ORM like Entity Framework Core
// For simplicity, let's assume there's a PizzaOrderRepository class
var order = new PizzaOrder
{
CustomerName = command.CustomerName,
PizzaItems = command.PizzaItems,
Status = OrderStatus.Pending
};
PizzaOrderRepository.AddOrder(order);
return Task.CompletedTask;
}
}
This class implements the business logic for handling the PlaceOrderCommand. It creates a new PizzaOrder object, sets its properties based on the command, and then adds it to the database. The database interaction is abstracted here for simplicity.
PlaceOrderValidator.cs
public class PlaceOrderValidator
{
public bool Validate(PlaceOrderCommand command)
{
// Implement validation logic here
// For simplicity, let's assume both customer name and pizza items are required
return !string.IsNullOrEmpty(command.CustomerName) && command.PizzaItems?.Any() == true;
}
}
This class contains validation logic for the PlaceOrderCommand. It ensures that both CustomerName and PizzaItems are provided.
UpdateOrderStatus
UpdateOrderStatusCommand.cs
public class UpdateOrderStatusCommand
{
public int OrderId { get; set; }
public OrderStatus NewStatus { get; set; }
}
This class represents the data needed to update the status of a pizza order. It includes properties such as OrderId and NewStatus.
UpdateOrderStatusHandler.cs
public class UpdateOrderStatusHandler
{
public Task Handle(UpdateOrderStatusCommand command)
{
// Implement the logic to update the status of a pizza order in the database
// For simplicity, let's assume there's a PizzaOrderRepository class
var order = PizzaOrderRepository.GetOrderById(command.OrderId);
if (order != null)
{
order.Status = command.NewStatus;
PizzaOrderRepository.UpdateOrder(order);
}
return Task.CompletedTask;
}
}
This class handles the business logic for updating the status of a pizza order. It retrieves the existing order from the database, updates its status based on the command, and then updates the order in the database.
ListOrders Feature:
ListOrdersQuery.cs
public class ListOrdersQuery
{
// You can add query parameters here if needed
}
This class represents a query for listing pizza orders. It can be expanded with additional parameters if needed.
ListOrdersHandler.cs
public class ListOrdersHandler
{
public List<PizzaOrder> Handle(ListOrdersQuery query)
{
// Implement the logic to retrieve a list of pizza orders from the database
// For simplicity, let's assume there's a PizzaOrderRepository class
return PizzaOrderRepository.GetAllOrders();
}
}
This class handles the business logic for listing pizza orders. It retrieves a list of all pizza orders from the database.
Controllers
PizzaController.cs
[ApiController]
[Route("api/pizza")]
public class PizzaController : ControllerBase
{
private readonly PlaceOrderHandler _placeOrderHandler;
private readonly UpdateOrderStatusHandler _updateOrderStatusHandler;
private readonly ListOrdersHandler _listOrdersHandler;
public PizzaController(
PlaceOrderHandler placeOrderHandler,
UpdateOrderStatusHandler updateOrderStatusHandler,
ListOrdersHandler listOrdersHandler)
{
_placeOrderHandler = placeOrderHandler;
_updateOrderStatusHandler = updateOrderStatusHandler;
_listOrdersHandler = listOrdersHandler;
}
[HttpPost("place-order")]
public IActionResult PlaceOrder([FromBody] PlaceOrderCommand command)
{
var validator = new PlaceOrderValidator();
if (!validator.Validate(command))
{
return BadRequest("Invalid order data");
}
_placeOrderHandler.Handle(command);
return Ok("Order placed successfully");
}
[HttpPost("update-status")]
public IActionResult UpdateOrderStatus([FromBody] UpdateOrderStatusCommand command)
{
_updateOrderStatusHandler.Handle(command);
return Ok("Order status updated successfully");
}
[HttpGet("list-orders")]
public IActionResult ListOrders()
{
var query = new ListOrdersQuery();
var orders = _listOrdersHandler.Handle(query);
return Ok(orders);
}
}
This controller class handles incoming HTTP requests related to pizza orders. It includes three actions:
This adapted example demonstrates a simple vertical slice architecture for a pizza delivery system using .NET Core. Each feature (PlaceOrder, UpdateOrderStatus, ListOrders) has its own set of classes organized within the Features directory. The controllers in the Controllers directory use these features to handle HTTP requests. This structure follows the vertical slice architecture, promoting separation of concerns and maintainability.