【初心者】SpringBootでTodoアプリのREST APIを書いてみる

# 追記
* Controllerのコードの一部が切れていたので修正
* Controllerのアノテーション周りの話が抜けていたので追記

# 内容
* Java初心者が夏休み中にSpringの勉強をする
* Spring Frameworkはハードルが高そうだったので一先ずSpringBootから
* お題は初心者がよくやるTodoアプリのREST API

# 詳しく触れないこと
* gradle周辺
* Database周辺
    * JPA
    * H2 Database

# 本編
* 開発は`IntelliJ IDEA`を使用
    * ビルドとかアプリケーションの起動はお任せ

## 雛形作成
* https://start.spring.io/ で作成
    * Project: `Gradle Project`
    * Language: `Java`
    * Spring Boot: `2.1.8`
    * Project Metadaba: 
        * Group: `com.example`
        * Artifact: `todoapp`
        * Options: そのまま
    * Dependencies:
        * `Spring Web`
        * `Spring Data JPA`
        * `H2 Database`
        * `Lombok`
* ダウンロードされたzipファイルをunzipしてIntelliJでopenしたところから開始

## パッケージ構成
* `com.example.todoapp`直下に以下を並べる
    * `controllers`
    * `repositories`
    * `models`
* `services`とかもあるべきかもしれないけど今回は割愛

## コード
### models
* Task.java

```java
package com.example.todoapp.models;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Data
@Entity
public class Task {

    @Id
    @GeneratedValue
    private Long id;
    private String summary;
    private Boolean done = false;

    public Task(){}
}

```
* 各アノテーションについて
    * `@Data`
        * 各フィールドのsetterとgetterを定義してくれる
        * `toString()`メソッドや`hashCode()`メソッドを定義してくれる
    * `@Entity`
        * エンティティであることを示す。
        * このクラスのフィールドを持つテーブルがDBに作られる
    * `@Id`
        * エンティティの主キーのフィールドを指定する
    * `@GeneratedValue`
        * 主キーの値の生成方法を指定する
        * `strategy`や`generator`を指定して挙動を変えられそう（試してない）

### repositories
* TaskRepository.java

```java
package com.example.todoapp.repositories;

import com.example.todoapp.models.Task;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TaskRepository extends JpaRepository<Task, Long> {
}
```
* `JpaRepository`を継承したinterfaceを定義
    * `JpaRepository`の祖先にいる`CrudRepository`インターフェースが`findById`などのインターフェースを定義している
    * 実装は`SimpleJpaRepository`なのかな？
* `JpaRepository`は対象のエンティティクラスとその主キーの型を指定する
    * ここで指定した主キーの型は、`findById`や`deleteById`の引数の型として使われる
    * 今回作成した`Task`エンティティの主キーは`Long`型なのでここでは`Long`を指定

### controllers
* ApiController.java

```java
package com.example.todoapp.controllers;

import com.example.todoapp.models.Task;
import com.example.todoapp.repositories.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping(value = "/api")
public class ApiController {

    @Autowired
    TaskRepository taskrepo;

    @GetMapping(value = "/todos")
    public List<Task> tasklist(){
        return taskrepo.findAll();
    }

    @GetMapping(value = "todo/{id}")
    public Optional<Task> retrieve(@PathVariable Long id){
        return taskrepo.findById(id);
    }

    @PostMapping(value = "/todo")
    public ResponseEntity<Task> newTask(@RequestBody Task task){
        Task result = taskrepo.save(task);
        return new ResponseEntity<Task> (result, HttpStatus.CREATED);
    }

    @PutMapping(value = "/todo/{id}")
    public Task update(@PathVariable Long id, @RequestBody Task task){
        task.setId(id);
        return taskrepo.save(task);
    }

    @DeleteMapping(value = "/todo/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable Long id){
        taskrepo.deleteById(id);
    }
}
```
* 各アノテーションについて
    * `@RestController`
        * 各メソッドの戻り値がそのままレスポンスボディになる
        * `@Controller`を使う場合、メソッドの戻り値はStringにしてテンプレートに埋め込む形で使うらしい
    * `@RequestMapping`
        * このコントローラが受け付けるリクエストのパスやメソッドやクエリパラメータを指定できる
    * `@Autowired`
        * `@Component`アノテーションをつけて登録されているBeanとの紐付けを行う
        * `@Repository`や`@Service`をつけたクラスも、内部では`@Component`が付加されている
        * ここでは`TaskRepository`インターフェースを満たすクラスが自動的に`taskrepo`に格納される
    * `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`
        * `@RequestMapping`のメソッドを固定したもの
        * `@GetMapping`の場合、`@RequestMapping(method=RequestMethod.GET)`と同等
    * `@ResponseStatus`
        * デフォルトのステータスコードを指定する
        * `200`以外を返したい時に使う？

## 動作確認
### 新規登録
```bash
$ curl http://localhost:8080/api/todo -XPOST -H 'Content-Type: application/json' -d '{"summary": "my first task"}' | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    75    0    47  100    28    170    101 --:--:-- --:--:-- --:--:--   170
{
  "id": 1,
  "summary": "my first task",
  "done": false
}
```
### リスト取得
```bash
$ curl http://localhost:8080/api/todos | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    49    0    49    0     0    285      0 --:--:-- --:--:-- --:--:--   286
[
  {
    "id": 1,
    "summary": "my first task",
    "done": false
  }
]
```
### 1件取得
```
$ curl http://localhost:8080/api/todo/1 | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    47    0    47    0     0   2045      0 --:--:-- --:--:-- --:--:--  2136
{
  "id": 1,
  "summary": "my first task",
  "done": false
}
```
### 更新
```bash
$ curl http://localhost:8080/api/todo/1 -XPUT -H 'Content-Type: application/json' -d '{"summary": "updated my task", "done": true}' | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    92    0    48  100    44   2632   2412 --:--:-- --:--:-- --:--:--  2666
{
  "id": 1,
  "summary": "updated my task",
  "done": true
}
```
### 削除(レスポンスボディがないのでStatusCodeで確認)
```bash
$ curl http://localhost:8080/api/todo/1 -XDELETE -v
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /api/todo/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 204
< Date: Wed, 25 Sep 2019 15:29:37 GMT
<
* Connection #0 to host localhost left intact
```

# まとめ
* 動かしたいもの自体はめっちゃ簡単に動かせた
* アノテーション種類多すぎて何使えばいいのかわからん
    * とりあえず今回使ったものくらいは最低限おさえておきたい
    * あとは何かするときに都度調べられればよさそう