「クラスの責務」を意識するようになった話

「クラスの責務」を意識するようになった話

公開: 2025年12月17日

学習振り返り
要約
  • 最近のプロジェクトではレイヤードアーキテクチャに基づき、設計から実装までの流れを経験している。
  • 各レイヤーの責務を明確にし、特に業務処理の統括、入力チェック、トランザクション管理を重視している。
  • シーケンス図を用いて設計意図を明確にし、AIを活用した迅速な実装が可能なサービスクラスを目指している。
音声で記事を再生
0:00

はじめに

最近は設計してからレビューしてもらって、実装という流れでタスクをしています。(この限りではないですが)
私の関わっているプロジェクトではレイヤードアーキテクチャを採用していて、MVCは当然のこと、クラスの設計や責務の範囲は厳しくレビューしてもらいます。

Railsを触り始めて半年くらい経って、やっと「書ける」と「考える」が別物だと感じるようになりました。

まだ設計論を語れるほどではないですが実装していて「これはここに置きたくないな」と思う瞬間は、確実に増えてきています。

私がRailsで業務処理を設計するときに、何を基準にクラスを分けているかをサンプルを使って整理してみます。

シーケンス図

設計する時にはNotionにシーケンス図を書くのですが、最近設計したものをかなりシンプルに抽象化して設計意図は残したサンプルを作りました。

sequenceDiagram
    autonumber

    participant Entry as 入口<br/>(Rake / Controller)
    participant Orchestrator as 業務処理の統括<br/>(Application Service)
    participant Validator as 入力チェック<br/>(Form object)
    participant TX as トランザクション<br/>(Application Service内)
    participant Domain as 業務処理<br/>(Domain Services)
    participant Job as 非同期ジョブ<br/>(Sidekiq)
    participant Worker as 実処理<br/>(Worker)

    %% ------------------------------
    %% 入口
    %% ------------------------------
    Entry->>Orchestrator: 処理を開始(input)

    %% ------------------------------
    %% 入力チェック
    %% ------------------------------
    Orchestrator->>Validator: 入力をチェック
    alt 不正な入力
        Validator-->>Orchestrator: エラー
        Orchestrator-->>Entry: 処理失敗
    end

    %% ------------------------------
    %% トランザクション
    %% ------------------------------
    Orchestrator->>TX: トランザクション開始

    TX->>Domain: 業務データを作成・更新
    Domain-->>TX: 作成結果

    TX-->>Orchestrator: コミット完了

    %% ------------------------------
    %% 非同期処理(副作用)
    %% ------------------------------
    Orchestrator->>Job: 非同期処理を登録(IDs)

    %% ------------------------------
    %% Sidekiq
    %% ------------------------------
    Job->>Worker: 処理を実行
    Worker->>Worker: 外部IO / 時間のかかる処理
    Worker-->>Job: 完了

    %% ------------------------------
    %% 返却
    %% ------------------------------
    Orchestrator-->>Entry: 処理成功

設計のポイント

入り口

controllerやrakeに当たる処理の入り口部分です。
ここはほんとに出入り口としてしか使わず、それ以上の責務は持たせません。

薄く薄くを意識し、いわゆる Fat Controller を避けています。
ただ現実はデータ取得して返すだけだったらここで終わることはあります。

業務処理の統括

複数のモデルのデータを操作するとか業務ロジックが複雑で複数のサービスクラスを使うときはOrchestratorとしてのサービスを作成します。

  • validation

  • transaction

  • 非同期実行のトリガー

これらの処理の順番や組み合わせを知っているのは「業務処理の統括」のレイヤーの責務だけにしています。

入力チェック

バリデーションするレイヤーはFormクラスを作成します。

ここで渡ってきたデータが業務ロジックに入って問題ないか、データのあるべき前提条件を満たしているかをバリデーションします。

トランザクション

トランザクションはOrchestratorの中で行います。

何がトランザクションの対象になるかはしっかり考えて、非同期処理や並行、並列の処理についてはトランザクションの外に出します。(DBロックされるので時間かかる系は含めない)

また、万一外したデータ操作が失敗した場合はすでに登録されているデータをどうするかは慎重に考えなければいけないと思います。
最近は論理削除で対応しました。

業務処理

ここは「業務フロー」は知らないで成立するように、個別の業務処理だけに集中できるよう設計します。
例えば、商品の登録をするだけのクラスのようなイメージです。

永続化の詳細は意識せず1つの関心事に閉じるようにします。

非同期ジョブ

時間がかかる(bulk_importや動画のアップロード等)操作は非同期処理などのside effectにしないとレスポンス返すまでの時間がかかりすぎてレイテンシーが上昇してしまいます。

非同期にする場合はOrchestratorからキューに入れるだけにして、Job 側には処理の流れを持たせません。

実処理

成功、失敗はユーザーにどう知らせるか考えないといけないです。

メールで通知する、slackで通知する、実行中、実行完了などのstatusの管理(カラムを用意しないといけないかも等)など、他にも考えること出てきますね、、
それら全て終わったらやっと全処理終了です!(非同期だったら待ってないけど)


おわりに

この処理はどのレイヤーの責務か?悩むこともありますが、ModelやControllerははっきりわかってきて、これから設計に強くなりAI使って爆速実装していきたいです。

シーケンス図で細かくサービスクラスの名前や何をそのクラスでするかまで書いておけばAIに投げたら結構すぐに綺麗めなコード出てきて嬉しいです。

業務フローを知らないサービスクラスはほんとに使いまわしやすくて便利さも感じていますし、テストもしやすいので、運用し続けることに耐えられる設計を私も意識して仕事していきたいです。

シェア!

XThreads
ShiftB Logo
user
吉本茜
山口在住/二児の母/エンジニア
Loading...
記事一覧に戻る
XThreads
0