【現場で求められるAPI設計の勘所~SIerエンジニアのための実践ガイド~】

SIer Tech Blog
2025年3月19日
【現場で求められるAPI設計の勘所~SIerエンジニアのための実践ガイド~】
SIerの現場では、システム間連携やマイクロサービス化の流れを受けて、APIの設計・開発の機会が増えています。本記事では、SIerエンジニアが押さえておくべきAPI設計のポイントを、実践的な観点から解説します。
1. API設計の基本原則
1.1 API設計で押さえるべき3つの視点
API設計において最も重要なのは、以下の3つの視点のバランスを取ることです:
-
ビジネス要件との整合性
- APIはビジネスプロセスを反映すべき
- 業務フローに沿った操作を提供する
- 将来の拡張性を考慮する
-
使いやすさ(Developer Experience)
- 直感的で理解しやすいインターフェース
- 一貫性のある命名規則
- 適切なドキュメント
-
保守性・運用性
- バージョン管理のしやすさ
- モニタリングと障害対応
- パフォーマンスチューニング
1.2 RESTful APIの設計原則
RESTful APIを設計する際の基本原則:
// URIの設計例
GET /api/v1/customers # 顧客一覧の取得
GET /api/v1/customers/{id} # 特定顧客の取得
POST /api/v1/customers # 新規顧客の作成
PUT /api/v1/customers/{id} # 顧客情報の更新
DELETE /api/v1/customers/{id} # 顧客の削除
// 関連リソースの表現
GET /api/v1/customers/{id}/orders # 特定顧客の注文一覧
POST /api/v1/customers/{id}/orders # 特定顧客の注文作成
1.2.1 リソース指向の設計
- リソースの明確な識別: URIでリソースを一意に特定
- HTTPメソッドの適切な使用: GET, POST, PUT, DELETEの使い分け
- ステートレス: セッション状態に依存しない
1.2.2 レスポンス設計
// 成功レスポンスの例
{
"status": "success",
"data": {
"customerId": "C001",
"name": "山田太郎",
"email": "yamada@example.com",
"createdAt": "2025-03-18T10:00:00Z"
}
}
// エラーレスポンスの例
{
"status": "error",
"code": "INVALID_PARAMETER",
"message": "無効なパラメータが指定されました",
"details": [
{
"field": "email",
"message": "メールアドレスの形式が不正です"
}
]
}
2. 要件定義とAPI設計プロセス
2.1 要件の整理と分析
2.1.1 ビジネス要件の把握
- 業務フローの理解
- 現行システムの分析
- ステークホルダーへのヒアリング
- 将来的な拡張性の検討
2.1.2 技術要件の整理
-
性能要件
- レスポンスタイム
- スループット
- 同時接続数
-
セキュリティ要件
- 認証・認可方式
- データ保護
- 監査ログ
2.2 API設計ドキュメントの作成
2.2.1 API仕様書の例(OpenAPI/Swagger)
openapi: 3.0.0
info:
title: 顧客管理API
version: 1.0.0
paths:
/customers:
get:
summary: 顧客一覧の取得
parameters:
- name: page
in: query
schema:
type: integer
- name: limit
in: query
schema:
type: integer
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
status:
type: string
data:
type: array
items:
$ref: '#/components/schemas/Customer'
post:
summary: 新規顧客の作成
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CustomerCreate'
responses:
'201':
description: 作成成功
components:
schemas:
Customer:
type: object
properties:
id:
type: string
name:
type: string
email:
type: string
3. セキュリティ設計
3.1 認証・認可の実装
3.1.1 JWT(JSON Web Token)による認証
// JWTトークンの生成
@Service
public class JwtTokenService {
private final String secretKey;
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getRoles())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24時間
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException e) {
return false;
}
}
}
3.1.2 OAuth 2.0による認可
// OAuth 2.0の設定例(Spring Security)
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/v1/public/**").permitAll()
.antMatchers("/api/v1/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
}
}
3.2 セキュリティヘッダーの設定
// セキュリティヘッダーの設定例
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.xssProtection()
.and()
.contentSecurityPolicy("default-src 'self'")
.and()
.frameOptions().deny()
.and()
.httpStrictTransportSecurity()
.includeSubDomains(true)
.maxAgeInSeconds(31536000);
}
}
4. エラーハンドリングとバリデーション
4.1 エラーハンドリング
4.1.1 共通エラーハンドラー
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException ex) {
ErrorResponse error = new ErrorResponse(
"VALIDATION_ERROR",
"入力値が不正です",
ex.getValidationErrors()
);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
"NOT_FOUND",
"リソースが見つかりません",
Collections.singletonList(ex.getMessage())
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse error = new ErrorResponse(
"INTERNAL_ERROR",
"内部エラーが発生しました",
Collections.singletonList(ex.getMessage())
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
4.2 入力バリデーション
4.2.1 リクエストバリデーション
// リクエストDTOの例
public class CustomerCreateRequest {
@NotBlank(message = "名前は必須です")
@Size(max = 100, message = "名前は100文字以内で入力してください")
private String name;
@NotBlank(message = "メールアドレスは必須です")
@Email(message = "メールアドレスの形式が不正です")
private String email;
@Pattern(regexp = "^[0-9]{10,11}$", message = "電話番号の形式が不正です")
private String phone;
// getters and setters
}
// コントローラーでのバリデーション
@RestController
@RequestMapping("/api/v1/customers")
public class CustomerController {
@PostMapping
public ResponseEntity<CustomerResponse> createCustomer(
@Valid @RequestBody CustomerCreateRequest request) {
// バリデーション済みのリクエストを処理
Customer customer = customerService.createCustomer(request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(CustomerResponse.from(customer));
}
}
5. パフォーマンスとスケーラビリティ
5.1 キャッシュ戦略
5.1.1 HTTPキャッシュ
// キャッシュヘッダーの設定例
@GetMapping("/api/v1/products/{id}")
public ResponseEntity<ProductResponse> getProduct(@PathVariable Long id) {
Product product = productService.findById(id);
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
.eTag(product.getVersion().toString())
.body(ProductResponse.from(product));
}
5.1.2 アプリケーションキャッシュ
// Spring Cacheの使用例
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
// データベースからの取得処理
return productRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product not found"));
}
@CacheEvict(value = "products", key = "#id")
public void updateProduct(Long id, ProductUpdateRequest request) {
// 更新処理
}
}
5.2 ページネーションと部分レスポンス
// ページネーションの実装例
@GetMapping("/api/v1/products")
public ResponseEntity<PageResponse<ProductResponse>> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String fields) {
Pageable pageable = PageRequest.of(page, size);
Page<Product> products = productRepository.findAll(pageable);
// フィールドフィルタリング
List<ProductResponse> responses = products.getContent().stream()
.map(product -> ProductResponse.from(product, fields))
.collect(Collectors.toList());
PageResponse<ProductResponse> response = new PageResponse<>(
responses,
products.getNumber(),
products.getSize(),
products.getTotalElements()
);
return ResponseEntity.ok(response);
}
6. API運用と監視
6.1 ログ設計
6.1.1 アクセスログ
// インターセプターによるアクセスログの実装
@Component
public class ApiLoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ApiLoggingInterceptor.class);
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String requestId = UUID.randomUUID().toString();
request.setAttribute("requestId", requestId);
logger.info("Request: [{}] {} {} (Client IP: {})",
requestId,
request.getMethod(),
request.getRequestURI(),
request.getRemoteAddr()
);
return true;
}
@Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
String requestId = (String) request.getAttribute("requestId");
logger.info("Response: [{}] Status: {} (Processing Time: {}ms)",
requestId,
response.getStatus(),
System.currentTimeMillis() - request.getAttribute("startTime")
);
}
}
6.2 メトリクス収集
// Prometheusメトリクスの設定例
@Configuration
public class MetricsConfig {
@Bean
public MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Counter orderCounter;
private final Timer orderProcessingTimer;
public OrderController(MeterRegistry registry) {
this.orderCounter = registry.counter("api.orders.created");
this.orderProcessingTimer = registry.timer("api.orders.processing_time");
}
@PostMapping
@Timed(value = "api.orders.create", description = "注文作成の処理時間")
public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
orderCounter.increment();
return orderProcessingTimer.record(() -> {
Order order = orderService.createOrder(request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(OrderResponse.from(order));
});
}
}
7. バージョニング戦略
7.1 URLベースのバージョニング
// URLパスでのバージョン管理
/api/v1/customers # Version 1
/api/v2/customers # Version 2
7.2 ヘッダーベースのバージョニング
@RestController
@RequestMapping("/api/customers")
public class CustomerController {
@GetMapping(headers = "API-Version=1")
public ResponseEntity<CustomerResponseV1> getCustomerV1(@PathVariable Long id) {
// Version 1の実装
}
@GetMapping(headers = "API-Version=2")
public ResponseEntity<CustomerResponseV2> getCustomerV2(@PathVariable Long id) {
// Version 2の実装
}
}
8. ドキュメント作成
8.1 Swaggerによる API ドキュメント
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.api"))
.paths(PathSelectors.ant("/api/**"))
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("顧客管理システム API")
.description("顧客情報の管理を行うためのRESTful API")
.version("1.0.0")
.build();
}
}
@RestController
@RequestMapping("/api/v1/customers")
@Api(tags = "顧客管理API")
public class CustomerController {
@ApiOperation(value = "顧客情報の取得", notes = "指定されたIDの顧客情報を取得します")
@ApiResponses({
@ApiResponse(code = 200, message = "成功"),
@ApiResponse(code = 404, message = "顧客が見つかりません")
})
@GetMapping("/{id}")
public ResponseEntity<CustomerResponse> getCustomer(
@ApiParam(value = "顧客ID", required = true)
@PathVariable Long id) {
// 実装
}
}
9. まとめ
API設計は、技術的な側面だけでなく、ビジネス要件やユーザビリティ、運用性など、多面的な考慮が必要な作業です。SIerの現場では特に以下の点に注意を払うことが重要です:
-
要件の明確化
- ビジネス要件の深い理解
- 将来的な拡張性の考慮
- ステークホルダーとの合意形成
-
設計品質の確保
- 一貫性のある設計方針
- 適切なセキュリティ対策
- 効果的なエラーハンドリング
-
運用性の考慮
- 監視とログ設計
- パフォーマンス最適化
- バージョン管理戦略
-
ドキュメント化
- API仕様書の整備
- 開発者向けガイドの作成
- サンプルコードの提供
これらの要素を適切にバランスを取りながら設計することで、長期的に維持可能で価値のあるAPIを提供することができます。
参考文献
- 「RESTful Web APIs」 - Leonard Richardson, Mike Amundsen
- 「Web API: The Good Parts」 - 水野貴明
- 「Clean Architecture」 - Robert C. Martin
- 「マイクロサービスパターン」 - Chris Richardson
API設計は継続的な学習と改善が必要な分野です。本記事で紹介した内容を基礎として、実際のプロジェクトでの経験を重ねながら、より良いAPI設計のスキルを磨いていってください。
関連記事
2025/3/25
【「動作保証はどこまで?」SIerのためのシステム保守の基本】
SIerエンジニアのためのシステム保守ガイド。業務システムの保守範囲の定義から具体的な保守活動まで、実践的なアプローチを解説します。
2025/3/24
【SIerが知るべきログ設計のベストプラクティス】
SIerエンジニアのためのログ設計ガイド。業務システムにおける効果的なログ設計から運用管理まで、実践的なベストプラクティスを解説します。
2025/3/23
【長年運用されている業務システムの"負債"とどう向き合うか?】
SIerエンジニアのための技術的負債管理ガイド。長年運用されてきた業務システムの負債を理解し、効果的に管理・改善していくための実践的なアプローチを解説します。