You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

技术问询:Query/QueryHandler与Command/CommandHandler设计模式的区别

Query/QueryHandler vs Command/CommandHandler: Key Differences for Beginners

Hey there! Let's break down these two patterns—they're both foundational to CQRS (Command Query Responsibility Segregation), but they serve totally distinct roles. Once you get their core purposes straight, the rest falls into place.

1. Core Purpose

First and foremost:

  • Command/CommandHandler: It's all about changing state. When you need to perform an action that modifies data, triggers a process, or has any kind of effect on your system, this is your go-to. Think "create a user", "update an order status", or "send a password reset email".
  • Query/QueryHandler: It's solely for reading data. No changes, no side effects—just fetching information to display or use elsewhere. Examples include "get all orders for a customer", "fetch product details by ID", or "count active users".

2. Return Values

This is a dead giveaway:

  • Command Handlers: Rarely return meaningful data (if at all). The focus is on executing the action, not returning results. You might return a simple identifier (like the ID of a newly created resource) or just void/Task (for async). Success or failure is usually communicated via exceptions or a dedicated result object if you need graceful error handling.
  • Query Handlers: Must return data. Their entire reason for existing is to provide a result—whether it's a single object, a list, a count, or a custom DTO (Data Transfer Object) tailored for your UI or service needs.

3. Side Effects

Side effects are anything that changes the system outside the handler's immediate scope:

  • Command Handlers: Expect side effects. They'll write to databases, call external APIs, publish events, update caches, or send notifications. That's their job!
  • Query Handlers: No side effects allowed. A good query handler should be as close to a pure function as possible—given the same input, it should always return the same output without modifying any state. Reading from a cache is okay (it's just a read optimization), but writing to it isn't.

4. Idempotency

Idempotency means repeating the same operation multiple times gives the same result as doing it once:

  • Command Handlers: Should be designed to be idempotent whenever possible. For example, if you have an UpdateUserEmailCommand with a user ID, running it twice shouldn't cause issues (the email just gets set to the same value twice). This is critical for reliability, especially in distributed systems where messages might be retried.
  • Query Handlers: Naturally idempotent. Running the same query 10 times will give you the same result every time (assuming the underlying data doesn't change between calls, which is outside the query's control).

Quick Code Examples to Drive It Home

Command/CommandHandler Example

// Command: Defines what action to take
public record UpdateUserEmailCommand(Guid UserId, string NewEmail);

// Handler: Executes the action (with side effects)
public class UpdateUserEmailCommandHandler : ICommandHandler<UpdateUserEmailCommand>
{
    private readonly IUserRepository _userRepo;

    public UpdateUserEmailCommandHandler(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }

    public async Task Handle(UpdateUserEmailCommand command)
    {
        var user = await _userRepo.GetByIdAsync(command.UserId);
        if (user == null)
            throw new UserNotFoundException(command.UserId);

        user.UpdateEmail(command.NewEmail);
        await _userRepo.SaveAsync(user); // Side effect: writes to DB
    }
}

Query/QueryHandler Example

// Query: Defines what data to fetch
public record GetUserByIdQuery(Guid UserId);

// Handler: Fetches and returns data (no side effects)
public class GetUserByIdQueryHandler : IQueryHandler<GetUserByIdQuery, UserDto>
{
    private readonly IUserRepository _userRepo;

    public GetUserByIdQueryHandler(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }

    public async Task<UserDto> Handle(GetUserByIdQuery query)
    {
        var user = await _userRepo.GetByIdAsync(query.UserId);
        if (user == null)
            throw new UserNotFoundException(query.UserId);

        // Map domain user to DTO for output
        return new UserDto(user.Id, user.Email, user.FullName);
    }
}

public record UserDto(Guid Id, string Email, string FullName);

Wrapping Up

To put it simply:

  • Use Command/CommandHandler when you want to do something to your system.
  • Use Query/QueryHandler when you want to ask something about your system.

Separating these concerns (via CQRS) makes your code more maintainable, easier to test, and gives you flexibility to optimize read and write paths independently (like using a read-only replica for queries).

内容的提问来源于stack exchange,提问作者oddyeti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:28:10