1. test 1
  2. test 2
  3. test 3
  4. test 4
  5. test 5
  6. test 6
コース一覧
Spring Boot入門
@Serviceに置くもの

Spring Boot入門

Java で Web API サーバーを作るための Spring Boot の入門コースです。DI コンテナ、REST コントローラ、リクエスト検証、レイヤ分割、Spring Data JPA、例外ハンドリング、テストまでを扱います。毎レッスン、Spring が裏側でやっている判断そのものを pure Java の関数として自分で書き、フレームワークが何を肩代わりしてくれているのかを手触りで理解できる構成にしました。約 12 時間 (1 日 30 分 × 24 日) で 48 レッスンを修了できます。

1
Spring Bootとは何か
01. Spring Bootで何が作れるのか10分
02. サーブレットからフレームワークへ15分
03. ルーティング表という発想15分
04. プロジェクトの構成10分
05. 起動のしくみと自動設定15分
06. パスのマッチング15分
2
DIコンテナ
01. newで書くと何が困るのか15分
02. コンストラクタインジェクション15分
03. インターフェースへの依存15分
04. @Componentと@Bean15分
05. 自作のDIコンテナ20分
06. Beanのスコープとライフサイクル15分
3
RESTコントローラ
01. RESTという設計方針15分
02. @RestControllerの役割15分
03. @GetMappingとパス変数15分
04. クエリパラメータの受け取り15分
05. @PostMappingとリクエストボディ15分
06. ステータスコードの選び方15分
07. ResponseEntityで組み立てる15分
4
リクエストとレスポンスの変換
01. JSONとオブジェクトの往復15分
02. DTOとエンティティを分ける15分
03. recordでDTOを書く15分
04. 入力検証の基本15分
05. 検証エラーをまとめて返す15分
06. アノテーションによる検証15分
5
レイヤ構成とビジネスロジック
01. 3層アーキテクチャ10分
02. @Serviceに置くもの15分
03. 状態遷移をルールにする15分
04. 料金計算のような業務ロジック15分
05. @Transactionalとは何か15分
06. トランザクションの巻き戻し15分
6
データアクセス
01. @Entityとテーブルの対応15分
02. リポジトリという抽象15分
03. メソッド名からクエリを作る20分
04. 検索条件の組み立て15分
05. ページングと並び替え15分
06. N+1問題15分
7
例外処理と横断的関心事
01. 例外をHTTPに変換する15分
02. @ExceptionHandlerと@ControllerAdvice15分
03. エラーレスポンスの形を決める15分
04. フィルタとインターセプタ15分
05. ログとリクエストの追跡15分
06. 設定値の外部化15分
8
テストと仕上げ
01. テストの種類と使い分け15分
02. Serviceの単体テスト15分
03. モックで依存を差し替える15分
04. MockMvcでエンドポイントを試す15分
05. 本番へ出すための次の一歩10分

@Serviceに置くもの

このレッスンでは、@Service を付けたクラスに置くべきものと置くべきでないものを区別し、業務ルールを1か所に集める書き方を身につけます。

@Service はただの目印です

@Service は @Component の別名です。DI コンテナに登録するという働きは @Component とまったく同じで、追加の機能はありません。それでもわざわざ使い分けるのは、読む人に対して「このクラスは業務ルールの置き場です」と宣言するためです。

Java

@Service public class OrderService { private final OrderRepository orderRepository; private final PointRepository pointRepository; OrderService(OrderRepository orderRepository, PointRepository pointRepository) { this.orderRepository = orderRepository; this.pointRepository = pointRepository; } }

依存はコンストラクタで受け取ります。フィールドを final にできるので、生成後に差し替わらないことが型で保証されます。

Service に置くもの

置くべきものは、次の4種類です。

  1. 可否の判断 — 出荷できる状態か、在庫は足りているか、この利用者に権限があるか
  2. 計算 — 送料、割引、ポイント、税額、締め日の判定
  3. 複数の操作をまとめた手続き — 注文を保存し、在庫を減らし、通知を積む、という一連の流れ
  4. トランザクション境界 — 上の手続きを、まとめて成功かまとめて失敗かにする @Transactional

共通しているのは、どれも「業務としてどうあるべきか」を決めている点です。HTTP でも SQL でもない、その事業の決まりごとがここに集まります。

Service に置かないもの

逆に、次のものは Service に入れません。

置かないもの本来の置き場理由
HttpServletRequest、ResponseEntity、ステータスコードControllerHTTP から呼ばれたことを Service が知る必要はありません
リクエスト DTO やレスポンス DTOController入口の形に業務ルールが縛られます
SQL 文字列、EntityManager の直接操作Repository保存先の都合が業務ルールに混ざります
画面表示用の文字列組み立てController または表示層表示の都合が変わるたびに業務ルールを触ることになります
System.out.println によるデバッグ出力ロガー出力先を後から切り替えられません

判定に迷ったら、次の問いを使ってください。この処理は、同じアプリを画面のない定期バッチとして書き直しても、そのまま必要でしょうか。必要ならば Service です。

薄い Service は Service ではありません

よくある失敗が、Repository をただ呼び直すだけのメソッドで Service を埋めることです。

Java

@Service public class OrderService { public Order find(Long id) { return orderRepository.findById(id).orElse(null); } }

これは層を1つ増やしただけで、何の判断も引き受けていません。業務ルールが無いのに Service を置いても、読む場所が増えるだけです。逆に言えば、Service が薄くなっているときは、業務ルールが Controller かエンティティか、あるいは画面側に漏れている可能性を疑ってください。

計算ルールは1つのメソッドにまとめます

送料や割引のようなルールは、あちこちの if に散らすと必ず食い違います。注文画面と確認画面と管理画面で送料が違って見える不具合は、たいていこれが原因です。ルールごとに入口を1つ決め、そこだけを見れば答えが分かる形にします。

Java

@Service public class PricingService { public int shippingFee(int total) { return total >= 5000 ? 0 : 500; } }

呼ぶ側は判断せず、答えを受け取るだけにします。これで、無料ラインを変えるときに触る場所が1か所に決まります。

演習で書くもの

演習では、注文の合計金額と会員かどうかから、割引額と送料と請求額を決めます。Spring では @Service を付けたクラスのインスタンスメソッドとして書き、DI で受け取って Controller から呼ぶものですが、判断そのものはフレームワークと無関係な素の Java です。ここでは static メソッドとして書いて、ルールの形だけに集中します。

ルールは次の通りです。まず合計金額から割引率を決め、割引額を引きます。割引後の金額で送料を判定します。この順番が結果を変える点にも注目してください。

要件

  1. 戻り値は discount=<割引額>,fee=<送料>,charge=<請求額> の形にすること
  2. 割引率は member が true のときだけ適用し、10000 円以上で 10 パーセント、5000 円以上で 5 パーセント、それ未満は 0 パーセントとすること
  3. 送料は割引前ではなく割引後の金額で判定し、5000 円以上なら 0 円、未満なら 500 円とすること。ただし total が 0 のときは送料も 0 円とすること

入出力例

quote(12000, true) → "discount=1200,fee=0,charge=10800" quote(10000, true) → "discount=1000,fee=0,charge=9000" quote(5000, true) → "discount=250,fee=500,charge=5250" quote(5000, false) → "discount=0,fee=0,charge=5000" quote(4999, true) → "discount=0,fee=500,charge=5499" quote(0, true) → "discount=0,fee=0,charge=0"

ヒント

割引率は 10 や 5 のような整数のパーセントとして持ち、total * rate / 100 と整数で計算すれば自然に切り捨てになります

割引後の金額を変数に入れてから送料を判定します。total で判定すると 10000 円の会員注文で結果がずれます

total が 0 の場合を最初に return で片付けてしまうと、後の条件が読みやすくなります

生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/09

関連レッスン

  • 状態遷移をルールにする

    許される状態変化だけを通す判定を実装します。

  • 料金計算のような業務ロジック

    条件が絡む計算を、読める形で実装します。

  • @Transactionalとは何か

    トランザクション境界とロールバックの条件を確認します。

  • トランザクションの巻き戻し

    失敗時に変更をなかったことにする処理を実装します。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • クラスデータと振る舞いを束ねる単位
  • コンテナデータ取得や状態を担う上位コンポーネント
  • コンストラクタインスタンス生成時の初期化処理
  • フィールドクラスが持つデータ
  • 判断YES/NO 分岐を表す菱形
  • トランザクション「全部成功 or 全部なかったことに」をまとめる単位
  • HTTPWeb の通信プロトコル、HTTPS は TLS で暗号化したもの
  • SQLデータベースを操作するための共通言語
main.java
学習モード
エディタを読み込んでいます

メモ

@Serviceに置くもの

⌘S で保存