본문으로 건너뛰기

HTTP Port 정의

Basic Structure

port:
PortServiceName:
meta: http @url("baseUrl") [@auth("token")]
methods:
methodName:
meta: method @HttpEndpoint(url="endpoint", method="HTTP_METHOD", contentType="content-type")
# 또는 단축 형태
meta: method @get("/endpoint") # GET 메서드
meta: method @post("/endpoint") # POST 메서드
params:
paramName: ParamType [@body]
return: ReturnType

HTTP Annotations

Port-level Annotations

  • @url("baseUrl"): HTTP 서버의 기본 URL
  • @auth("token"): 기본 인증 토큰 (선택사항)

Method-level Annotations

  • @HttpEndpoint: 상세한 HTTP 설정
    • url: API 엔드포인트 경로
    • method: HTTP 메서드 (GET, POST, PUT, DELETE, PATCH)
    • contentType: Content-Type 헤더 (기본값: application/json)
    • paramNames: 파라미터 이름 목록 (자동 생성)

Shorthand Annotations

  • @get("url"): GET 메서드 단축 표기
  • @post("url"): POST 메서드 단축 표기
  • @put("url"): PUT 메서드 단축 표기
  • @delete("url"): DELETE 메서드 단축 표기

Parameter Annotations

  • @body: 요청 본문으로 전송할 파라미터 지정

HTTP Port Examples

1. 기본 JSON API 호출

dto:
UserRequest:
meta: dto
fields:
name: string
email: string

UserResponse:
meta: dto
fields:
id: long
name: string
status: string

port:
UserApiAdapter:
meta: http @url("https://api.example.com")
methods:
createUser:
meta: method @post("/users")
params:
body: UserRequest @body
return: UserResponse

getUser:
meta: method @get("/users/{id}")
params:
id: long
return: UserResponse

updateUser:
meta: method @HttpEndpoint(url="/users/{id}", method="PUT", contentType="application/json")
params:
id: long
body: UserRequest @body
return: UserResponse

2. 파일 업로드 (Multipart)

port:
FileUploadAdapter:
meta: http @url("https://api.fileservice.com") @auth("Token abc123")
methods:
uploadFile:
meta: method @HttpEndpoint(url="/upload", method="POST", contentType="multipart/form-data")
params:
body: org.springframework.util.MultiValueMap @body
return: String

uploadUserDocument:
meta: method @post("/users/{userId}/documents")
params:
userId: long
body: org.springframework.util.MultiValueMap @body
return: Map<String, Object>

3. 외부 인증이 필요한 API

port:
PaymentApiAdapter:
meta: http @url("https://api.payment.com") @auth("Bearer sk_test_123")
methods:
processPayment:
meta: method @post("/v1/payments")
params:
body: PaymentRequest @body
return: PaymentResponse

getPaymentStatus:
meta: method @get("/v1/payments/{paymentId}")
params:
paymentId: string
return: PaymentStatusResponse

4. 실제 외부 API 연동 예시 (Parts Catalog)

port:
PartsCatalogAdapter:
meta: http @url("https://api.parts-catalogs.com") @auth("TDTDTD-9800-2A6D1DB6")
methods:
getCatalogs:
meta: method @get("/v1/catalogs/") -- 차량 카탈로그(브랜드) 목록 정보
params:
return: java.util.List

getCarModelList:
meta: method @get("/v1/catalogs/{catalogId}/models") -- 브랜드별 차량 모델 목록 정보
params:
catalogId: String
return: java.util.List<PartInfo>

getCarInfoByVIN:
meta: method @get("/v1/car/info") -- 대차 번호별 정보
params:
q: String -- VIN or FRAME
return: java.util.List

HTTP Authentication

1. 기본 토큰 인증

port:
ApiService:
meta: http @url("https://api.example.com") @auth("Bearer your-token-here")

2. 설정파일(properties)에 의한 설정

# domainName과 adapterName은 해당 port 설정에 따라 변경 처리한다.
domainName.adapterName.authKey=auth_key_info

3. 복잡한(동적) 인증 - HttpAuthProvider 구현

@Configuration
public class CustomHttpAuthProvider {

@Bean("payment.PaymentApiAdapter.httpAuthProvider")
public HttpAuthProvider getHttpAuthProvider() {
return (inputObj, headers) -> {
// 동적 토큰 생성
String token = getTokenFromExternalService();
headers.add(Map.of("Authorization", "Bearer " + token));
headers.add(Map.of("X-API-Key", "custom-api-key"));
headers.add(Map.of("X-Client-Version", "1.0"));
return null;
};
}
}

4. HttpAuthProviderFactory 활용

@Configuration
public class HttpAuthConfig {

@Bean("domainName.adapterName.httpAuthProvider")
public HttpAuthProvider createBearerAuthProvider() {
return HttpAuthProviderFactory.createBearerToken("your-dynamic-token");
}

@Bean("domainName.adapterName2.httpAuthProvider")
public HttpAuthProvider createCustomAuthProvider() {
return HttpAuthProviderFactory.create("X-API-Key", "your-api-key");
}
}

Generated Java Interface

package com.example.domain.port;

import io.elasticore.runtime.port.*;
import com.example.domain.dto.*;

@ExternalService(protocol="http", id="demo.UserApiAdapter", url="https://api.example.com")
public interface UserApiAdapter {

@HttpEndpoint(url="/users", method="POST", contentType="application/json", paramNames="body")
UserResponse createUser(UserRequest body);

@HttpEndpoint(url="/users/{id}", method="GET", contentType="application/json", paramNames="id")
UserResponse getUser(Long id);
}

Spring Boot Configuration

HTTP 클라이언트 설정

# HTTP 연결 타임아웃 설정
elasticore.http.connect-timeout=5000
elasticore.http.read-timeout=30000
elasticore.http.write-timeout=30000

# HTTP 연결 풀 설정
elasticore.http.max-connections=100
elasticore.http.max-connections-per-route=20

# 로깅 설정
logging.level.io.elasticore.springboot3.http=DEBUG
logging.level.org.springframework.web.reactive.function.client=DEBUG

캐시 설정

elasticore.cache.http.enabled=true
elasticore.cache.http.max-size=1000
elasticore.cache.http.expire-after-write=300s

Advanced Features

HTTP 요청/응답 인터셉터

@Component
public class HttpRequestInterceptor {

@EventListener
public void handleHttpRequest(HttpRequestEvent event) {
log.info("HTTP Request: {} {}", event.getMethod(), event.getUrl());
event.getHeaders().add("X-Request-ID", UUID.randomUUID().toString());
}

@EventListener
public void handleHttpResponse(HttpResponseEvent event) {
log.info("HTTP Response: {} - {}", event.getStatusCode(), event.getResponseTime());
}
}

에러 처리 및 재시도

@Service
public class ResilientApiService {

@Autowired
private PaymentApiAdapter paymentApiAdapter;

@Retryable(value = {HttpTimeoutException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public PaymentResponse processPaymentWithRetry(PaymentRequest request) {
try {
return paymentApiAdapter.processPayment(request);
} catch (HttpClientErrorException e) {
if (e.getStatusCode() == HttpStatus.BAD_REQUEST) {
throw new PaymentValidationException("Invalid payment request", e);
}
throw e;
}
}

@Recover
public PaymentResponse recover(Exception ex, PaymentRequest request) {
log.error("Payment processing failed after retries", ex);
return PaymentResponse.failed("Service temporarily unavailable");
}
}

Security Considerations

Authentication Security

# 좋은 예: 환경 변수 사용
port:
PaymentApiAdapter:
meta: http @url("https://api.payment.com") @auth("${PAYMENT_API_KEY}")

# 나쁜 예: 하드코딩
port:
PaymentApiAdapter:
meta: http @url("https://api.payment.com") @auth("sk_live_12345...") # 보안 위험

Preventing Sensitive Data Logging

@Component
public class SecureHttpLoggingInterceptor {

private static final Set<String> SENSITIVE_HEADERS = Set.of(
"authorization", "x-api-key", "cookie"
);

@EventListener
public void handleHttpRequest(HttpRequestEvent event) {
Map<String, String> safeHeaders = event.getHeaders().entrySet().stream()
.filter(entry -> !SENSITIVE_HEADERS.contains(entry.getKey().toLowerCase()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

log.info("HTTP Request: {} {} Headers: {}",
event.getMethod(), event.getUrl(), safeHeaders);
}
}