이전의 API 응답을 살펴보자.
기존 API 응답을 보자.
code:
message:
result:
응답 형태는 위와 같다. 이렇게 했을 때, 불편한 점이 발생했었다.
기존에 작성한 API인 회원 가입, 과연 Result가 필요할까..?
나는 아니라고 본다.
이유 >
1. 회원 가입을 진행한다.
2. 회원 가입이 완료된다.
3. 로그인을 한다.
우리가 설계한 회원으로서의 서비스 이용 워크 플로우다. 회원가입 이후 생성된 계정 정보를 사용할 이유가 없는데, 응답을 줘야할까?
해당 이유를 바탕으로 특정 서비스에는 응답의 결과가 필요 없다고 느꼈다.
// 결과가 필요한 경우
code: ,
message: ,
result:
// 결과가 필요 없는 경우
code: ,
meesage:
이런 형태로 응답을 준다면 code와 message가 재사용되므로 추상화 할 수 있겠다는 생각이 들었다. 하지만 이런 형태로 사용했을 경우, result가 필요 없을 때 code와 message만 응답해줘야 하는데 code와 message를 추상화 한다면 두가지 문제점이 발생한다.
code: ,
message: ,
result: null
그래서 가장 단순하고 직관적으로 작성하는 방법을 선택했다.
@Schema(description = "응답 상태")
public record StatusResponse (
@Schema(description = "응답 코드", example = "9999")
int code,
@Schema(description = "응답 코드", example = "응답 성공 메시지입니다.")
String message
) {
public static StatusResponse from(final StatusType statusType) {
return new StatusResponse(statusType.getCode(), statusType.getMessage());
}
}
---
@Schema(description = "API 응답")
public record API<T>(
@Schema(description = "응답 상태")
StatusResponse status,
@Schema(description = "응답 결과")
T result
) {
public static <T> API<T> of(final StatusType status, final T result) {
return new API<>(StatusResponse.from(status), result);
}
}
---
@Schema(description = "API 응답")
public record NoContent(
@Schema(description = "응답 상태")
StatusResponse status
) {
public static NoContent from(final StatusType status) {
return new NoContent(StatusResponse.from(status));
}
}
해당 방식은 SRP와 OCP, DIP가 잘 적용된 객체지향적 API Wrapper 설계라고 볼 수 있다.
1. 각 클래스는 단일 책임을 잘 수행하고 있다.
2. 제네릭을 사용하고 불변 record object를 사용하면서 OCP를 만족한다. 즉, 확장에는 열려있고 변경에는 닫혀있다.
3. StatusResponse는 interface로 작성된 StatusType을 의존하고 있고 Wrapper들에서 의존성을 주입해주고 있기 때문에 DIP 또한 잘작성되었다.
그런데 이전 포스트와 마찬가지로 해당 포스트도 작성하면서 조금 더 좋은 객체지향 설계로 개선할 수 있겠다는 생각이 들었다. 바로LSP를 적용할 수 있겠다는 것이다.
@Schema(description = "API 응답")
@Getter
public class NoContent {
@Schema(description = "응답 상태")
private final StatusResponse status;
protected NoContent(final StatusResponse status) {
this.status = status;
}
public static NoContent from(final StatusType status) {
return new NoContent(StatusResponse.from(status));
}
}
---
@Schema(description = "API 응답")
@Getter
public class API<T> extends NoContent {
@Schema(description = "응답 결과")
private final T result;
private API(final StatusResponse status, final T result) {
super(status);
this.result = result;
}
public static <T> API<T> of(final StatusType status, final T result) {
return new API<>(StatusResponse.from(status), result);
}
}
이런 방식으로 수정하면 LSP 또한 만족하고 추상클래스를 사용하지 않으므로 Swagger 또한 제대로 Convert 하면서 우리가 기대하는 결과를 구할 수 있다.
SOLID를 열심히 공부하고 내가 공부한 부분을 제대로 적용해 볼 수 있었던 것 같다. 당장 개발에서도 내가 성장하는 것이 느껴졌지만, 글을 작성하면서 제 3자의 시선으로 여유있게 바라보니 조금 더 좋은 개선사항이 보인다.