jpskill.com
🛠️ 開発・MCP コミュニティ

cqs-patterns

Command Query Separation (CQS) and CQRS patterns for .NET. Use when designing methods, handlers, and application architecture. Ensures predictable, testable code.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o cqs-patterns.zip https://jpskill.com/download/18838.zip && unzip -o cqs-patterns.zip && rm cqs-patterns.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/18838.zip -OutFile "$d\cqs-patterns.zip"; Expand-Archive "$d\cqs-patterns.zip" -DestinationPath $d -Force; ri "$d\cqs-patterns.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して cqs-patterns.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → cqs-patterns フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] cqs-patterns

コマンドクエリ分離 (CQS)

原則

メソッドは、アクションを実行するコマンドであるか、データを返すクエリであるかのどちらかであるべきであり、両方であってはいけません。

┌─────────────────────────────────────────────────────────┐
│                        メソッド                         │
├───────────────────────┬─────────────────────────────────┤
│       コマンド        │            クエリ               │
├───────────────────────┼─────────────────────────────────┤
│ - 状態を変更する      │ - データを返す                  │
│ - voidを返す          │ - 副作用がない                  │
│ - 「何かをする」      │ - 「何かを教えて」              │
│ - 命令形動詞          │ - 名詞または疑問形              │
│   (Create, Update,    │   (Get, Find, Is, Has,          │
│    Delete, Process)   │    Calculate, Count)            │
└───────────────────────┴─────────────────────────────────┘

メソッドレベルでのCQS

違反例

// BAD: メソッドが状態を変更し、かつデータを返しています
public class Stack<T>
{
    public T Pop()  // アイテムを削除し、かつそれを返しています
    {
        var item = _items[_count - 1];
        _count--;
        return item;
    }
}

// BAD: 副作用のあるゲッター
public class Counter
{
    private int _value;

    public int Value
    {
        get { return _value++; }  // 状態を変更するクエリです!
    }
}

// BAD: コマンドがデータを返しています
public class UserService
{
    public User CreateUser(string email, string name)  // 作成されたユーザーを返しています
    {
        var user = new User { Email = email, Name = name };
        _repository.Add(user);
        return user;  // CQS違反です
    }
}

正しいCQSの実装

// GOOD: コマンドとクエリが分離されています
public class Stack<T>
{
    // クエリ - データを返し、副作用はありません
    public T Peek()
    {
        if (_count == 0)
            throw new InvalidOperationException("Stack is empty");
        return _items[_count - 1];
    }

    // コマンド - 状態を変更し、voidを返します
    public void Pop()
    {
        if (_count == 0)
            throw new InvalidOperationException("Stack is empty");
        _count--;
    }

    // 使用法: 呼び出しを分離します
    var top = stack.Peek();
    stack.Pop();
}

// GOOD: 純粋なクエリを持つカウンター
public class Counter
{
    private int _value;

    public int Value => _value;  // 純粋なクエリ

    public void Increment() => _value++;  // コマンド
}

// GOOD: CQSを持つユーザーサービス
public class UserService
{
    // コマンド - ユーザーを作成し、識別子のみを返します
    public Guid CreateUser(string email, string name)
    {
        var userId = Guid.NewGuid();
        var user = new User { Id = userId, Email = email, Name = name };
        _repository.Add(user);
        return userId;  // IDを返すことは許容されます
    }

    // クエリ - ユーザーを取得します
    public User? GetUser(Guid userId)
    {
        return _repository.GetById(userId);
    }
}

CQSの例外

コマンドとクエリの組み合わせが正当化される状況がいくつかあります。

1. アトミック操作

// 許容されます: Interlocked操作はアトミックである必要があります
public int IncrementAndGet()
{
    return Interlocked.Increment(ref _counter);
}

// 許容されます: Compare-and-swapパターン
public bool TryUpdate(int expected, int newValue)
{
    return Interlocked.CompareExchange(ref _value, newValue, expected) == expected;
}

2. Fluent API

// 許容されます: ビルダーパターンはthisを返します
public class QueryBuilder
{
    public QueryBuilder Where(string condition)
    {
        _conditions.Add(condition);
        return this;  // 変更されたビルダーを返します
    }

    public QueryBuilder OrderBy(string column)
    {
        _orderBy = column;
        return this;
    }
}

3. ファクトリメソッド

// 許容されます: 生成は作成されたオブジェクトを返します
public static Order Create(int customerId, IEnumerable<OrderItem> items)
{
    return new Order
    {
        Id = Guid.NewGuid(),
        CustomerId = customerId,
        Items = items.ToList()
    };
}

CQRS - コマンドクエリ責務分離

CQRSは、読み取りと書き込みに別々のモデルを使用することで、アーキテクチャレベルでCQSを適用します。

                    ┌─────────────────────────────────────┐
                    │           アプリケーション          │
                    └─────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │   コマンド    │               │    クエリ     │
           │    (書き込み) │               │    (読み取り) │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ コマンド      │               │ クエリ        │
           │ ハンドラー    │               │ ハンドラー    │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ ドメインモデル│               │ 読み取りモデル│
           │ (リッチ)      │               │ (DTO)         │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ 書き込みDB    │──────────────▶│ 読み取りDB    │
           │ (正規化)      │  同期/イベント  │ (最適化)      │
           └───────────────┘               └───────────────┘

コマンドとクエリ

// === マーカーインターフェース ===

public interface ICommand { }

public interface ICommand<TResult> { }

public interface IQuery<TResult> { }

// === コマンド ===

public rec
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Command Query Separation (CQS)

The Principle

A method should either be a Command that performs an action, or a Query that returns data, but not both.

┌─────────────────────────────────────────────────────────┐
│                        METHOD                           │
├───────────────────────┬─────────────────────────────────┤
│       COMMAND         │            QUERY                │
├───────────────────────┼─────────────────────────────────┤
│ - Changes state       │ - Returns data                  │
│ - Returns void        │ - No side effects               │
│ - "Do something"      │ - "Tell me something"           │
│ - Imperative verbs    │ - Noun or question              │
│   (Create, Update,    │   (Get, Find, Is, Has,          │
│    Delete, Process)   │    Calculate, Count)            │
└───────────────────────┴─────────────────────────────────┘

CQS at Method Level

Violation Examples

// BAD: Method both modifies state AND returns data
public class Stack<T>
{
    public T Pop()  // Both removes item AND returns it
    {
        var item = _items[_count - 1];
        _count--;
        return item;
    }
}

// BAD: Getter with side effects
public class Counter
{
    private int _value;

    public int Value
    {
        get { return _value++; }  // Query that modifies state!
    }
}

// BAD: Command returns data
public class UserService
{
    public User CreateUser(string email, string name)  // Returns created user
    {
        var user = new User { Email = email, Name = name };
        _repository.Add(user);
        return user;  // CQS violation
    }
}

Correct CQS Implementation

// GOOD: Separated commands and queries
public class Stack<T>
{
    // Query - returns data, no side effects
    public T Peek()
    {
        if (_count == 0)
            throw new InvalidOperationException("Stack is empty");
        return _items[_count - 1];
    }

    // Command - modifies state, returns void
    public void Pop()
    {
        if (_count == 0)
            throw new InvalidOperationException("Stack is empty");
        _count--;
    }

    // Usage: separate calls
    var top = stack.Peek();
    stack.Pop();
}

// GOOD: Counter with pure query
public class Counter
{
    private int _value;

    public int Value => _value;  // Pure query

    public void Increment() => _value++;  // Command
}

// GOOD: User service with CQS
public class UserService
{
    // Command - creates user, returns identifier only
    public Guid CreateUser(string email, string name)
    {
        var userId = Guid.NewGuid();
        var user = new User { Id = userId, Email = email, Name = name };
        _repository.Add(user);
        return userId;  // Returning ID is acceptable
    }

    // Query - retrieves user
    public User? GetUser(Guid userId)
    {
        return _repository.GetById(userId);
    }
}

CQS Exceptions

Some situations justify combining command and query:

1. Atomic Operations

// Acceptable: Interlocked operations need to be atomic
public int IncrementAndGet()
{
    return Interlocked.Increment(ref _counter);
}

// Acceptable: Compare-and-swap patterns
public bool TryUpdate(int expected, int newValue)
{
    return Interlocked.CompareExchange(ref _value, newValue, expected) == expected;
}

2. Fluent APIs

// Acceptable: Builder pattern returns this
public class QueryBuilder
{
    public QueryBuilder Where(string condition)
    {
        _conditions.Add(condition);
        return this;  // Returns modified builder
    }

    public QueryBuilder OrderBy(string column)
    {
        _orderBy = column;
        return this;
    }
}

3. Factory Methods

// Acceptable: Creation returns the created object
public static Order Create(int customerId, IEnumerable<OrderItem> items)
{
    return new Order
    {
        Id = Guid.NewGuid(),
        CustomerId = customerId,
        Items = items.ToList()
    };
}

CQRS - Command Query Responsibility Segregation

CQRS applies CQS at the architectural level, using separate models for reads and writes.

                    ┌─────────────────────────────────────┐
                    │           APPLICATION               │
                    └─────────────────────────────────────┘
                                    │
                    ┌───────────────┴───────────────┐
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │   COMMANDS    │               │    QUERIES    │
           │    (Write)    │               │    (Read)     │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ Command       │               │ Query         │
           │ Handlers      │               │ Handlers      │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ Domain Model  │               │ Read Model    │
           │ (Rich)        │               │ (DTOs)        │
           └───────────────┘               └───────────────┘
                    │                               │
                    ▼                               ▼
           ┌───────────────┐               ┌───────────────┐
           │ Write DB      │──────────────▶│ Read DB       │
           │ (Normalized)  │  Sync/Events  │ (Optimized)   │
           └───────────────┘               └───────────────┘

Commands and Queries

// === MARKER INTERFACES ===

public interface ICommand { }

public interface ICommand<TResult> { }

public interface IQuery<TResult> { }

// === COMMANDS ===

public record CreateOrderCommand(
    int CustomerId,
    List<OrderItemDto> Items
) : ICommand<Guid>;

public record UpdateOrderStatusCommand(
    Guid OrderId,
    OrderStatus NewStatus
) : ICommand;

public record CancelOrderCommand(Guid OrderId) : ICommand;

// === QUERIES ===

public record GetOrderByIdQuery(Guid OrderId) : IQuery<OrderDto?>;

public record GetOrdersByCustomerQuery(
    int CustomerId,
    int Page = 1,
    int PageSize = 10
) : IQuery<PagedResult<OrderSummaryDto>>;

public record GetOrderStatisticsQuery(
    DateTime From,
    DateTime To
) : IQuery<OrderStatisticsDto>;

Command Handlers

// === HANDLER INTERFACES ===

public interface ICommandHandler<in TCommand> where TCommand : ICommand
{
    Task HandleAsync(TCommand command, CancellationToken ct = default);
}

public interface ICommandHandler<in TCommand, TResult> where TCommand : ICommand<TResult>
{
    Task<TResult> HandleAsync(TCommand command, CancellationToken ct = default);
}

// === IMPLEMENTATION ===

public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand, Guid>
{
    private readonly IOrderRepository _orderRepository;
    private readonly ICustomerRepository _customerRepository;
    private readonly IEventPublisher _eventPublisher;

    public CreateOrderCommandHandler(
        IOrderRepository orderRepository,
        ICustomerRepository customerRepository,
        IEventPublisher eventPublisher)
    {
        _orderRepository = orderRepository;
        _customerRepository = customerRepository;
        _eventPublisher = eventPublisher;
    }

    public async Task<Guid> HandleAsync(CreateOrderCommand command, CancellationToken ct)
    {
        // Validate
        var customer = await _customerRepository.GetByIdAsync(command.CustomerId, ct);
        if (customer == null)
            throw new CustomerNotFoundException(command.CustomerId);

        // Create domain entity
        var order = Order.Create(command.CustomerId);
        foreach (var item in command.Items)
        {
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        }

        // Persist
        await _orderRepository.AddAsync(order, ct);

        // Publish domain event
        await _eventPublisher.PublishAsync(new OrderCreatedEvent(order.Id), ct);

        return order.Id;
    }
}

public class CancelOrderCommandHandler : ICommandHandler<CancelOrderCommand>
{
    private readonly IOrderRepository _orderRepository;
    private readonly IEventPublisher _eventPublisher;

    public async Task HandleAsync(CancelOrderCommand command, CancellationToken ct)
    {
        var order = await _orderRepository.GetByIdAsync(command.OrderId, ct);
        if (order == null)
            throw new OrderNotFoundException(command.OrderId);

        order.Cancel();

        await _orderRepository.UpdateAsync(order, ct);
        await _eventPublisher.PublishAsync(new OrderCancelledEvent(order.Id), ct);
    }
}

Query Handlers

public interface IQueryHandler<in TQuery, TResult> where TQuery : IQuery<TResult>
{
    Task<TResult> HandleAsync(TQuery query, CancellationToken ct = default);
}

public class GetOrderByIdQueryHandler : IQueryHandler<GetOrderByIdQuery, OrderDto?>
{
    private readonly IDbConnection _connection;

    public GetOrderByIdQueryHandler(IDbConnection connection)
    {
        _connection = connection;
    }

    public async Task<OrderDto?> HandleAsync(GetOrderByIdQuery query, CancellationToken ct)
    {
        // Direct SQL for optimized reads
        const string sql = @"
            SELECT o.Id, o.CustomerId, o.Status, o.Total, o.CreatedAt,
                   c.Name as CustomerName, c.Email as CustomerEmail
            FROM Orders o
            JOIN Customers c ON o.CustomerId = c.Id
            WHERE o.Id = @OrderId";

        return await _connection.QuerySingleOrDefaultAsync<OrderDto>(
            new CommandDefinition(sql, new { query.OrderId }, cancellationToken: ct));
    }
}

public class GetOrdersByCustomerQueryHandler
    : IQueryHandler<GetOrdersByCustomerQuery, PagedResult<OrderSummaryDto>>
{
    private readonly IDbConnection _connection;

    public async Task<PagedResult<OrderSummaryDto>> HandleAsync(
        GetOrdersByCustomerQuery query,
        CancellationToken ct)
    {
        const string sql = @"
            SELECT Id, Status, Total, CreatedAt
            FROM Orders
            WHERE CustomerId = @CustomerId
            ORDER BY CreatedAt DESC
            OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY;

            SELECT COUNT(*) FROM Orders WHERE CustomerId = @CustomerId;";

        using var multi = await _connection.QueryMultipleAsync(
            new CommandDefinition(sql,
                new
                {
                    query.CustomerId,
                    Offset = (query.Page - 1) * query.PageSize,
                    query.PageSize
                },
                cancellationToken: ct));

        var items = (await multi.ReadAsync<OrderSummaryDto>()).ToList();
        var totalCount = await multi.ReadSingleAsync<int>();

        return new PagedResult<OrderSummaryDto>(items, query.Page, query.PageSize, totalCount);
    }
}

Dispatcher Pattern

public interface IDispatcher
{
    Task<TResult> SendAsync<TResult>(ICommand<TResult> command, CancellationToken ct = default);
    Task SendAsync(ICommand command, CancellationToken ct = default);
    Task<TResult> QueryAsync<TResult>(IQuery<TResult> query, CancellationToken ct = default);
}

public class Dispatcher : IDispatcher
{
    private readonly IServiceProvider _serviceProvider;

    public Dispatcher(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task<TResult> SendAsync<TResult>(ICommand<TResult> command, CancellationToken ct)
    {
        var handlerType = typeof(ICommandHandler<,>).MakeGenericType(command.GetType(), typeof(TResult));
        dynamic handler = _serviceProvider.GetRequiredService(handlerType);
        return await handler.HandleAsync((dynamic)command, ct);
    }

    public async Task SendAsync(ICommand command, CancellationToken ct)
    {
        var handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
        dynamic handler = _serviceProvider.GetRequiredService(handlerType);
        await handler.HandleAsync((dynamic)command, ct);
    }

    public async Task<TResult> QueryAsync<TResult>(IQuery<TResult> query, CancellationToken ct)
    {
        var handlerType = typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
        dynamic handler = _serviceProvider.GetRequiredService(handlerType);
        return await handler.HandleAsync((dynamic)query, ct);
    }
}

Usage in Controllers

[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IDispatcher _dispatcher;

    public OrdersController(IDispatcher dispatcher)
    {
        _dispatcher = dispatcher;
    }

    [HttpPost]
    public async Task<ActionResult<Guid>> CreateOrder(
        CreateOrderRequest request,
        CancellationToken ct)
    {
        var command = new CreateOrderCommand(request.CustomerId, request.Items);
        var orderId = await _dispatcher.SendAsync(command, ct);
        return CreatedAtAction(nameof(GetOrder), new { id = orderId }, orderId);
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<OrderDto>> GetOrder(Guid id, CancellationToken ct)
    {
        var query = new GetOrderByIdQuery(id);
        var order = await _dispatcher.QueryAsync(query, ct);
        return order == null ? NotFound() : Ok(order);
    }

    [HttpPost("{id}/cancel")]
    public async Task<IActionResult> CancelOrder(Guid id, CancellationToken ct)
    {
        var command = new CancelOrderCommand(id);
        await _dispatcher.SendAsync(command, ct);
        return NoContent();
    }
}

Benefits of CQS/CQRS

Benefit Description
Testability Commands and queries are isolated, easy to test
Scalability Read and write sides can scale independently
Optimization Read models optimized for queries, write models for business rules
Clarity Clear intent - methods either change state or return data
Maintainability Single responsibility, easier to modify
Debugging Queries are safe to call repeatedly

When to Use CQRS

Good Fit

  • Complex domains with different read/write patterns
  • High-read, low-write scenarios
  • Systems requiring audit trails
  • Event-sourced systems
  • Microservices with separate read replicas

Not Needed

  • Simple CRUD applications
  • Small teams/projects
  • Consistent read/write patterns
  • When added complexity outweighs benefits

Quick Reference

// Pure Query - Safe, no side effects
public Customer? GetCustomer(int id);
public IReadOnlyList<Order> FindOrdersByStatus(OrderStatus status);
public bool IsEmailAvailable(string email);
public int CountActiveUsers();
public decimal CalculateTotalRevenue(DateTime from, DateTime to);

// Pure Command - Changes state, returns void (or ID)
public void CreateCustomer(Customer customer);
public void UpdateOrderStatus(Guid orderId, OrderStatus status);
public void DeleteProduct(int productId);
public Guid PlaceOrder(OrderRequest request);  // ID return is acceptable