사내 서비스는 마이크로서비스 아키텍처 환경으로 구성이 된 상태이다.
처음에 서비스를 분리했을 때는 한 개의 공통 코드에서 여러 서비스에 복붙이 되면서 중복된 코드가 발생하여도 동일한 로직으로 동일한 코드를 가지고 있었는데 역시 시간이 점점 지날수록 조금씩 변하더니 나중에는 동일한 코드인가 싶은 것들도 많아지게 되었다.
위와 같은 현상 때문에 공통된 코드를 관리를 어떤 식으로 해야 할지 고민이 되기 시작했고 고민을 하던 중에 공통 코드에 대해서 라이브러리 화해서 관리하자는 아이디어가 나왔다.
한 번에 모든 공통 코드를 라이브러리로 분리하기에는 범위가 너무 넓고 적용 부담도 컸기 때문에, 먼저 모든 API 서버에서 공통적으로 사용하는 응답 값 처리 로직을 라이브러리로 분리하여 시범적으로 적용해 보기로 했다.
우선 기존 API 요청 성공/실패 시 응답값 형태이다.
// 성공
{
"code" : "SUCCESS",
"message" : "정상적으로 처리된 상태입니다.",
"item" : {
"userId" : 1
"email" : "1@example.com"
}
}
// 실패
{
"code": "UNKNOWN_ERROR",
"message": "정의하지 않은 에러가 발생했습니다. 증상이 계속되면 관리자에게 문의해주세요.",
"transactionId": "txn_1234567890"
}
그에 따라서 DTO 객체를 만들면 아래와 같은 형태가 된다.
@Getter
public class ResponseDataDto<T> implements Serializable {
private static final long serialVersionUID = 4243108899022411721L;
@JsonProperty("code")
private String code;
@JsonProperty("message")
private String message;
@JsonProperty("item")
private T item;
private ResponseDataDto(String code, String message, T item ) {
this.code = code;
this.message = message;
this.item = item;
}
public static <T> ResponseDataDto<T> of(String code, String message, T item){
return new ResponseDataDto<T>(code, message, item);
}
// ... 기타 공통화 한 메소드
}
@Getter
public class ResponseErrorDto implements Serializable {
private static final long serialVersionUID = 4243108899022411721L;
@JsonProperty("code")
private String code;
@JsonProperty("message")
private String message;
@JsonProperty("transactionId")
private String transactionId;
protected ResponseErrorDto(){
}
protected ResponseErrorDto(String code, String message, String transactionId) {
this.code = code;
this.message = message;
this.transactionId = transactionId;
}
public static ResponseErrorDto of(String code , String message, String transactionId) {
return new ResponseErrorDto(code, message, transactionId);
}
public static ResponseErrorDto of(String code , String message) {
return of(code, message, null);
}
// ... 기타 공통화 한 메소드
}
이렇게 첫 라이브러리로 만들 코드가 완성이 된 상태여서 배포를 하려고 보니 네이밍 / 버전 관리 / 기타 등의 엄청난 일들이 남아 있었다.
첫번째로 고민했던 부분은 네이밍에 대한 부분이다. 네이밍이라는 게 처음에 잘 정하지 않으면 나중에 참 문제가 될 법한 부분이어서 어디 참고를 할 부분이 없나 확인하던 중 우리가 아주 친숙하게 쓰는 스프링이 라이브러리로 아주 기가 막히게 구분해서 관리가 되고 있는 상태여서 스프링 코드를 확인하면서 최대한 참고하기로 했다.
참고를 하다보니 해당 코드는 HTTP의 응답값에 대한 라이브러리라서 group id는 회사도메인.(프로젝트). web.http로 정했다.
두 번째로 고민했던 부분은 버전 관리 부분이었다.
처음에는 단순하게 1.0.0으로 시작하면 되겠지 싶었지만, 막상 배포를 하려고 하니 버전을 어떤 기준으로 올릴지, 그리고 개발 중인 버전은 어떻게 관리할지 등 세부적인 정책이 필요하다는 걸 느꼈다. 그래서 버전 관리 방식 중 하나인 Semantic Versioning 규칙을 도입하기로 했다.
해당 규칙은 MAJOR.MINOR.PATCH 형식으로 관리하고 각각의 의미는 아래와 같다.
- MAJOR - 이전 버전과 호황되지 않는 변경이 있는 경우
- MINOR - 새로운 기능이 추가되었지만 기존과의 호환성은 유지되는 경우
- PATH - 버그 수정이나 내부적인 변경처럼 외부에 영향을 주지 않는 경우
그다음으로는 배포 방식에 대한 고민이었다.
원래 목적은 버전 관리를 깃 커밋 메시지 기반으로 자동화하고 Nexus에 자동으로 배포하는 것이었지만 현재 프로젝트의 규모가 크지 않고 관리를 해줄 개발자가 많은 편이 아니라 해당 기능까지는 구현하지 않기로 했다.
그 대신 SNAPSHOT 버전은 라이브러리를 개발하는 개발자가 로컬에서 Nexus에 덮어쓰기 가능하도록 설정했고 정식 릴리스 버전은 덮어쓰기가 불가능하도록 설정한 뒤 Nexus UI를 통해 수동으로 배포하는 방식으로 관리하고 있다.
위와 같은 고민을 하나씩 해결해 가며 코드 내에서 공통화할 수 있는 부분들은 라이브러리로 분리하여 관리하고 있는 상태이며 현재는 동시성 제어용 concurrency 모듈, 공통 로그 추적용 log-trace 모듈, 마이크로 서비스의 인증을 위한 ouath2-resource-server 등과 같은 라이브러리들이 구현되어 실제 서비스에서 사용되고 있다.