1
3

More than 1 year has passed since last update.

Spring BootのWebアプリケーションの基本③~3層アーキテクチャと依存性の注入~

Last updated at Posted at 2023-08-09

前回の記事では、Entityについて必要な処理について説明をしてきました。

今回からは実際にWebアプリケーションの開発でよく使われている3層アーキテクチャについて説明をしていきます。

今回のファイル構成は、以下になります。

ファイル構成

ファイル構成(クリックして展開)


.
├── .gradle
├── .idea
├── build
├── gradle
└── src
     ├── main
     |      ├── java
     |      |      └── com
     |      |           └── example
     |      |                 └── practice 
     |      |                          PracticeApplication.java
     |      |                          ├── web 
     |      |                          |    ├── order
     |      |                          |    |     └── OrderController.java
     |      |                          |    |                
     |      |                          |    └── IndexController.java
     |      |                          └── domain
     |      |                                 └── order 
     |      |                                       └── OrderEntity.java
     |      |                                       └── OrderService.java
     |      |                               
     |      └── resources
     |              ├── static
     |              ├── templates
     |              |        └── order
     |              |                  └── list.html
     |              └── index.html
     └── test
.gitignore
build.gradle
gradlew.bat
HELP.md
settings.gradle

今回作成するにあたっての事前知識

今回はWebアプリケーションの本格的な実装に入っていきますが、その前に今回作成するにあたっての事前の知識をお話しします。

3層アーキテクチャ

まずは、アーキテクチャについて解説をしていきます。
IT分野では、「基本設計」や「設計思想」を指します。

そして、Spring BootのWebアプリケーションの基本①では、「リクエスト」と「レスポンス」という説明をしました。

クライアント側がサーバーに「リクエスト」を送信し、サーバがそれに「レスポンス」を返信するという形で処理が行われます。
これは、2層アーキテクチャと呼ばれるもので、 クライアントサーバー・モデルを指します。

今回の例で言えば、
・クライアントがサーバーに「http://localhost:8080」を送信
・サーバがそれにレスポンスとして「<h1>Hello World</h1>」を 返信
の形で処理を行なっているのが2層アーキテクチャです。

スクリーンショット 2023-08-09 7.22.45.png

以下の図が3層アーキテクチャの図ですが、プレゼンテーション層とアプリケーション層の役割を兼任しているのが、2層アーキテクチャの特徴です。

スクリーンショット 2023-08-09 7.29.54.png

次に3層アーキテクチャの説明をしたいと思います。

3層アーキテクチャは、クライアントサーバー・モデルを
・プレゼンテーション層(主にユーザインターフェース部分)
・ビジネスロジック層(アプリケーション層とも呼ぶことがある。主にロジック部分)
・データ・アクセス層(データを管理する部分)
の3層に分割して構築したモデルを指します。

2層アーキテクチャは、使い勝手の良いプログラムだが、UI(ユーザインターフェイス)とビジネスロジックが混在しているため、運用やプログラムの更新が大変という点とディスク容量を圧迫してしまうというデメリットがあります。

その欠点を補ったものが3層アーキテクチャで、プレゼンテーション層とビジネスロジック層、データ・アクセス層に分離することで、変更が容易になったり、後から機能を追加したりしやすくなることができます。

プレゼンテーション層

プレゼンテーション層は主に画面の部分に関する機能を指します。 画面からの入力や画面への出力の部分やユーザーインターフェースを提供します。 つまり、ユーザとシステム間のやりとりを担当しています。

Spring Bootでは@Controllerとして付与することができます。

ビジネスロジック層

ビジネスロジック層はシステムが扱うデータの処理ルールや業務フローなど、アプリケーションの基本的な機能を定義します。

また、プレゼンテーション層とデータ・アクセス層の仲介の役割を担っており、データ・アクセス層にアクセスして、データの取得や更新を行なったり、その結果をプレゼンテーション層に返して、ユーザに表示させたりなどの役割を担っています。

Spring Bootでは@Serviceとして付与することがあります。

データ・アクセス層

データ・アクセス層は、データの検索や操作をしたり、データの永続化を管理したりする役割を担っています。

Spring Bootでは@Repositoryとして付与することがあります。

まとめ

今までのまとめをサンプルコードとしてまとめたので、確認してみてください。

2層アーキテクチャのサンプルコード

3層アーキテクチャのサンプルコード

ビジネスロジック層の作成

ここからは、実際に先ほど説明した内容をもとにWebアプリケーションの作成をしていきたいと思います。

まずは、前回の記事で作成をしたOrderControllerを3層アーキテクチャの考え方に従って@Controllerの処理をビジネスロジック層を分離する処理をしていきます。

サービスクラスを作成します。サービスクラスはドメインに属するクラスなので、domainの配下に作成します。

ちなみに、ドメイン層は、 アプリケーション層に提供する業務ロジックを実装するための階層です。

ドメイン層は、主に、データを保持するためのクラスであるEntityの実装とデータを操作するためのメソッドであるRepositoryの実装、業務ロジックを実行し、アプリケーション層に提供するServiceの実装の3つに分かれます。

以下がプレゼンテーション層とアプリケーション層を分離したコードです。

findAllメソッドは、OrderEntityクラスのオブジェクトを生成し、注文情報を設定してリストに追加しています。
その後、生成された注文情報のリストを返す処理になります。

OrderService.java
package com.example.practice.domain.order;

import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class OrderService {
    // 注文情報のリストを取得するメソッド
    public List<OrderEntity> findAll() {
        // OrderEntityクラスのオブジェクトを生成し、リストに追加して返す
        return List.of(new OrderEntity(1000, "20230801", 1000,
                "株式会社あいうえお", 1000, "パソコン", 1, 100000,
                100000), new OrderEntity(1001, "20230802", 1001,
                "株式会社ABCD", 1001, "デスクトップ", 2, 60000,
                120000));
    }
}

こちらがOrderControllerクラスになります。

OrderController
package com.example.practice.web.order;

import com.example.practice.domain.order.OrderService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class OrderController {
    /*
    orderServiceクラスのインスタンスを作成する。
     orderServiceクラスのメソッドをコントローラで呼び出すために使用
    */
    private final OrderService orderService = new orderService();
    // GETリクエストを受け取り、オーダーリストを作成し、ビューに渡すメソッド
    @GetMapping("/orders")
    public String showList(Model model) {
        //OrderServiceクラスのfindAllメソッドで取得されたオーダーリストを、orderListでビューに渡す
        model.addAttribute("orderList", orderService.findAll());
        // "order/list"という名前のThymeleafビューを表示する
        return "order/list";
    }
}

再度、「http://localhost:8080/orders」を開くと、以下の通りになる。

スクリーンショット 2023-08-09 21.10.59.png

依存性の注入(Dependency Injection)

今回はSpringの重要な機能である依存性の注入(Dependency Injection)についての説明をします。

依存性の注入とは、「使いたいオブジェクトを直接newはせずに外部から渡してもらうようにする」ということです。

まず、 「依存」について説明をします。
例えば、今回の例で言うと、OrderControllerのなかで、OrderServiceを使っています。

model.addAttribute("orderList", orderService.findAll());

これが、OrderControllerはOrderServiceに依存しているといいます。

次に依存性の注入についての説明をします。
依存性の注入があるパターンとないパターンを用います。

依存性の注入がないパターンのコード

@Controller
public class OrderController {
    //newをしている
    private final OrderService orderService = new orderService();

newがない代わりにorderControllerの引数で、orderServiceを外部から受け取るようにしている。

依存性の注入があるパターンのコード

@Controller
public class OrderController {
    private final OrderService;
    //コンストラクタを呼び出す側でnewしてもらう
    public OrderController(OrderService orderService) {
      this.orderService = orderService;
    }

では、なぜ依存性の注入をするかというと、拡張性があることとテストがしやすくなることがあります。

例えば、new orderService()の引数が、new orderService(A,B,C・・・・)で引数が増えたときに、orderControllerを変更せずに済むなどがあります。

また、単体テストをするときに、そのアプリが「Hello World!」だとしたら、目視で確認することができるが、アプリの規模が大きくなると、依存性の注入がない場合は、orderServiceの内部の仕様を理解をしていなければ使えないが、依存性の注入がある場合では内部の仕様を理解しなくても使えるというメリットがあります。

まず、OrderService.javaはServiceクラスなので、アノテーションを付与します。

OrderService
package com.example.practice.domain.order;

import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class OrderService {
    // 注文情報のリストを取得するメソッド
    public List<orderEntity> findAll() {
        // orderEntityクラスのオブジェクトを生成し、リストに追加して返す
        return List.of(new orderEntity(1000, "20230801", 1000,
                "株式会社あいうえお", 1000, "パソコン", 1, 100000,
                100000), new orderEntity(1001, "20230802", 1001,
                "株式会社ABCD", 1001, "デスクトップ", 2, 60000,
                120000));
    }
}

次にOrderController.javaの修正ですが、@RequiredArgsConstructorアノテーションをクラスに指定するとfinalなフィールドを初期化するコンストラクタが生成されるので、newを削除し、@RequiredArgsConstructorを付与します。

OrderController
package com.example.practice.web.order;

import com.example.practice.domain.order.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
@RequiredArgsConstructor
public class OrderController {
    private final OrderService orderService;

    // GETリクエストを受け取り、オーダーリストを作成し、ビューに渡すメソッド
    @GetMapping("/orders")
    public String showList(Model model) {
        //orderServiceクラスのfindAllメソッドで取得されたオーダーリストを、orderListでビューに渡す
        model.addAttribute("orderList", orderService.findAll());
        // "order/list"という名前のThymeleafビューを表示する
        return "order/list";
    }
}

まとめ

今回はプレゼンテーション層、ビジネスロジック層、データ・アクセス層の3層アーキテクチャに焦点を当てました。

プレゼンテーション層とビジネスロジック層の分離、依存性の注入の重要性を実際のコードを通じて説明をし、3層アーキテクチャの基本的な理解が得られたと思います。

1
3
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
3