Spring BootでQiitaのAPIを実行し記事の情報を取得する

# はじめに
こんにちは。みみしたです。
Spring Boot + Reactを使用したアプリケーションの開発について現在勉強しており、その中でSpring Bootを使ってQiitaのAPIを実行する方法について調べたので、アウトプットとして記事を起こしています。
Spring Bootをしようして外部のAPIを実行する方法を今回初めて勉強したので、そのあたりをメインに書ければと思います。

# コードと解説
## pom(一部抜粋)
```xml:pom.xml
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
			<version>3.4.3</version>
	</dependency>
	<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.36</version>
			<scope>provided</scope>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>
```
ここで大事なのは、外部のAPIを実行するために「spring-boot-starter-webflux」を追加している点です。
Webfluxについては公式や他の方がまとめたドキュメントが非常にわかりやすいため、ここでは説明を省きます。
<https://spring.pleiades.io/spring-framework/reference/web/webflux.html>


## Config
```java:WebClientConfig.java
package com.example.DevDigest.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

  @Bean
  public WebClient qiitaWebClient(WebClient.Builder builder) {
    return builder.baseUrl("https://qiita.com/api/v2").build();
  }
}
```
BeanにWebClientを登録します。今回はqiitaのapiを登録するので、ベースとなる「<https://qiita.com/api/v2>」をbaseUrlとしてビルドします。

## Service
```java:QiitaApiService.java
package com.example.DevDigest.service;

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import com.example.DevDigest.dto.QiitaPostDto;

import reactor.core.publisher.Flux;

@Service
public class QiitaApiService {
  private final WebClient qiitaWebClient;

  public QiitaApiService(WebClient qiitaWebClient) {
    this.qiitaWebClient = qiitaWebClient;
  }

  public Flux<QiitaPostDto> fetchLatestPosts() {
    return qiitaWebClient.get()
        .uri(uriBuilder -> uriBuilder
          .path("/items")
          .queryParam("page", 1)
          .queryParam("per_page", 3)
          .queryParam("query", "stocks:>20")
          .build())
        .retrieve()
        .bodyToFlux(QiitaPostDto.class);
  }
}
```
Configで登録したqiitaWebClientを注入します。
メソッド「fetchLatestPosts()」では以下の条件で記事を取得しています。
* ストック数が20以上
* 1ページ目を取得
* 1ページ当たりの記事数は3

~メソッドについてもう少し細かく~
.uri以下では初めに定義したbaseUrlの後ろに加えるパスを追記しています。
.queryParamはクエリパラメータ方式でurlに値を加えていくことができます。
今回の場合、最終的には以下のURLが生成されます。
<https://qiita.com/api/v2/items?page=1&per_page_3&query=stocks:>>20

* .retrieve().bodyToFlux()
このメソッドでAPIの実行結果を取得しています。

### Fluxとは？
そもそもWebClientで非同期処理を実現できますが、その核となっているクラスがFlux。主な特徴は以下。

1. 非同期処理
    * データを非同期かつノンブロッキングで処理できます
    * サーバーリソースを効率的に使用できます
2. ストリーム処理
    * データを連続的なストリームとして扱います
    * 大量のデータを効率的に処理できます

これらの特徴により、クライアント側からは処理の待ち時間でアプリケーションが止まることはなく、さらにストリーム処理によりデータが到着次第処理することが可能。
※この辺りは自分で調べたものを参考に記載していますが、異なっているかもしれません。

## DTO
```java:QiitaPostDto.java
package com.example.DevDigest.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.Data;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class QiitaPostDto {
  private String id;
  private String title;
  private String url;
  @JsonProperty("created_at")
  private String createdAt;
  private QiitaUserDto user;

  @Data
  @JsonIgnoreProperties(ignoreUnknown = true)
  public static class QiitaUserDto {
    private String id;
    private String name;
    @JsonProperty("profile_image_url")
    private String profileImageUrl;
  }
}
```
ここではserviceでbodyToFluxを実行したときの変換先のDTOを定義しています。
以下にAPIレスポンスの例があるのでそれを参考に必要な要素のみ定義しています。
<https://qiita.com/api/v2/docs#get-apiv2items>

今回、すべてのJSONデータを使うわけではないので、エラーを防ぐために`@JsonIgnoreProperties(ignoreUnknown = true)`を記載しています。

また、フィールドの定義ではキャメルケースを使用したかったため、`@JsonProperty`を使用してフィールドのマッピングをしています。

## Controller
```java:QiitaApiController
package com.example.DevDigest.controller;

import org.springframework.web.bind.annotation.RestController;

import com.example.DevDigest.dto.QiitaPostDto;
import com.example.DevDigest.service.QiitaApiService;

import reactor.core.publisher.Flux;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
@RequestMapping("/api/qiita")
public class QiitaApiController {
  
  private final QiitaApiService qiitaApiService;

  public QiitaApiController(QiitaApiService qiitaApiService) {
      this.qiitaApiService = qiitaApiService;
  }

  @GetMapping("/latest-posts")
  public Flux<QiitaPostDto> getLatestPosts() {
      return qiitaApiService.fetchLatestPosts();
  }
  
}
```

## 追加設定
試しに実行したところ、apiの実行結果がデフォルトのメモリサイズ(256KB)を超えてしまいエラーとなったため、暫定的な対応としてメモリサイズを増やすことにした。
```properties:qpplication.properties
spring.application.name=DevDigest
# 追記
spring.codec.max-in-memory-size=2MB
```




# 終わりに
今回は、Spring BootでQiitaのAPIを実行する方法について記載しました。最終的にはQiitaだけではなくZennや海外のサイトの情報も取得してReactで表示するようなアプリケーションを作りたいなと思っているので、今後も進捗や学びがあれば都度記事にしたいと思います。

# 参考
https://hyper-text.org/archives/2021/07/nextjs_qiita_api_get_post/

https://qiita.com/zumax/items/f07ce794cfd597ca58b1

https://qiita.com/ko9uch1/items/5d293321919763831a5c