Spring BootでAPIサーバアプリケーションを書く

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

よく訓練されたアップル信者、都元です。ガンガンいきますよ。

前回は、Spring BootでWebアプリを書きました。しかし、モバイルのバックエンドや、リッチなフロントエンドを持つアプリケーションに対しては、JSONをやり取りするようなAPIサーバを書く機会が増えていると思います。

現時点において、APIサーバの入出力フォーマットのデファクトスタンダードはJSONであると思います。先日までのサンプルアプリケーションbersesrkerは、GET /users に対して下記のような text/plain を返していました。

User(username=miyamoto, password=$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy),
User(username=yokota, password=$2a$10$nkvNPCb3Y1z/GSearD7s7OBdS9BoLBss3D4enbFQIgNJDvr0Xincm)

願わくば、下記のような application/json を返すAPIを実装したいものです。

[
  {
    "username": "miyamoto",
    "password": "$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy"
  },
  {
    "username": "yokota",
    "password": "$2a$10$nkvNPCb3Y1z/GSearD7s7OBdS9BoLBss3D4enbFQIgNJDvr0Xincm"
  }
]

ハンドラメソッドで自作のクラスを返してみる

@RequestMapping(value = "/users", method = RequestMethod.GET)
@Transactional
@ResponseBody
public ResponseEntity<?> users() {
  log.debug("users");
  Iterable<User> users = userRepos.findAll();
  return ResponseEntity.ok(users);
}

こんな風に何も考えずに、Userの集合を返すことによって、それが実現できたらいいよなぁ、って思いませんか。

でもUserというクラスは自分で作ったものだし、どのようにJSONに変換すべきか判断が付かないのが一般的でしょう。どうせ、これじゃRuntimeExceptionが発生するんだろうという諦めと、もしかしたらSpring Bootならやってくれるかもしれない、という淡い期待を抱きながら、いざ実行。 → /diff

$ curl -s http://localhost:8080/users
[{"username":"miyamoto","password":"$2a$10$cPnF0sq.bCPHeGuzVagOgOmbe2spT1Uh1k9LyuS0jzb5F3Lm.9kEy"},{"username":"yokota","password":"$2a$10$nkvNPCb3Y1z/GSearD7s7OBdS9BoLBss3D4enbFQIgNJDvr0Xincm"}]

キタ━━━━(゚∀゚)━━━━!!

あっけなさ過ぎますね。

passwordフィールドは出力したくない問題

さて、パスワードはハッシュ化しているとは言え出力したくないですね。もう一工夫して、ここは隠してみましょう。

そもそもJavaオブジェクトをJSON形式に変換しているのはJacksonというライブラリです。Jacksonの基本的な使い方は、こう!

@Data
@AllArgsConstructor
@NoArgsConstructor
public class HogeBean {
  String str;
  int num;
  boolean bool;
}
public static void main(String[] args) throws JsonProcessingException {
  ObjectMapper objectMapper = new ObjectMapper();
  String json = objectMapper.writeValueAsString(new HogeBean("foo", 1, true));
  System.out.println(json);
  
  HogeBean obj = objectMapper.readValue(json, HogeBean.class);
  System.out.println(obj);
}

結果は、こう!

{"str":"foo","num":1,"bool":true}
HogeBean(str=foo, num=1, bool=true)

あー、もうなんて直感的なんでしょうか。さらにJacksonには様々なアノテーションが用意されており、これらを活用することによってJSONとObject間の変換処理を柔軟に制御できます。下記にメジャーどころを挙げますが、その他にも色々あります。

  • @JsonAnyGetter
  • @JsonAnySetter
  • @JsonCreator
  • @JsonIgnore
  • @JsonInclude
  • @JsonProperty

この中でそれっぽい奴、、、@JsonIgnore ですね。これを User#password につけてみましょう。

public class User {
  
  // (ry
  
  @Getter
  @Setter
  @Column(name = "password")
  @JsonIgnore
  private String password;
}
$ curl -s http://localhost:8080/users | jq .
[
  {
    "username": "miyamoto"
  },
  {
    "username": "yokota"
  }
]

めでたし。

恒例の、動かしてみようのコーナー

$ git clone https://github.com/classmethod-aws/berserker.git
$ cd berserker
$ git checkout 13.0
$ ./gradlew bootRun

まぁ、いつもの感じでやってみてください。