質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.12%

【MVC】コントローラとサービスの切り分け

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 2,628

HelloWorld2

score 24

わからないこと

  • ControllerとServiceの切り分けが分かりません。
  • 下記に簡単な例を示しましたので、どの方法がベストか、他の方法が良いか、ご教示いただきたいです。
  • (そもそもRepositoryを直接コントローラから実行するのって普通ですか?Serviceを経由する必要はないですか?)

処理概要(細かいところは省きます)

  1. テーブルaのデータを取得
  2. 取得できなかったら処理終了
  3. テーブルbのデータ取得
  4. DTOにaとbのデータをセット

コード

※現在springを使用しているため、javaおよびspringで記載します。

パターン1:全部Serviceで処理(同一のDTOを構成する処理はまとめるという思想、ただ関連テーブルが増えるとserviceのメソッド内の処理が膨大になります・・・)

@Controller
public Class hogeController{
    public hogeDto search(){
        return hogeService.getDto();
    }
}

@Service
public Class hogeService{
    public hogeDto getDto(){
        aEntity = aRepogitory.findOne(1);
        if( aEntity == null ){
            return null;
        }
        bEntity = bRepogitory.findOne(1);

        hogeDto.setHoge(aEntity.hoge);
        hogeDto.setFuge(bEntity.fuga);

        return hogeDto;
    }
}

パターン2:全部Controllerで処理(多分この程度の処理なら許容されそうですけど、もっと膨大になったらだめですよね・・・?)

@Controller
public Class hogeController{
    public hogeDto search(){
        aEntity = aRepogitory.findOne(1);
        if( aEntity == null ){
            return null;
        }
        bEntity = bRepogitory.findOne(1);

        hogeDto.setHoge(aEntity.hoge);
        hogeDto.setFuge(bEntity.fuga);

        return hogeDto;
    }
}

パターン3:値のセットはControllerで処理、後はServiceで実装(データ操作はすべてServiceを経由、データ操作に不随してロジックもついてたらこれですかね・・・・?)

@Controller
public Class hogeController{
    public hogeDto search(){
        aEntity = hogeService.getA();
        if( aEntity == null ){
            return null;
        }
        bEntity = hogeService.getB()
        hogeDto.setHoge(aEntity.hoge);
        hogeDto.setFuge(bEntity.fuga);
        return hogeDto;
    }
}

@Service
public Class hogeService{
    public hogeDto getA(){
        aEntity = aRepogitory.findOne(1);
        if( aEntity == null ){
            return null;
        }

        return aEntity;
    }

    public hogeDto getB(){
        return bRepogitory.findOne(1);
    }
}

どうあるべきか、ご意見お聞かせいただけたら幸いです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+2

MVCというよりSpringMVCについての回答となりますが、
コントローラからServiceクラス(@Serviceアノテーション付加)のメソッドを呼び出した際にトランザクションが開始されますので、
トランザクション管理したいものをServiceクラスに実装し、不要なものはコントローラ層で実装すればよいです。

1.テーブルaのデータを取得
3.テーブルbのデータ取得

とありますが、これは同一トランザクションで取得すべき内容でしたら
両方とも同一Serviceクラス(の同一メソッド)内に記載すべきです。
※SharedServiceを利用して分割する方法もありますが割愛します

以下が参考になるかと。
http://terasolunaorg.github.io/guideline/5.5.1.RELEASE/ja/ImplementationAtEachLayer/DomainLayer.html

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

プログラミングの世界には「ファットコントローラ」という概念があります。
ファットコントローラ撲滅運動というスライドが作られるくらい、忌み嫌われるものです。

そもそもオブジェクト指向では「役割分担」をきちんとすることが理想とされています。はじめのうちは何でもかんでもコントローラに全て書いてしまいがちです。
ただ、それでは可読性もメンテナンス性も悪くなります。

そのために細かい役割分担をすることで「道具を用意して適宜使う」ような形にするわけですね。
1個軽い道具を作れば他でも使いまわしがききます。

サービスとは・・でいうと使っているフレームワークや言語、もしかしたら現場で概念がちょっとずつ違うかもしれませんが、一般的には下記に現れるようなものと思っていいと思います。

基本はビジネスロジック部分を切り出したもの・・・という理解ですね。
コントローラはあくまでリクエストを受けて、レスポンスを返すだけに終始し、その間の処理部分はサービスに任せる。

どこまでどう切り出すかは状況次第とは思います。
サービスもあまりコントローラに特化したものでないほうが良いとは思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/23 17:26

    ご回答ありがとうございます。

    > どこまでどう切り出すかは状況次第
    とのことですが、基本形・理想形はどういった形式で、どういう状況で変化するのでしょうか?

    > ファットコントローラ
    パターン2は基本形でないことはわかりました。

    また、コントローラから(サービスを経由せず)リポジトリを呼び出すことは問題ないのでしょうか?

    キャンセル

  • 2019/08/23 18:18

    デザインパターン
    で調べてください。

    キャンセル

+1

>とのことですが、基本形・理想形はどういった形式で、どういう状況で変化するのでしょうか?

理想形かどうかはわかりませんが、一つの指針ならあげられますので参考にしてみてください。

各クラスの役割

  • コントローラークラス
    画面からのイベントを受け取ってサービスを呼び出す
  • サービスクラス
    リポジトリを呼び出してデータの取得、ドメインクラスを呼び出す
  • ドメインクラス
    業務のデータの保持と、ルールを記載するクラス
    リポジトリは呼び出さずに業務ルールだけに専念する

流れ

サービスは言い換えるとユースケースと言えます。
例えば自動販売機でジュースを買う場合、以下のように考えられます。

  • コントローラ
    「自販機のボタンを押す」というイベントをうけて
    自販機サービスの「ジュースを買う」を呼びだす
  • サービス
    ジュースの情報(ドメイン)を取得
  • ドメイン
    金額やジュースの残量、冷え具合などの状態を持っているので、
    「まだジュースは残ってるか?」
    「冷えて出せる状態か?」
    「投入されたお金は本物か?」
    「金額は足りてるか?」
    などのルールを判定する
    また、ルールには判定だけじゃなくて、このジュースの場合は
    シールをつけるみたいな業務ルールのロジックも組み込みます
    ※自販機ではそういうことはないですが
  • サービス
    ドメインのルール判定の結果、問題なければコントローラーに結果を返す
  • コントローラー
    サービスの結果をもとにジュースを出す

設計方法

作る前に上記のルールを考えておけば、設計段階で

  • 画面からのイベント
  • ユースケース
  • 業務ルール

以上を押さえた資料を作成すればバックエンドのプログラムは作れると思います。

その他

サービスクラスがオブジェクト指向っぽくなりませんが、
そこは仕方ないと割り切って作ります。
例えば、「自動販売機サービス」なのに持っているメソッドは「ジュースを買う」っておかしな感じになっちゃってます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る