[스프링부트] 디스코드 웹훅 (2) - API call 알림 (Interceptor, FeignClient)

2025. 1. 10. 14:45·백엔드

지난 시간에는 서버 500 Internal Server Error를 디스코드 서버로 전송해줬다.

 

이번 시간에는 백엔드 API call을 디스코드로 전송해보자!


⭐️ 배경

🙋‍♀️(프론트엔드) 흠... 한 화면(페이지) 접속 시, 몇 개의 API가 호출되는지 궁금해요

🙋‍♀️(프론트엔드) 백엔드 분들, API 로그 확인해주실 수 있을까요?

 

🙋‍♀️(백엔드) 물론이죠! 잠시만 기다려주세요

 

백엔드 동료의 빠른 조치 덕분에,

API call 시 로그를 콘솔을 통해 확인할 수 있었다.

 

 

✅ 동료의 조치 방법

기존 스프링부트 실행 시, API call 발생 시 log가 발생하지 않는다.

➔ Spring Intercept를 이용해 API Call 발생 시, HTTP 요청을 가로채 log로 남긴다

 

 

✅ 나의 발전

🙋‍♀️ (프론트엔드) 또 요청드려도 되죠,,,? 🥹

🙋‍♀️ (백엔드) 앗, 디스코드로 만들어 드릴게요!

 

그렇다.

디스코드에 API Call 발생 시 알림을 덧붙이는 작업을 해보도록 하자!


⭐️고민

동료가 만든 Spring Interceptor 방식을 살릴까? 말까? 

를 고민하기에 앞서, 

 

어떻게 해야 discord로 전송하는 것이 효율적인 방식인지를 고민해보기로 했다.

 

✅ 디스코드에 데이터를 보내는 방식 : Interceptor vs Filter

찾아보니, 보통 이 두 가지 방법으로 웹 서버 ➔ 디스코드로 데이터(알림)를 전송했다.

 

둘 다 비슷한 점은 HTTP 요청을 가로채서 ➔ 디스코드로 전송한다는 점이고,

다른 점은 언제 HTTP 요청을 가로채는지이다.

 

https://goddaehee.tistory.com/154

이 블로그에 따르면,

Filter ➔ Interceptor ➔ AOP ➔ Interceptor ➔ Filter 순서로 

사용자의 API 요청이 들어오고 처리된다고 한다.

Filter, Interceptor, AOP의 흐름 ❘ https://goddaehee.tistory.com/154

 

Filter 방식

- Spring MVC에서 관리하는 영역이 아님!

- 웹 컨테이너에서 동작

- request, response 조작 가능

 

Interceptor 방식

- Filter 이후, Spring MVC에서 관리하는 영역에 속함

- 스프링 컨텍스트에서 동작

- 인터셉터가 등록되어 있다면, Dispatcher Servlet에서 인터셉터를 순서대로 거치고 그 후 controller가 실행됨

- request, response 조작 불가능

 

두 방식을 포함해 Filter, Interceptor, AOP의 특징을 더 알아보기보다는

이 블로그에서는 

그래서 어떤 방식을 선택할지 고민해보자!

 

✅ 그래서 어떤 방식을 선택할까?

Interceptor 방식을 선택하려고 한다!

 

그런 생각을 하게 된 여정은 

 

1. request, response를 가공하지 않고, 요청이 들어올 때 '요청 들어왔어요' 라는 알림을 주고 싶다

➔ Filter, Interceptor 방식 상관 x

 

2. 프론트엔드 분들을 위한 백엔드 API call 알림이므로, controller로 가는 요청에 대해 다루고 싶다

➔ Filter, Interceptor 방식 상관 x

    (만일, controller 를 제외한 모든 HTTP 요청을 로깅을 위함이었다면 Filter가 더 적합했을 것)

 

그렇다면 Filter, Interceptor 방식이 크게 상관이 없으므로,

이미 팀원이 구현해놓은 Interceptor 방식은 살리고,

어떻게 디스코드 서버로 전송할지를 덧붙이기로 결정했다.

 

✅ 디스코드 서버로 어떻게 전송할까? : FeignClient vs RestTemplate vs WebClient

서비스(서버) 간 HTTP 통신에 있어서

주로 feign client 방식, Rest template 방식, WebClient 방식을 사용하는 것을 찾을 수 있었다.

(MSA 구조에서의 마이크로 서비스간 통신에서도 이 방식을 사용한다고 한다)

 

FiegnClient

Netflix에서 개발한, Spring Cloud Framework에서 제공하는 비동기식 클라이언트 라이브러리

 

RestTemplate

Spring Framework(3~)에서 제공하는 동기식 클라이언트 라이브러리 

 

WebClient

Spring Framework(5~)에서 제공하는 비동기식/함수형 클라이언트 라이브러리

 

이중에서 FeignClient를 선택했다!

 

성능 이슈를 위한 비동기 방식 + interface를 작성하고 annotation을 선언하기만 하면 되는 간편함

으로 인해 손쉽게 구현할 수 있을 것 같아 선택했다!

(자세한 내용은 이 블로그를 참고해보라!)


⭐️ 구현 과정

1. Interceptor로 API call 가로채기 (이미 구현완료!)

2. FeignClient 만들어, API call 발생 시 디스코드로 전송

 

✅ 1. interceptor로 API call 가로채기

팀원이 이미 구현해놨지만, 코드만 살펴보자!

 

1. WebConfig.java 추가

@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final ObjectProvider<ApiLoggingInterceptor> apiLoggingInterceptorProvider;

    public WebConfig(ObjectProvider<ApiLoggingInterceptor> apiLoggingInterceptorProvider) {
        this.apiLoggingInterceptorProvider = apiLoggingInterceptorProvider;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        ApiLoggingInterceptor apiLoggingInterceptor = apiLoggingInterceptorProvider.getIfAvailable();
        if (apiLoggingInterceptor != null) {
            registry.addInterceptor(apiLoggingInterceptor)
                    .addPathPatterns("/api/**");
        }
    }
}

/api/** 경로로 들어오는 모든 요청에 대해 ApiLoggingInterceptor를 적용하도록 인터셉터를 등록시키자

 

 

2. ApiLoggingInterceptor.java 추가

@RequiredArgsConstructor
@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) throws Exception {
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String logMessage = String.format("API Called: [%s %s]", method, uri);
        logger.info(logMessage);
        return true;
    }
}

/api/** 경로로 요청이 들어오면 preHandle이 실행되는데,

preHandle에서는 요청 메소드, URI를 로깅한다

 

 

✅ 2. FeignClient 만들어, API call 발생 시 디스코드로 전송

 

1. 디스코드 채널 및 웹훅 생성

지난시간과 똑같이 디스코드 채널, 웹훅을 만들어주자!

 

 

2. application.yml 변경 (디스코드 웹훅 추가)

logging:
  discord-api-call:
    webhook-url: (디스코드 웹훅 URL)
    name: discord-feign-client

1번에서 생성한 디스코드 웹훅 주소를 넣어준다 !

 

 

3. DiscordFiegnClient.java 인터페이스 생성

@FeignClient(name = "${logging.discord-api-call.name}", url = "${logging.discord-api-call.webhook-url}")
public interface DiscordFeignClient {
    @PostMapping
    void sendMessage(@RequestBody DiscordMessage discordMessage);
}

디스코드 웹훅으로 메시지를 전송하는 코드를 생성한다!

 

 

4. DiscordMessage.java DTO 만들기 

public record DiscordMessage (
    String content
){
    public static DiscordMessage createDiscordMessage(String content) {
        return new DiscordMessage(content);
    }
}

주의할 점은, 메시지 필드를 content라고 해야 한다는 점이다 !

 

만일 content가 아닌 값으로 필드 명을 선택하면

feign.FeignException$BadRequest: [400 Bad Request] during [POST] to [디스코드웹훅주소] [DiscordFeignClient#sendMessage(DiscordMessage)]: [{"message": "Cannot send an empty message", "code": 50006}]

다음과 같은 400 Bad Request : "Cannot send an empty message" 가 뜰 것이다..... (경험담)

 

 

5. DiscordMessageProvider.java 만들기

@RequiredArgsConstructor
@Component
public class DiscordMessageProvider {
    private final DiscordFeignClient discordFeignClient;

    public void sendMessage(String message) {
        DiscordMessage discordMessage = createDiscordMessage(message);
        sendMessageToDiscord(discordMessage);
    }

    private void sendMessageToDiscord(DiscordMessage discordMessage) {
        discordFeignClient.sendMessage(discordMessage);
    }
}

sendMessage: String 형태의 메시지를 (로그 데이터) ➔ discordMessage DTO로 변환하고

sendMessageToDiscord: 실제로 디스코드로 전송하는 코드를 작성한다

 

 

6. 기존 인터셉터 코드(ApiLoggingInterceptor.java) 변경 (Interceptor - feign client 연결)

@RequiredArgsConstructor
@Component
public class ApiLoggingInterceptor implements HandlerInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(ApiLoggingInterceptor.class);
    private final DiscordMessageProvider discordMessageProvider;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String logMessage = String.format("API Called: [%s %s]", method, uri);
        logger.info(logMessage);

        // discord trigger
        discordMessageProvider.sendMessage(logMessage);
        return true;
    }
}

 

필드로 discordMessageProvider를 추가하고,

Intercept한 API method과 URI를 디스코드로 전송한다 !


⭐️ 결과

디스코드로 API call이 잘 오는 것을 확인할 수 있다 ☺️


⭐️ 여담

'프론트엔드에서 이 정보가 왜 궁금하지?' 라는 생각부터 들었다.

 

이유를 여쭤보니,

프론트엔드에 적용한 API 캐싱이 제대로 동작하는지 테스트하기 위함이라고 하셨다.

(앱 프론트엔드에서 API call 수를 줄여 더 나은 UX를 만들기 위함이라고 한다)

 

동시에

플러터 위젯리빌딩 / 상태 관리도 언급하셨다

 

gpt를 통해 이 개념이 API 캐싱과 무슨 연관이 있는지 찾아보았다.

 

(gpt) 플러터 위젯 리빌딩과 상태 관리가 API 캐싱과 관련 있는 이유

  1. API 요청 결과를 상태로 관리:
    • API로 데이터를 받아오면 이를 앱의 상태로 저장합니다.
    • 상태 관리 도구를 사용해, 상태가 업데이트될 때 관련된 위젯만 리빌딩합니다.
    • 캐싱된 데이터를 상태로 유지하면 리빌딩할 때 빠르게 데이터를 제공할 수 있습니다.
  2. 효율적인 UI 업데이트:
    • 캐싱된 데이터를 활용하면, 위젯 리빌딩 시 네트워크 요청 없이 UI를 빠르게 업데이트할 수 있습니다.
    • 예: 사용자가 화면을 이동했다가 다시 돌아왔을 때, API를 다시 호출하지 않고 캐싱된 데이터를 사용하여 위젯을 즉시 리빌딩합니다.
  3. 불필요한 리빌딩 방지:
    • 상태 관리 도구는 상태가 바뀌었을 때만 필요한 위젯을 리빌딩하도록 돕습니다.
    • 캐싱 덕분에 상태 변경이 줄어들면, 리빌딩도 줄어들어 성능이 향상됩니다.

오, 그러니까 

웹 사이트의 UI는 상태 관리를 통해 각 위젯이 항상 새로운 상태임을 보장하고,

특정 위젯의 상태가 바뀔 때에만 해당 위젯에 대해 새 상태를 요청하도록 하는 것이구나!

(신기하다)

프론트엔드, 특히 웹은 잘 몰랐는데 이번 기회에 알게 되어 매우 흥미롭다.

 

백엔드에서도 이번 경험으로 화면당 API call 수를 확인할 수 있었는데,

한 화면에서 특히 API call 수가 많은 것 같으니 새로운 API를 만드는 것을 고려해야겠다.


⭐️ 참고자료

filter, intercept 비교 (*이용사례, 메소드 종류 포함) https://dev-coco.tistory.com/173

 

filter, interceptor, AOP 비교와 흐름 https://goddaehee.tistory.com/154

 

RestTemplate vs FeignClient (*특징, 비교 포함) https://velog.io/@choiyunh/MSA-서비스간-통신시-RestTemplate-vs-FeignClient

 

RestTemplate vs FeignClient vs WebClient https://velog.io/@wlsgur1533/RestTemplate-WebClient-FeignClient-를-비교-OAuth-로그인-예시로

 

'백엔드' 카테고리의 다른 글

[스프링부트] 디스코드 웹훅 (1) - 에러 로그 전송 (logback)  (1) 2025.01.07
[DB 해킹] MySQL DB 털린 썰 풉니다 (RECOVER_YOUR_DATA)  (3) 2024.12.23
MySQL 연결 - characterEncoding=utf8mb4 쓰지 마세요!  (1) 2024.12.12
[스프링부트] Soft delete 구현  (3) 2024.12.09
[스프링부트] Spring Batch와 적용방법  (4) 2024.12.09
'백엔드' 카테고리의 다른 글
  • [스프링부트] 디스코드 웹훅 (1) - 에러 로그 전송 (logback)
  • [DB 해킹] MySQL DB 털린 썰 풉니다 (RECOVER_YOUR_DATA)
  • MySQL 연결 - characterEncoding=utf8mb4 쓰지 마세요!
  • [스프링부트] Soft delete 구현
KyuminKim
KyuminKim
컴퓨터공학과 학생의 이모저모 개발 일지 📝
  • KyuminKim
    이모저모
    KyuminKim
  • 전체
    오늘
    어제
    • 분류 전체보기 (53)
      • 프로젝트 (2)
        • first-blog (2)
      • 클라우드 (22)
        • 도커 (14)
        • 쿠버네티스 (5)
        • AWS (2)
      • 알고리즘 (5)
        • 코드트리 (0)
        • 프로그래머스 (5)
      • 백엔드 (8)
      • 프론트엔드 (2)
      • 보안 (3)
        • 드림핵 (2)
      • python (3)
      • 네트워크 (1)
      • 기타 (6)
        • 2025 프로펙트 부트캠프(1차) | 클라우드 엔.. (0)
        • OSSCA | 2024 오픈소스 컨트리뷰션 아카데.. (0)
        • WIK (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    알고리즘
    character_set_server
    코드트리조별과제
    코딩테스트
    cannot send an empty message
    고랭
    characterencoding
    오블완
    2024 당근 테크 밋업
    탈퇴구현
    amazonlinux
    인코딩
    DB
    티스토리챌린지
    EC2
    도커
    DP
    urf8
    쿠버네티스
    진단평가
    도커파일
    MySQL
    코딩트리조별과제
    docker
    코드트리
    파이썬
    recover_your_data
    주간레포트
    자료구조
    apiserver-runtime
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.2
KyuminKim
[스프링부트] 디스코드 웹훅 (2) - API call 알림 (Interceptor, FeignClient)
상단으로

티스토리툴바