6
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-07-14

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;
}
6
11
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
6
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?