Java
spring-data-jpa
SpringBoot
11
どのような問題がありますか?

この記事は最終更新日から3年以上が経過しています。

投稿日

更新日

Spring BootとJPAでREST APIを実装する(ドメイン層編)

Spring Boot + MySQLでシンプルなWeb REST APIサーバを実装する - Qiita

Outline

以下の設計に従って、ドメイン層を実装する。
Spring Bootで実装するWebAPIのアーキテクチャを考える - Qiita

ドメイン層としては、以下の3クラスを作成する。

  • User.java
  • UserRepository.java
  • USerService.java
├── domain
│   ├── object
│   │   └── User.java
│   ├── repository
│   │   └── UserRepository.java
│   └── service
│       └── UserService.java

User.java

アプリケーション上で、ユーザという概念を表現するためのクラス。
今回は特に振る舞いは無し、必要な情報を保持するだけ。
Lombokを利用して簡潔に記述している。

User.java
package com.example.springapi.domain.object;

import lombok.Builder;
import lombok.Data;

/**
 * ユーザ
 */
@Data
@Builder
public class User {

    /**
     * ユーザID
     */
    private String id;

    /**
     * ユーザ情報
     */
    private String value;
}

※ Lombok

@Data

以下のメソッドを自動で作成してくれる。

  • Getter
  • Setter
  • Equals
  • HashCode
  • ToString
  • finalのフィールドのみを引数とするコンストラクタ

こんな感じ。


package com.example.springapi.domain.object;

import lombok.Builder;

/**
 * ユーザ
 */
@Builder
public class User {

    /**
     * ユーザID
     */
    private String id;

    /**
     * ユーザ情報
     */
    private String value;

    public String getId() {
        return this.id;
    }

    public String getValue() {
        return this.value;
    }

    public void setId(String id) {
        this.id = id;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof User)) return false;
        final User other = (User) o;
        if (!other.canEqual((Object) this)) return false;
        final Object this$id = this.getId();
        final Object other$id = other.getId();
        if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false;
        final Object this$value = this.getValue();
        final Object other$value = other.getValue();
        if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $id = this.getId();
        result = result * PRIME + ($id == null ? 43 : $id.hashCode());
        final Object $value = this.getValue();
        result = result * PRIME + ($value == null ? 43 : $value.hashCode());
        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof User;
    }

    public String toString() {
        return "User(id=" + this.getId() + ", value=" + this.getValue() + ")";
    }
}

@Builder

インスタンス生成をメソッドチェーン的に記述できるようになる。
これは好み。こんな感じにUserインスタンスが生成できる。

String id;
String value;
User user = User.builder()
                .id(id)
                .value(value)
                .build();

実際には以下のようなコードがLombokによって生成されている。

package com.example.springapi.domain.object;

import lombok.Data;

/**
 * ユーザ
 */
@Data
public class User {

    /**
     * ユーザID
     */
    private String id;

    /**
     * ユーザ情報
     */
    private String value;

    @java.beans.ConstructorProperties({"id", "value"})
    User(String id, String value) {
        this.id = id;
        this.value = value;
    }

    public static UserBuilder builder() {
        return new UserBuilder();
    }

    public static class UserBuilder {
        private String id;
        private String value;

        UserBuilder() {
        }

        public UserBuilder id(String id) {
            this.id = id;
            return this;
        }

        public UserBuilder value(String value) {
            this.value = value;
            return this;
        }

        public User build() {
            return new User(id, value);
        }

        public String toString() {
            return "User.UserBuilder(id=" + this.id + ", value=" + this.value + ")";
        }
    }
}

UserRepository.java

ドメイン層とインフラ層のインタフェース。
ドメイン層で使いたいインタフェースを、ここで定義する。
このインタフェースによって、ドメイン層とインフラ層の依存関係が逆転する(依存関係逆転の原則)

UserRepository.java
package com.example.springapi.domain.repository;

import com.example.springapi.domain.object.User;

import java.util.Optional;

/**
 * インフラ層とのインタフェース
 */
public interface UserRepository {

    /**
     * ユーザ検索
     *
     * @param id 検索したいユーザID
     * @return ユーザ
     */
    Optional<User> findById(String id);

    /**
     * ユーザ作成、更新
     *
     * @param user 作成、更新したユーザ
     * @return 更新後のユーザ
     */
    User save(User user);

    /**
     * ユーザ削除
     *
     * @param id 削除したいユーザID
     */
    void deleteById(String id);
}

UserService.java

User.javaに記述するのは不自然なビジネスロジックを記述するクラス。
先程定義したインタフェースを利用して、永続化されているユーザ情報を操作する。
UserRepositoryの実装クラスは、SpringのDIコンテナという仕組みを利用して解決する。
(インタフェースの実装が無い場合、この時点ではまだコンパイルエラーになる。)

UserService.java
package com.example.springapi.domain.service;

import com.example.springapi.domain.object.User;
import com.example.springapi.domain.repository.UserRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Optional;

/**
 * ユーザ操作のロジック
 */
@Service
@RequiredArgsConstructor
public class UserService {

    @NonNull
    private final UserRepository userRepository;

    /**
     * ユーザ検索
     *
     * @param id 検索したいユーザID
     * @return ユーザ
     */
    public Optional<User> findById(String id) {
        return this.userRepository.findById(id);
    }

    /**
     * ユーザ作成、更新
     *
     * @param user 作成、更新したユーザ
     * @return 更新後のユーザ
     */
    public User save(User user) {
        return this.userRepository.save(user);
    }

    /**
     * ユーザ削除
     *
     * @param id 削除したいユーザID
     */
    public void deleteById(String id) {
        this.userRepository.deleteById(id);
    }
}

※ SpringによるDI

Springでは、DIコンテナと呼ばれる仕組みによって、DIを実装している。

通常のjavaの依存関係

A.java
public class A {
    private B b = new B();
}

Class AはClass Bに依存している

Spring DIの場合

Springでは宣言した変数に対して、自動でインスタンスを注入してくれるDIコンテナという仕組みがある。
@Autowiredを付与することで、自動でインスタンスを生成し、注入してくれる。

A.java
@Compornent
public class A {
    @Autowired
    private B b;
}
B.java
@Compornent
public class B {
}

ただし、付与するAutowiredが有効になるのは注入される側のクラス(A)がDIコンテナ経由で取得された場合のみである。
すなわち、通常のJavaアプリケーションのようにクラスAをnewしてしまうと、bはnullのままである。
そのため、Autowiredを利用する場合、自身もDIコンテナに登録する必要がある。
コンテナへの登録方法は様々あるが、SpringBootでは基本的に@Compornentをクラスに付与することで登録される。
今回利用した@Service@Compornentのエイリアス。

※ SpringによるDIの種類

SpringのDIは以下の記法がある。
フィールドインジェクションをすると、IntelliJに怒られる。
(再利用性やインスタンスの不変性、依存の明示化などいろいろな理由)
セッターインジェクションもあるが使ったこと無いので割愛。

  • フィールドインジェクション
@Compornent
public class A {
    @Autowired
    private B b;
}
  • コンストラクタインジェクション
@Compornent
public class A {
    private B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }
}

※ コンストラクタインジェクション with Lombok

コンストラクタが1つしか無い場合、@Autowiredは省略可能。
Lombokのコンストラクタ生成のアノテーション使えばここまで省略できる。

@RequiredArgsConstructor
@Compornent
public class A {
    @NonNull
    private final B b;
}
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
ユーザー登録ログイン
YutaKase6

コメント

この記事にコメントはありません。
あなたもコメントしてみませんか :)
ユーザー登録
すでにアカウントを持っている方はログイン
記事投稿イベント開催中
Snykを使って開発者セキュリティに関する記事を投稿しよう!
~
React 18、あなたならどう使いこなす?
~
11
どのような問題がありますか?
ユーザー登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる
ユーザー登録ログイン
ストックするカテゴリー