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

[Spring Boot + MySQLでシンプルなWeb REST APIサーバを実装する - Qiita](https://qiita.com/YutaKase6/items/007f6b7d12d342c1330c)

# Outline
以下の設計に従って、ドメイン層を実装する。
[Spring Bootで実装するWebAPIのアーキテクチャを考える - Qiita](https://qiita.com/YutaKase6/items/7d88fa23f81366905270)

ドメイン層としては、以下の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のフィールドのみを引数とするコンストラクタ

こんな感じ。

```java

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インスタンスが生成できる。

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

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

```java
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に**依存している**。
<img width=512 src="https://qiita-image-store.s3.amazonaws.com/0/90526/0a008fe1-4686-09de-b12e-c02460c7877e.png">

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

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

```B.java
@Compornent
public class B {
}
```

<img width=512 src="https://qiita-image-store.s3.amazonaws.com/0/90526/74797937-f918-dbaf-aa15-3514e5e5ac7c.png">

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

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

- フィールドインジェクション

```java
@Compornent
public class A {
    @Autowired
    private B b;
}
```
- コンストラクタインジェクション

```java
@Compornent
public class A {
    private B b;
    
    @Autowired
    public A(B b) {
        this.b = b;
    }
}
```

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

```java
@RequiredArgsConstructor
@Compornent
public class A {
    @NonNull
    private final B b;
}
```