Vertical slice architecture is a software design approach that organizes an application into self-contained, end-to-end features or “slices,” rather than by traditional horizontal layers (such as data access, business logic, and presentation layers). Each vertical slice represents a complete feature or functionality that includes all the necessary components, from the user interface down to the database interactions.
Key Concepts of Vertical Slice Architecture:
- Feature-Centric Organization: Each slice corresponds to a specific feature or user story, encapsulating all the logic and data related to that feature.
- Independence: Slices are independent from each other, which allows teams to develop, test, and deploy them separately. This modularity can lead to better maintainability and scalability.
- Cross-Cutting Concerns Included: Each slice can include UI elements, application logic, data access, and even external dependencies, reducing the need for common layers that cross-cut the entire application.
- Domain-Driven Design (DDD) Alignment: Vertical slice architecture often aligns well with domain-driven design principles, focusing on business capabilities and keeping the codebase aligned with the problem domain.
- Simplified Testing: Since each slice is a self-contained unit, it can be tested in isolation, leading to simpler and more effective unit and integration testing.
- Flexibility: Vertical slices can evolve independently. New features can be added as new slices without modifying existing slices, thus reducing the risk of introducing bugs in existing functionality.
Example:
Consider a web application with a feature like “User Registration.” In a traditional layered architecture, this feature would have parts spread across multiple layers:
- UI Layer: Handles the user interface and form submission.
- Business Logic Layer: Contains the registration logic.
- Data Access Layer: Interacts with the database to store user information.
In a vertical slice architecture, the “User Registration” feature would be a single slice that includes:
- The UI logic for displaying the form and handling submissions.
- The business logic for validating and processing the registration.
- The data access logic to store the user in the database.
This self-contained slice can be developed, tested, and deployed independently of other features in the application.
Advantages of VSA
- Focused Development: Developers can focus on one feature at a time, leading to more coherent and understandable code.
- Reduced Complexity: By avoiding shared layers, the system’s complexity is reduced.
- Better Scaling: As the application grows, adding new features doesn’t require altering the existing structure significantly.
- Improved Collaboration: Teams can work on different slices without interfering with each other.
Disadvantages of VSA
- Potential Duplication: Some code or logic might be duplicated across slices, which can lead to higher maintenance costs.
- Learning Curve: For teams accustomed to traditional layered architecture, adopting a vertical slice approach may require a shift in mindset.
Vertical slice architecture is especially beneficial in microservices and modular monoliths, where isolation and independent deployment are key considerations.
Vertical Slice Architecture – Project Structure
Here’s an example of how you might structure a user management feature in a .NET project using vertical slice architecture:
Project Structure
/src
/Application
/Users
/Commands
/RegisterUser
RegisterUserCommand.cs
RegisterUserHandler.cs
RegisterUserValidator.cs
/UpdateUser
UpdateUserCommand.cs
UpdateUserHandler.cs
UpdateUserValidator.cs
/Queries
/GetUser
GetUserQuery.cs
GetUserHandler.cs
GetUserResponse.cs
/GetAllUsers
GetAllUsersQuery.cs
GetAllUsersHandler.cs
GetAllUsersResponse.cs
/Dtos
UserDto.cs
/Events
UserRegisteredEvent.cs
/Interfaces
IUserRepository.cs
/Common
/Behaviors
ValidationBehavior.cs
/Interfaces
IApplicationDbContext.cs
/MediatR
MediatorExtensions.cs
/Domain
/Entities
User.cs
/ValueObjects
Email.cs
/Infrastructure
/Persistence
ApplicationDbContext.cs
UserRepository.cs
/Services
EmailService.cs
/API
/Controllers
UsersController.cs
/Middlewares
ExceptionHandlingMiddleware.cs
/Tests
/UnitTests
/Application
/Users
/Commands
RegisterUserCommandTests.cs
/Queries
GetUserQueryTests.cs
/IntegrationTests
/API
UsersControllerTests.cs
Explanation
- Application Layer
- Commands and Queries: The
Usersfolder contains separate folders for commands (e.g.,RegisterUser) and queries (e.g.,GetUser). Each command/query has its handler, validator (if needed), and relevant request/response objects. This aligns with the CQRS (Command Query Responsibility Segregation) pattern. - Dtos: The
UserDto.csis used for data transfer objects that define the shape of the data being passed around in the application. - Events: Domain events like
UserRegisteredEvent.csare raised during user-related actions. - Interfaces: Define contracts like
IUserRepository.cs, which abstracts the persistence logic.
- Commands and Queries: The
- Domain Layer
- Entities: The
User.csentity represents the user domain model. - ValueObjects: The
Email.csvalue object represents an email, ensuring it is validated and treated consistently across the domain.
- Entities: The
- Infrastructure Layer
- Persistence: Contains implementations for data access like
ApplicationDbContext.cs(EF Core DbContext) andUserRepository.cs. - Services: Any external services or infrastructure dependencies, such as
EmailService.cs.
- Persistence: Contains implementations for data access like
- API Layer
- Controllers: The
UsersController.cshandles HTTP requests related to users and uses MediatR to dispatch commands and queries. - Middlewares: Handles cross-cutting concerns, such as the
ExceptionHandlingMiddleware.csfor global error handling.
- Controllers: The
- Tests Layer
- UnitTests: Contains unit tests for application logic, such as command and query handlers.
- IntegrationTests: Contains integration tests, focusing on the API and ensuring that all the layers interact correctly.
Project Structure Principles
- Feature Separation: Each feature (in this case, user management) is self-contained, following the principles of vertical slice architecture.
- CQRS: Commands modify state, while queries read state, leading to a clear separation of concerns.
- Dependency Injection: Interfaces like
IUserRepositoryare injected into the handlers, promoting loose coupling. - Testing: The structure supports both unit and integration testing, ensuring that each slice can be tested independently.
This structure is designed to be scalable, maintainable, and testable, making it ideal for complex applications.