1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【小さく始める設計③】拡張メソッドで責務を分離するという選択 〜Model を肥大化させないために〜

Last updated at Posted at 2025-12-19

はじめに

この記事は、小規模な Web アプリケーション開発において、
私が実際に採用している設計の考え方
を整理した連載の一部です。

大規模アーキテクチャや厳密な DDD を前提にせず、
「まずはシンプルに作り、必要になったら分ける」設計を重視しています。

この記事では、C# の拡張メソッドを使って
Entity / Model の責務を Command / Query / Utility に分ける考え方と実装例を紹介します。


1. 拡張メソッドの基本的な考え方

  • EntityやModel自体に処理を直接書かず、拡張メソッドとして責務を分離
  • namespaceやフォルダで整理することで、アクセス制御や依存関係を明確化
  • 小規模案件でも保守性・理解性を向上

2. OrderEntity の拡張メソッド例

namespace Entity.Query;
public static class OrderQuery
{
    // Query: データ取得
    public static Order GetById(this DbContext db, int id)
    {
        return db.Orders.FirstOrDefault(o => o.Id == id);
    }
}
namespace Entity.Command;
public static class OrderCommand
{
    // Command: データ更新
    public static void UpdateStatus(this DbContext db, Order request)
    {
        request.Validate(); // 失敗したら例外
        
        // 実案件では要件に応じて追加検討
        
        var target = db.GetById(request.Id);
        target.Status = request.Status;
        db.SaveChanges();
    }

    //note: 例では void ですが、成功/失敗の bool を返す場面も多いです
}

※ Commandでは、DTOや画面入力をそのまま渡すのではなく
  「更新したい状態を持ったEntity」を引数にすることで
  呼び出し側の責務を最小限にしています。

namespace Entity.Utility;
public static class OrderUtility
{
    // DTO変換
    public static OrderDto ToDto(this Order order)
    {
        return new OrderDto
        {
            Id = order.Id,
            Status = order.Status,
            Amount = order.Amount
        };
    }

    // バリデーション(例外版)
    public static void Validate(this Order order)
    {
        if (order == null)
            throw new ArgumentNullException(nameof(order), "Order is null.");
    
        if (order.Id <= 0)
            throw new ArgumentException("Invalid Order Id.", nameof(order.Id));
    
        if (string.IsNullOrWhiteSpace(order.Status))
            throw new ArgumentException("Status is required.", nameof(order.Status));
    
        if (order.Amount < 0)
            throw new ArgumentException("Amount cannot be negative.", nameof(order.Amount));
    }

}

※ 現状は例外を投げるスタイルですが、
  戻り値やエラーリストでの検証することもあります

バリデーション(エラーリスト版)

    // バリデーション(エラーリスト版)
  public static bool Validate(this Order order, out List<string> errors)
  {
      errors = new List<string>();

      if (order == null)
          errors.Add("Order is null.");

      if (order.Id <= 0)
          errors.Add("Invalid Order Id.");

      if (string.IsNullOrWhiteSpace(order.Status))
          errors.Add("Status is required.");

      if (order.Amount < 0)
          errors.Add("Amount cannot be negative.");

      return errors.Count == 0;
  }

3. Command / Query / Utility の分け方

種類 主な役割 記述例
Query データ取得、参照専用 GetById()
Command データ更新・登録・削除 UpdateStatus()
Utility DTO変換や便利メソッド ToDto(), Validate()

ポイント

  • 同じEntityでも、参照用処理と更新処理を明確に分ける
  • Utilityは副作用なしで純粋にデータ変換や計算のみ
  • namespaceやフォルダで分けると、間違ってCommandをQuery側で呼んでしまうことを防げる
  • Validate は例外を投げる実装例を示していますが、戻り値で成否を返す方法や、エラーリストを返す方法も可能です

4. namespaceでの責務分離

[ Entity Project ]
  ├─ Entity
  ├─ Entity.Command
  ├─ Entity.Query
  └─ Entity.Utility
  • Command/Query/Utilityでnamespaceを分ける
  • これにより、開発者が参照できる範囲を制限できる
  • 新しい概念やドメインロジックを追加する場合も、namespace単位で整理可能

5. メリットと注意点

観点 メリット デメリット / 注意点
Entityの責務 Entity本体を薄く保てる 本来Entityに含めるべきロジックが分散しすぎる可能性
コードの見通し Command / Query / Utility で整理され、役割が明確 namespaceやフォルダ構成が複雑になると逆に迷いやすい
小規模案件での効果 シンプルに始められ、保守性が向上 チームメンバーが慣れていないと理解コストが増える
拡張メソッドの利点 既存クラスを変更せず機能追加できる 過度に利用すると「どこに処理があるか」追いづらい
移行性 将来的にService層やUseCase層へ切り出しやすい 大規模化すると責務境界が曖昧になり、再設計が必要
バリデーション 例外版・エラーリスト版など柔軟に選べる 実装スタイルが統一されないと混乱を招く

6. まとめ

  • 拡張メソッドを使った責務分離は、小規模C# MVC設計でも有効
  • Command / Query / Utility に整理し、namespaceでアクセス制御
  • 将来的な層追加(UseCase層やService層)にも柔軟に対応可能

💡 ポイント

  • Query: 読み取り専用
  • Command: データ変更
  • Utility: 純粋関数的な処理
  • namespaceで責務を分けることが保守性のカギ
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?