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を利用して簡潔に記述している。
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
ドメイン層とインフラ層のインタフェース。
ドメイン層で使いたいインタフェースを、ここで定義する。
このインタフェースによって、ドメイン層とインフラ層の依存関係が逆転する(依存関係逆転の原則)
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コンテナという仕組みを利用して解決する。
(インタフェースの実装が無い場合、この時点ではまだコンパイルエラーになる。)
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の依存関係
public class A {
private B b = new B();
}
Spring DIの場合
Springでは宣言した変数に対して、自動でインスタンスを注入してくれるDIコンテナという仕組みがある。
@Autowiredを付与することで、自動でインスタンスを生成し、注入してくれる。
@Compornent
public class A {
@Autowired
private B b;
}
@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;
}