Model Mapping in CQRS Pattern with C# Web API
What is CQRS?
CQRS stands for Command Query Responsibility Segregation. It is an architectural pattern that separates the read and write operations (commands and queries) for a data store into separate models. This pattern acknowledges that the requirements for read and write operations in an application can differ significantly, and by separating these concerns, it aims to simplify the design and improve performance.
Embark on a journey of continuous learning and exploration with DotNet-FullStack-Dev. Uncover more by visiting our https://dotnet-fullstack-dev.blogspot.com reach out for further information.
How CQRS Works?
In traditional CRUD (Create, Read, Update, Delete) applications, the same model and database schema are often used for both reading and writing data. However, in CQRS:
Separate Models:
- Command Model: Handles operations that modify data (Create, Update, Delete).
- Query Model: Handles operations that retrieve data (Read).
Command Handling:
- Commands are actions that change the state of the application (e.g., creating a new user, updating a product).
- Command handlers receive commands, validate them, and perform the necessary actions on the domain model.
Query Handling:
- Queries are actions that retrieve data from the application (e.g., retrieving user details, listing products).
- Query handlers retrieve data from specialized query models optimized for reading operations.
Separate Data Stores:
- Commands typically update a write-optimized data store (e.g., a SQL database).
- Queries typically read from read-optimized data stores or query models (e.g., read replicas, NoSQL databases, Elasticsearch).
Eventual Consistency:
- Due to the separation of models and possibly different data stores, there might be eventual consistency between the command and query models.
- This means that after a write operation, it may take some time for the read model to reflect the changes.
Benefits of CQRS:
- Scalability: Allows scaling read and write operations independently based on the application’s requirements.
- Performance: Optimizes read operations by using specialized query models and data stores.
- Simplicity: Simplifies the domain model by focusing each model on a specific responsibility (write vs. read).
- Flexibility: Enables different optimization strategies for commands and queries based on the application’s needs.
Considerations:
- Complexity: Introducing CQRS adds complexity to the application due to managing separate models and potential eventual consistency.
- Learning Curve: Developers need to understand and implement the pattern correctly to leverage its benefits effectively.
Example Use Cases:
- E-commerce Platforms: Separate write operations (placing orders, updating inventory) from read operations (listing products, retrieving order history).
- Financial Systems: Handle transactions (deposits, withdrawals) separately from querying account balances or transaction histories.
1. Mapping View Models to Domain Models (Commands)
In CQRS, commands are typically used for write operations, where view models from the client are mapped to domain models that represent the business logic and data structure.
Example: Mapping View Model to Domain Model (Create Operation)
// View Model
public class CreateUserViewModel
{
public string Username { get; set; }
public string Email { get; set; }
// Other properties
}
// Domain Model
public class User
{
public string Username { get; set; }
public string Email { get; set; }
// Other properties
}
// Mapper Class (using AutoMapper for simplicity)
public class UserMapper
{
private readonly IMapper _mapper;
public UserMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<CreateUserViewModel, User>();
});
_mapper = config.CreateMapper();
}
public User MapToDomainModel(CreateUserViewModel viewModel)
{
return _mapper.Map<CreateUserViewModel, User>(viewModel);
}
}
// Usage in Web API Controller
[HttpPost("users")]
public IActionResult CreateUser([FromBody] CreateUserViewModel createUserViewModel)
{
// Map view model to domain model
var newUser = _userMapper.MapToDomainModel(createUserViewModel);
// Perform command handling (e.g., saving to database)
_userRepository.Add(newUser);
_unitOfWork.SaveChanges();
return Ok("User created successfully");
}
2. Mapping Domain Models to View Models (Queries)
In CQRS, queries are used for read operations, where domain models retrieved from the database are mapped to view models for presentation to the client.
Example: Mapping Domain Model to View Model (Read Operation)
// View Model
public class UserViewModel
{
public string Username { get; set; }
public string Email { get; set; }
// Other properties
}
// Domain Model (same as before)
// Mapper Class (using AutoMapper for simplicity)
public class UserMapper
{
private readonly IMapper _mapper;
public UserMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserViewModel>();
});
_mapper = config.CreateMapper();
}
public UserViewModel MapToViewModel(User domainModel)
{
return _mapper.Map<User, UserViewModel>(domainModel);
}
}
// Usage in Web API Controller
[HttpGet("users/{userId}")]
public IActionResult GetUserById(int userId)
{
// Retrieve domain model from database
var user = _userRepository.GetById(userId);
if (user == null)
{
return NotFound();
}
// Map domain model to view model
var userViewModel = _userMapper.MapToViewModel(user);
return Ok(userViewModel);
}
Conclusion
In the CQRS pattern, separating commands (write operations) and queries (read operations) helps to achieve better separation of concerns and scalability in applications. Using model mapping techniques such as AutoMapper simplifies the process of converting between view models and domain models, ensuring clarity and maintainability in your codebase. By following these practices, you can effectively implement CQRS in your C# Web API projects, enhancing both performance and developer productivity.