티스토리 뷰

반응형

 

 

 

한 달간의 기업정보 조회 API 개발 내용을 정리하기 위한 포스팅입니다.

 

 

📌 2줄 요약

1) 외부 API 호출 시 QueryParameter의 다양한 조건을 어떻게 처리할 것인가? → UriComponentsBuilder
2) API 호출시 걸린 시간과 Endpoint uri를 로그로 기록하려면? → Interceptor

 

 

1.  개발 배경

신규 API의 역할을 한마디로 정리하면 미들웨어(Middleware)입니다. 자사 플랫폼 서비스(이하 A서비스라고 호칭)와 분리되어 운영되며, 주 업무는 외부 API 호출 및 DB 저장입니다. A서비스에서 기업 정보가 필요한 경우 신규 API를 통해서 정보를 조회하도록 강제해 직접적인 외부 API를 막아두었습니다. 이렇게 한 이유는 크게 두 가지가 있습니다.

 

  1. 서비스 간 영향 방지 A 서비스는 이미 운영 중이므로 신규 API의 개발로 인한 영향을 최소화해야 합니다. 만약 신규 API의 이슈로 인해 A 서비스가 문제를 겪는다면, B2B 고객에게 큰 타격을 줄 수 있습니다.
  2. 배포의 독립성 신규 API는 서비스 안정화까지 자주 배포가 이뤄질 예정입니다. 이를 A 서비스의 배포와 분리하면, 운영 중인 서비스에 대한 부담을 줄일 수 있습니다. 만약 배포 후 이슈가 발생하더라도, 신규 API만 롤백하면 되므로 훨씬 유연한 대응이 가능합니다.

 

 

 

 

Q. 왜 배치가 아닌 별도 API로 개발했나요?
외부 API는 다른 회사에서 제공하는 것으로, 대량의 배치 호출을 허용하지 않았습니다. 또한, 전국의 모든 기업 정보를 조회할 필요는 없고, 특정 기업 정보만 요청받기 때문에 별도 서비스로 개발하는 것이 더 적합하다고 생각했습니다.

 

 


 

 

2.  개발 히스토리

 

2.1 외부 API 호출 시 QueryParameter의 다양한 조건을 어떻게 처리할 것인가? → UriComponentsBuilder

외부 API의 엔드 포인트는 총 5가지로 기업개요, 기업채무, 기업재무, 기업검색, 기업등급 조회를 API로 제공합니다.

우선 각 엔드포인트를 호출 하기위해 RestClient를 이용, 각 EndPoint를 호출할 수 있도록 클래스를 생성했고 이를 Component로 등록했습니다. 등록한 Component를 필요로 하는 모든 Service layer에서 의존성을 받아 사용하도록 했습니다.

@RequiredArgsConstructor
@Service
public class EndPointService {
    private final EnpApiService enpApiService; // api 호출 Component 의존성 추가

    public TempResponse createEnpInfoHd001(EnpInfo enpInfo) {
        // api 요청 쿼리 파라미터 매핑
        final String bNum = enpInfo.getBnum(); // 기업정보 1)
        final String cNum = enpInfo.getCnum(); // 기업정보 2)
        
        final EnpInfo enpInfo = enpApiService.requestApiEnp(bNum, cNum); // End Poin 호출 및 Response

		...
   }
}

 

 

문제 발생: 클라이언트가 다양한 조건으로 요청할 때, 파라미터 조합이 여러 가지가 될 수 있습니다. String 조합 방식은 불변 객체가 계속 생성되어 성능 저하와 메모리 낭비가 우려되었고, StringBuilder 사용 시 가독성이 떨어지는 문제가 있었습니다.

@RequiredArgsConstructor
@Component
public class enpApiService {
    private final String url = "https://someUrl:0000";

    public ResponseEntity<Response> requestApiEnp(String bnum, String cnum) {
            String requestUrl = UriComponentsBuilder.fromUriString(url).path("/endpoint1")
                    .queryParam("bnum", bnum) // 기업정보 1)
                    .queryParam("cnum", cnum) // 기업정보 2)
                    .build().toString();
            return httpClient.get(requestUrl); // RestClient Get 메서드 호출
    }
}

 

UriComponentsBuilder를 사용해 Uri를 작성 시 코드 가독성이 높아지고, String+String 조합시 불변객체(String)의 무한 생성을 예방할 수 있었습니다. 또한 문자열 인코딩을 UriComponentsBuilder에서 자동으로 수행해 주기에 인코딩 에러를 예방할 수 있고, 궁극적으로 메서드 체이닝 형식과 체이닝메서드 명칭으로 개발자가 무엇을 넣어야 하는지 한눈에 알 수 있는 가독성 향상이 너무 마음에 들었습니다.

 

장점:

  • 메서드 체이닝 방식으로 가독성 향상
  • 파라미터가 자동으로 URL 인코딩됨
  • 조건에 따라 유연하게 파라미터를 추가/제거 가능
  • 유지보수 및 확장에 용이

 


 

 

2.2 API 호출 시 걸린 시간과 Endpoint URI 로그 기록 → Interceptor

API 호출 시간과 URI를 로그에 기록하는 기능이 필요했습니다. 기존에 각 Controller에 로직을 추가하는 방식으로 개발하려 했으나, 모든 컨트롤러를 수정해야 하는 번거로움이 있었습니다. 그래서 Interceptor를 사용해 문제를 해결하기로 결정했습니다.

 

Interceptor 사용 이유:

  • 요청 시작 시간(preHandle)을 request에 기록하고, 요청 종료 시(afterCompletion) API 처리 시간을 계산해 로그에 기록합니다.
  • 새로운 요구사항이 발생하면, Interceptor 하나만 수정하면 되므로 모든 Controller를 수정하는 수고를 덜 수 있습니다.

Controller에 도달하기 전 Interceptor에서 api 호출 시점의 시스템 시간을 requestheader의 attribute에 추가해 주고, api가 종료되었을 때의 시스템시간과 requestheader 담아두었던 시간을 기반으로 수행된 시간과 호출된 uri를 log에 기록하도록 구성했습니다.

 

향후 요구사항이 추가되었을 때, Intercetop만 수정하면 되기에 모든 Controller를 수정해야 하는 번거로움을 피할 수 있었습니다.

아래에 실제 코드 구현을 첨부하였으니 참고하시면 될 것 같습니다.

 

* WebConfig.class

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final LoggingInterceptor loggingInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loggingInterceptor).addPathPatterns("/**");
    }
}

 

 

* LoggingInterceptor.class

@Slf4j
@Component
public class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        request.setAttribute("startTime", System.currentTimeMillis());
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long executeTime = endTime - startTime;

        // Log the API call time and processing time
        log.info("API Call to [{}], took {} ms ",request.getRequestURI(), executeTime);
    }
}
Interceptor의 장점: 모든 Controller에 로직을 중복해서 추가할 필요 없이, Interceptor에서 공통 처리 로직을 구현할 수 있습니다.

 

 

 


 

3. 개선해야 할 점

API 호출 시 상태 관리를 위해 api_status 테이블과 ApiStatus 엔티티를 사용하고 있습니다. 하지만 모든 API 호출 전에 상태를 Processing으로 변경하고, 호출이 끝나면 다시 Completed로 변경하는 로직은 중복이 많고 유지보수가 어렵습니다.

 

개선 방향:
상태 변경 로직을 AOP(Aspect Oriented Programming)로 처리하거나,
Event를 활용하여 비동기로 상태를 업데이트하는 방식으로 변경을 고려 중입니다.

 

 

이상 전달 끝!

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함
반응형