Skip to content

API利用法

Spring Bootアプリケーションで外部APIを利用する際の実践的な方法について詳しく解説します。

RESTful APIは、リソースを中心に設計します。

@RestController
@RequestMapping("/api/users")
public class UserController {
// GET /api/users - ユーザー一覧取得
@GetMapping
public ResponseEntity<List<UserDTO>> getUsers(
@RequestParam(required = false) String name,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
// 実装
}
// GET /api/users/{id} - ユーザー詳細取得
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
// 実装
}
// POST /api/users - ユーザー作成
@PostMapping
public ResponseEntity<UserDTO> createUser(@Valid @RequestBody UserCreateRequest request) {
// 実装
}
// PUT /api/users/{id} - ユーザー更新
@PutMapping("/{id}")
public ResponseEntity<UserDTO> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request) {
// 実装
}
// DELETE /api/users/{id} - ユーザー削除
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
// 実装
}
}
@Service
@Slf4j
public class ExternalApiService {
private final RestTemplate restTemplate;
private final String apiBaseUrl;
public ExternalApiService(RestTemplate restTemplate,
@Value("${external.api.base-url}") String apiBaseUrl) {
this.restTemplate = restTemplate;
this.apiBaseUrl = apiBaseUrl;
}
public UserDTO fetchUser(Long id) {
String url = apiBaseUrl + "/users/{id}";
try {
ResponseEntity<UserDTO> response = restTemplate.getForEntity(
url, UserDTO.class, id);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
} else {
throw new ApiException("Failed to fetch user: " + response.getStatusCode());
}
} catch (HttpClientErrorException.NotFound e) {
throw new ResourceNotFoundException("User", id);
} catch (Exception e) {
log.error("Error fetching user: id={}", id, e);
throw new ApiException("Error fetching user", e);
}
}
public UserDTO createUser(UserCreateRequest request) {
String url = apiBaseUrl + "/users";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<UserCreateRequest> entity = new HttpEntity<>(request, headers);
try {
ResponseEntity<UserDTO> response = restTemplate.postForEntity(
url, entity, UserDTO.class);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
} else {
throw new ApiException("Failed to create user: " + response.getStatusCode());
}
} catch (Exception e) {
log.error("Error creating user", e);
throw new ApiException("Error creating user", e);
}
}
}

WebClientを使用した実装(推奨)

Section titled “WebClientを使用した実装(推奨)”
@Service
@Slf4j
public class ExternalApiService {
private final WebClient webClient;
public ExternalApiService(WebClient.Builder webClientBuilder,
@Value("${external.api.base-url}") String apiBaseUrl) {
this.webClient = webClientBuilder
.baseUrl(apiBaseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
public Mono<UserDTO> fetchUser(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> {
if (response.statusCode() == HttpStatus.NOT_FOUND) {
return Mono.error(new ResourceNotFoundException("User", id));
}
return Mono.error(new ApiException("Client error: " + response.statusCode()));
})
.onStatus(HttpStatus::is5xxServerError, response ->
Mono.error(new ApiException("Server error: " + response.statusCode())))
.bodyToMono(UserDTO.class)
.doOnError(error -> log.error("Error fetching user: id={}", id, error))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.filter(throwable -> throwable instanceof WebClientException));
}
public Mono<UserDTO> createUser(UserCreateRequest request) {
return webClient.post()
.uri("/users")
.bodyValue(request)
.retrieve()
.bodyToMono(UserDTO.class)
.doOnError(error -> log.error("Error creating user", error));
}
}
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(@Value("${api.key}") String apiKey) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add((request, body, execution) -> {
request.getHeaders().set("X-API-Key", apiKey);
return execution.execute(request, body);
});
return restTemplate;
}
}
@Service
public class AuthenticatedApiService {
private final RestTemplate restTemplate;
private final TokenService tokenService;
public AuthenticatedApiService(RestTemplate restTemplate, TokenService tokenService) {
this.restTemplate = restTemplate;
this.tokenService = tokenService;
}
public UserDTO fetchUser(Long id) {
String token = tokenService.getAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
HttpEntity<?> entity = new HttpEntity<>(headers);
ResponseEntity<UserDTO> response = restTemplate.exchange(
apiBaseUrl + "/users/{id}",
HttpMethod.GET,
entity,
UserDTO.class,
id
);
return response.getBody();
}
}
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<ErrorResponse> handleClientError(HttpClientErrorException ex) {
ErrorResponse error = new ErrorResponse(
ex.getStatusCode().value(),
"Client Error",
ex.getMessage()
);
return ResponseEntity.status(ex.getStatusCode()).body(error);
}
@ExceptionHandler(HttpServerErrorException.class)
public ResponseEntity<ErrorResponse> handleServerError(HttpServerErrorException ex) {
ErrorResponse error = new ErrorResponse(
ex.getStatusCode().value(),
"Server Error",
"External API server error occurred"
);
return ResponseEntity.status(ex.getStatusCode()).body(error);
}
@ExceptionHandler(ResourceAccessException.class)
public ResponseEntity<ErrorResponse> handleConnectionError(ResourceAccessException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.SERVICE_UNAVAILABLE.value(),
"Service Unavailable",
"Failed to connect to external API"
);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(error);
}
}
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplateWithRetry() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
CloseableHttpClient httpClient = HttpClients.custom()
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true))
.build();
factory.setHttpClient(httpClient);
return new RestTemplate(factory);
}
}
@Service
@Cacheable("users")
public class CachedApiService {
private final RestTemplate restTemplate;
@Cacheable(value = "users", key = "#id")
public UserDTO fetchUser(Long id) {
// API呼び出し
return restTemplate.getForObject(apiBaseUrl + "/users/{id}", UserDTO.class, id);
}
@CacheEvict(value = "users", key = "#id")
public void evictUser(Long id) {
// キャッシュをクリア
}
}
@Service
public class RateLimitedApiService {
private final RestTemplate restTemplate;
private final RateLimiter rateLimiter;
public RateLimitedApiService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
this.rateLimiter = RateLimiter.create(10.0); // 秒間10リクエスト
}
public UserDTO fetchUser(Long id) {
rateLimiter.acquire(); // レート制限をチェック
return restTemplate.getForObject(
apiBaseUrl + "/users/{id}", UserDTO.class, id);
}
}

Spring BootでのAPI利用は、以下の要素を考慮して実装します:

  • RESTful APIの設計: リソース指向の設計
  • APIクライアント: RestTemplateまたはWebClientの使用
  • 認証: APIキーやBearer Tokenの実装
  • エラーハンドリング: 適切な例外処理
  • リトライ機能: 一時的なエラーへの対応
  • キャッシング: パフォーマンスの向上
  • レート制限: APIの過剰な呼び出しを防止

これらの要素を適切に実装することで、堅牢で効率的なAPI連携を実現できます。