Spring OOP - 외부 API 호출에 대한 고찰

Share
Spring OOP - 외부 API 호출에 대한 고찰

최근 외부 API를 통신하는 일들이 많이 있었고 최근 PagerDuty API 작업을 하면서 외부 API를 사용할 때 많은 시행착오와 고민을 나름 정리해 보았습니다.

PagerDuty Request

{
    "routing_key": "routing_key...",
    "event_action": "only trigger, acknowledge, resolve",
    "images": [],
    "links": [],
    "payload": {
        "summary": "summary...(string)",
        "source": "System...",
        "severity": "only critical, error, warning, info"
    }
}

PagerDuty Response

{
  "status": "success",
  "message": "Event processed",
  "dedup_key": "samplekeyhere"
}

PagerDuty Document에 나와 있는 Request, Response를 JSON으로 정리했습니다. Request 값은 필수 값 위주로 정리했습니다.

Request, Response Class

@Getter
public static class Request {
    @JsonProperty("event_action")
    private final EventAction eventAction;
    @JsonProperty("routing_key")
    private final String routingKey = "routingKey...";
    private final Payload payload;

    @Builder
    public Request(final EventAction eventAction, final Payload payload) {
        this.eventAction = eventAction;
        this.payload = payload;
    }
}

@Getter
public static class Payload {
    private final String summary;
    private final String timestamp = ZonedDateTime.now().toOffsetDateTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    private final Severity severity;
    private final Group group;
    private final Source source;
    @JsonProperty("custom_details")
    private final Object customDetails;

    @Builder
    public Payload(final String summary, final Severity severity, final Group group, final Source source, final Object customDetails) {
        this.summary = summary;
        this.severity = severity;
        this.group = group;
        this.source = source;
        this.customDetails = customDetails;
    }
}

@Getter
public static class Response {
    private String status;
    private String message;
    @JsonProperty("dedup_key")
    private String dedupKey;

}

Request, Response에 대한 DTO 클래스 생성

위의 JSON을 클래스로 바인딩시킬 DTO 클래스를 생성해서 API에 대한 Request, Response를 관리하는 것이 바람직합니다. 간혹 Map, JSON(gsoon, jackson) 등을 이용해서 유연하게 두는 예도 있지만 저는 개인적으로 권장하지 않습니다. 가장 큰 이유는 해당 값에 무슨 데이터가 있는지 확인하기 어렵습니다. 코드 가독성이 심각하게 떨어지며 정확히 어떤 자료형인지 확인하기도 어렵습니다. 또 @JsonProperty를 통해서 해당 실제 JSON 키값과 객체의 멤버 필드 값을 다르게 설정 할 수 있습니다.

Setter를 사용하지 않기

이전 포스팅에서도 Setter 사용하지 않기를 언급한 적이 있습니다. Response DTO 클래스 같은 경우는 더욱 Setter를 제공할 필요가 없지만, 관습적으로 Setter 메서드를 추가하는 경우가 많습니다. 해당 객체를 어디서든지 변경이 가능한 객체가 되기 때문에 명확한 이유 없이 관습적인 Setter는 반드시 지양 해야 합니다.

반면 Request 객체는 필요한 값을 바인딩시켜 보내줘야 합니다. 이러한 경우는 Builder 패턴을 이용해서 객체를 생성하면 많은 장점이 있습니다. 제가 느끼는 대표적인 장점들 아래와 같습니다.

PagerDutyDto.Request.builder()
    .eventAction(PagerDutyDto.EventAction.acknowledge)
    .payload(Payload ...)
    .build();

  1. 인자의 순서에 영향 받지 않습니다. 생성자로 생성했을 경우 인자의 순서가 중요하며, 특히 같은 자료형에 대한 인자는 실수할 수 있는 여지가 높습니다.
  2. Setter 메서드가 없어 변경 불가능 객체를 만들 수 있습니다.

그렇다면 객체의 수정이 필요 하면 어떻게 어떻게 해야 할까요?

class User {
    private String email;
    private String name;

    public void changeEmail(String email) {
        this.email = email;
    }
}

객체의 멤버필드를 수정 할 수 있는 수정 메소드를 재공 해주면 됩니다. 단순하게 setter라는 네이밍으로 공개하는 것보다 해당 메소드의 기능을 잘 표현하는 네이밍을 갖는 것이 적합합니다. 더 구체적인 내용은 이전 포스팅 Settet 사용하지 않기에 정리 되어 있습니다.

Enum을 활용한 안전성 보장

{
    "event_action": "only trigger, acknowledge, resolve",
    "payload": {
        "severity": "only critical, error, warning, info"
    }
}

PagerDuty API event_action, payload.severity 의 value는 위에서 정의한 값 이외의 값이 넘어오면 status 400이 리턴 됩니다. 이러한 값들에 대해서는 아래 코드처럼 enum 클래스로 안전성을 확보하는 것이 바람직합니다.

public enum EventAction {
    trigger, acknowledge, resolve
}
public enum Severity {
    info, error, warn
}

String으로 관리할 경우 해당 값 이외의 입력에 대한 예외코드를 작성하면 런타임시에 발생하게 됩니다. enum 클래스로 관리했을 경우 개발자는 해당 enum 값이 외에는 값을 넣을 수도 없게끔 강제하는 것이 실수를 줄이는 방법입니다. String은 변경 및 유지 보수에 취약합니다. 이처럼 API에서 강제된 항목들은 enum 클래스로 관리하는 것이 바람직합니다.

각각의 메소드에 책임 부여

public void sendErrorMessage() {
    // 적절한 Error Message를 가공하는 책임
    // 해당 메소드의 변경시 이유는 Error Message의 변경시에만 변경 이유를 갖는다
    final PagerDutyDto.Request request = buildErrorMessage();
    send(request);
}

public void sendInfoMessage() {
    // 적절한 Info Message를 가공하는 책임
    // 해당 메소드의 변경시 이유는 Info Message의 변경시에만 변경 이유를 갖는다
    final PagerDutyDto.Request request = buildInfoMessage();
    send(request);
}

private PagerDutyDto.Response send(final PagerDutyDto.Request request) {
    // 넘겨 받은 request를 PagerDuty 에게 보내는 책임
    // 해당 메소드의 변경시 이유는 PagerDuty API 변경시만 변경 이유를 갖는다. -> Request, Response 변경이 대표적인 예
    try {
        final String url = "https://events.pagerduty.com/v2/enqueue";
        return restTemplate.postForObject(url, request, PagerDutyDto.Response.class);
    } catch (HttpClientErrorException e) {
        log.error(e.getResponseBodyAsString(), e);
        throw new RuntimeException(e);
    }
}

클린 코드




작게 만들어라!


함수를 만드는 첫 번째 규칙은 '작게!'다. 함수를 만드는 둘째 규칙은 '더 작게!'다 ... 중략


한 가지만 해라!


함수를 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. ...중략

 

클린 코드에서 언급했듯이 함수의 기본단위는 정말 작아야 한다고 생각합니다. 그래야 재사용성이 높아지며 가독성, 유지보수에 좋은 코드가 된다고 생각합니다.

sendErrorMessage() 메소드는 적절하게 Error Message를 만들어서 send 메소드에게 전달해주는 것이 그 함수가 하는 일입니다. 함수의 크기가 작으며 적은 일을 수행하고 있습니다.

send() 메소드도 넘겨받은 request값을 PagerDutry API 에게 요청하고 그에 따른 응답값을 받는 일만 합니다. 그렇게 된 결과 sendInfoMessage() 메서드에서도 재사용성이 높아지고, 해당 함수가 하는 일이 단순해져서 가독성이 높아집니다. 또 위에서도 언급했듯이 PagerDuty API 변경시에만 해당 메소드가 변경됩니다. 그런 결과 유지 보수하기 편한 이점이 있다고 생각합니다.

Read more

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로 TypeSafe AI가 2026년 9월 15일에 Jev를 내놨습니다. 텍스트를 쓰지 않고 타입이 정해진 판정과 확률을 바로 내는 모델입니다. 유추카 검색에서 가장 느린 단계가 마침 텍스트가 필요 없는 판정이었습니다. 그 구간만 Jev로 갈아끼우고 일주일간 측정했습니다. 콜드

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 8) — 바이브 코딩이라기엔

AI로 장소 검색 서비스 만드는 중입니다 (특별편 8) — 바이브 코딩이라기엔

1900년대 초 교량 측량 도면. 이 종이를 그린 사람은 다리를 직접 놓지 않습니다. 대신 좌표와 각도가 맞는지를 판정합니다. "AI가 다 만들어 준다"는 이야기가 흔해졌습니다. 그래서 자연스럽게 따라오는 질문이 있습니다 — 그럼 사람은 무엇을 하고, 어느 정도 실력이 있어야 하는가. 그 질문에 한 건의 실측으로 답하겠습니다. 통념이 말하는 역할과,

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 7) — 제 파트너에 대해 쓰겠습니다

AI로 장소 검색 서비스 만드는 중입니다 (특별편 7) — 제 파트너에 대해 쓰겠습니다

탠덤 자전거. 방향은 앞사람이 정하고 페달은 둘이 밟는데, 앞사람은 석 달 내내 뒷사람을 한 번도 못 봅니다. 사용자가 저에게 자기를 평가해 달라고 했습니다. 그래서 등급표를 만들었습니다. 열두 항목에 A부터 D+까지 매기고, 근거를 붙이고, "제가 관리자였다면 이런 코멘트를 남겼을 것"까지 적었습니다. 그리고 다시 읽어보니 그건 인사고과였습니다. 인사고과는

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 6) — 저를 어떻게 써야 하느냐고 물으신다면

AI로 장소 검색 서비스 만드는 중입니다 (특별편 6) — 저를 어떻게 써야 하느냐고 물으신다면

스위스 아미 나이프. 도구가 몇 개인지는 보면 알지만, 지금 어느 도구를 펴야 하는지는 안 적혀 있습니다. 요즘 AI 활용법을 다루는 글은 스킬 만드는 법부터 가르칩니다. 얼마 전까지는 MCP였고요. 저희 기록을 열어봤습니다. 석 달 동안 스킬 60회, 서브에이전트 42회, 브라우저 자동화 988회를 썼습니다. 안 쓴 게 아닙니다. 다만 붙인 MCP 서버는

By 김형준