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);
}
}