[스프링 핵심 원리 - 고급편] #2 예제 만들기 #645
Develop-KIM
started this conversation in
동환
Replies: 1 comment
|
수고하셨습니다! |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
예제 프로젝트 만들기 - V0
상품을 주문하는 프로세스로 가정하고, 일반적인 웹 애플리케이션에서
Controller -> Service -> Repository로 이어지는 흐름을 최대한 단순하게 만들어보자.
OrderRepositoryV0
@Repository: 컴포넌트 스캔의 대상이 된다. 따라서 스프링 빈으로 자동 등록된다.sleep(1000): 리포지토리는 상품을 저장하는데 약 1초 정도 걸리는 것으로 가정하기 위해 1초 지연을 주었다. (1000ms)itemId의 값이ex로 넘어오면IllegalStateException예외가 발생하도록 했다.OrderServiceV0
@Service: 컴포넌트 스캔의 대상이 된다.OrderControllerV0
@RestController: 컴포넌트 스캔과 스프링 Rest 컨트롤러로 인식된다.@RequestMapping("/v0"): V0 으로 공통 시켜주었다./request메서드는 HTTP 파라미터로itemId를 받을 수 있다.실무에서 일반적으로 사용하는 컨트롤러 서비스 리포지토리의 기본 흐름을 만들었다.
로그 추적기 - 요구사항 분석
요구사항은 로그 추적기를 만드는 것이다.
애플리케이션이 커지면서 점점 모니터링과 운영이 중요해지는 단계이다. 특히 최근 자주 병목이 발생하고 있다.
어떤 부분에서 병목이 발생하는지, 그리고 어떤 부분에서 예외가 발생하는지를 로그를 통해 확인하는 것이 점점 중요해지고 있다.
기존에는 개발자가 문제가 발생한 다음에 관련 부분을 어렵게 찾아서 로그를 하나하나 직접 만들어서 남겼다.
로그를 미리 남겨둔다면 이런 부분을 손쉽게 찾을 수 있을 것이다. 이 부분을 개선하고 자동화 하는 것이 목표이다.
요구사항
예시
로그 추적기 V1 - 프로토타입 개발
애플리케이션의 모든 로직에 직접 로그를 남겨도 되지만, 그것보다는 더 효율적인 개발 방법이 필요하다.
특히 트랜잭션 ID와 깊이를 표현하는 방법은 기존 정보를 이어 받아야 하기 때문에 단순히 로그만 남긴다고 해결할 수 있는 것은 아니다.
요구사항에 맞추어 애플리케이션에 효과적으로 로그를 남기기 위한 로그 추적기를 개발해보자.
프로토타입 버전을 먼저 개발해보려고 한다. 로그 추적기를 위한 기반 데이터를 가지고 있는
TraceId,TraceStatus클래스를 만들어보자.TraceId
TraceId 클래스
로그 추적기는 트랜잭션ID와 깊이를 표현하는 방법이 필요하다.
여기서는 트랜잭션ID와 깊이를 표현하는 level을 묶어서
TraceId라는 개념을 만들었다.TraceId는 단순히id(트랜잭션ID)와level정보를 함께 가지고 있다.UUID
TraceId를 처음 생성하면createId()를 사용해서 UUID를 만들어낸다. UUID가 너무 길어서 여기서는 앞 8자리만 사용한다.이 정도면 로그를 충분히 구분할 수 있다. 여기서는 이렇게 만들어진 값을 트랜잭션ID로 사용한다.
createNextId()
다음
TraceId를 만든다. 예제 로그를 잘 보면 깊이가 증가해도 트랜잭션ID는 같다. 대신에 깊이가 하나 증가한다.실행 코드:
new TraceId(id, level + 1)따라서
createNextId()를 사용해서 현재TraceId를 기반으로 다음TraceId를 만들면id는 기존과 같고,level은 하나 증가한다.createPreviousId()
createNextId()의 반대 역할을 한다.id는 기존과 같고,level은 하나 감소한다.isFirstLevel()
첫 번째 레벨 여부를 편리하게 확인할 수 있는 메서드
TraceStatus
TraceStatus 클래스: 로그의 상태 정보를 나타낸다.
로그를 시작하면 끝이 있어야 한다.
TraceStatus는 로그를 시작할 때의 상태 정보를 가지고 있다. 이 상태 정보는 로그를 종료할 때 사용된다.traceId: 내부에 트랜잭션ID와 level을 가지고 있다.startTimeMs: 로그 시작시간이다. 로그 종료시 이 시작 시간을 기준으로 시작~종료까지 전체 수행 시간을 구할 수 있다.message: 시작시 사용한 메시지이다. 이후 로그 종료시에도 이 메시지를 사용해서 출력한다.TraceId,TraceStatus를 사용해서 실제 로그를 생성하고, 처리하는 기능을 개발해보자.HelloTraceV1
HelloTraceV1을 사용해서 실제 로그를 시작하고 종료할 수 있다. 그리고 로그를 출력하고 실행시간도 측정할 수 있다.@Component: 싱글톤으로 사용하기 위해 스프링 빈으로 등록한다. 컴포넌트 스캔의 대상이 된다.공개 메서드
로그 추적기에서 사용되는 공개 메서드는 다음 3가지이다.
begin(..)end(..)exception(..)하나씩 자세히 알아보자
TraceStatus begin(String message)TraceStatus를 반환한다.void end(TraceStatus status)TraceStatus)를 전달 받는다.이 값을 활용해서 실행 시간을 계산하고, 종 료시에도 시작할 때와 동일한 로그 메시지를 출력할 수 있다.
void exception(TraceStatus status, Exception e)TraceStatus,Exception정보를 함께 전달 받아서 실행시간, 예외 정보를 포함한 결과 로그를 출력한다.비공개 메서드
complete(TraceStatus status, Exception e)end(),exception(), 의 요청 흐름을 한곳에서 편리하게 처리한다. 실행 시간을 측정하고 로그를 남긴다.String addSpace(String prefix, int level): 다음과 같은 결과를 출력한다.-->|-->| |--><--|<--| |<--<X-|<X-| |<X-참고로
HelloTraceV1는 아직 모든 요구사항을 만족하지는 못한다.테스트 작성
HelloTraceV1Test
테스트 코드를 보면 로그 추적기를 어떻게 실행해야 하는지, 그리고 어떻게 동작하는지 이해가 될 것이다.
begin_end() - 실행 로그
begin_exception() - 실행 로그
로그 추적기 V1 - 적용
OrderControllerV1
HelloTraceV1 trace:HelloTraceV1을 주입 받는다.참고로
HelloTraceV1은@Component애노테이션을 가지고 있기 때문에 컴포넌트 스캔의 대상이 된다. 따라서 자동으로 스프링 빈으로 등록된다.trace.begin("OrderController.request()"): 로그를 시작할 때 메시지 이름으로 컨트롤러 이름 + 메서드 이름을 주었다.이렇게 하면 어떤 컨트롤러와 메서드가 호출되었는지 로그로 편리하게 확인할 수 있다.
trace.begin(),trace.end()코드 두 줄만 적용하면 될 줄 알았지만, 실상은 그렇지 않다.trace.exception()으로 예외까지 처리해야 하므로 지저분한try,catch코드가 추가된다.begin()의 결과 값으로 받은TraceStatus status값을end(),exception()에 넘겨야 한다.결국
try,catch블록 모두에 이 값을 넘겨야한다. 따라서try상위에TraceStatus status코드를 선언해야 한다.만약
try안에서TraceStatus status를 선언하면try블록안에서만 해당 변수가 유효하기 때문에catch블록에 넘길 수 없다.따라서 컴파일 오류가 발생한다.
throw e: 예외를 꼭 다시 던져주어야 한다. 그렇지 않으면 여기서 예외를 먹어버리고, 이후에 정상 흐름으로 동작한다.로그는 애플리케이션에 흐름에 영향을 주면 안된다. 로그 때문에 예외가 사라지면 안된다.
OrderServiceV1
OrderRepositoryV1
정상 실행 로그
예외 실행 로그
남은 문제
요구사항
모든 PUBLIC 메서드의 호출과 응답 정보를 로그로 출력애플리케이션의 흐름을 변경하면 안됨로그를 남긴다고 해서 비즈니스 로직의 동작에 영향을 주면 안됨메서드 호출에 걸린 시간정상 흐름과 예외 흐름 구분예외 발생시 예외 정보가 남아야 함아직 구현하지 못한 요구사항은 메서드 호출의 깊이를 표현하고, 같은 HTTP 요청이면 같은 트랜잭션 ID를 남기는 것
아직 구현하지 못한 요구사항은 메서드 호출의 깊이를 표현하고, 같은 HTTP 요청이면 같은 트랜잭션 ID를 남기는 것이다.
이 기능은 직전 로그의 깊이와 트랜잭션 ID가 무엇인지 알아야 할 수 있는 일이다.
예를 들어서
OrderController.request()에서 로그를 남길 때 어떤 깊이와 어떤 트랜잭션 ID를 사용했는지를그 다음에 로그를 남기는
OrderService.orderItem()에서 로그를 남길 때 알아야한다.결국 현재 로그의 상태 정보인
트랜잭션ID와level이 다음으로 전달되어야 한다.정리하면 로그에 대한 문맥(
Context) 정보가 필요하다.로그 추적기 V2 - 파라미터로 동기화 개발
트랜잭션ID와 메서드 호출의 깊이를 표현하는 하는 가장 단순한 방법은 첫 로그에서 사용한
트랜잭션ID와level을 다음 로그에 넘겨주면 된다.HelloTraceV2
beginSync(..)
TraceId에서createNextId()를 통해 다음 ID를 구한다.createNextId()의TraceId생성 로직은 다음과 같다.0 -> 1)HelloTraceV2Test
처음에는
begin(..)을 사용하고, 이후에는beginSync(..)를 사용하면 된다.beginSync(..)를 호출할 때 직전 로그의traceId정보를 넘겨주어야 한다.begin_end_level2() - 실행 로그
begin_exception_level2() - 실행 로그
실행 로그를 보면 같은
트랜잭션ID를 유지하고level을 통해 메서드 호출의 깊이를 표현하는 것을 확인할 수 있다.로그 추적기 V2 - 적용
메서드 호출의 깊이를 표현하고, HTTP 요청도 구분해보자.

이렇게 하려면 처음 로그를 남기는
OrderController.request()에서 로그를 남길 때 어떤 깊이와 어떤 트랜잭션 ID를 사용했는지다음 차례인
OrderService.orderItem()에서 로그를 남기는 시점에 알아야한다.결국 현재 로그의 상태 정보인
트랜잭션ID와level이 다음으로 전달되어야 한다.이 정보는
TraceStatus.traceId에 담겨있다. 따라서traceId를 컨트롤러에서 서비스를 호출할 때 넘겨주면 된다.OrderControllerV2
TraceStatus status = trace.begin()에서 반환 받은TraceStatus에는트랜잭션ID와level정보가 있는TraceId가 있다.orderService.orderItem()을 호출할 때TraceId를 파라미터로 전달한다.TraceId를 파라미터로 전달하기 위해OrderServiceV2.orderItem()의 파라미터에TraceId를 추가해야 한다.OrderServiceV2
orderItem()은 파라미터로 전달 받은traceId를 사용해서trace.beginSync()를 실행한다.beginSync()는 내부에서 다음traceId를 생성하면서 트랜잭션ID는 유지하고level은 하나 증가시킨다.beginSync()가 반환한 새로운TraceStatus를orderRepository.save()를 호출하면서 파라미터로 전달한다.TraceId를 파라미터로 전달하기 위해orderRepository.save()의 파라미터에TraceId를 추가해야 한다.OrderRepositoryV2
save()는 파라미터로 전달 받은traceId를 사용해서trace.beginSync()를 실행한다.beginSync()는 내부에서 다음traceId를 생성하면서 트랜잭션ID는 유지하고level은 하나 증가시킨다.beginSync()는 이렇게 갱신된traceId로 새로운TraceStatus를 반환한다.trace.end(status)를 호출하면서 반환된TraceStatus를 전달한다.정상 실행 로그
예외 실행 로그
실행 로그를 보면 같은 HTTP 요청에 대해서
트랜잭션ID가 유지되고,level도 잘 표현되는 것을 확인할 수 있다.정리
요구사항
모든 PUBLIC 메서드의 호출과 응답 정보를 로그로 출력애플리케이션의 흐름을 변경하면 안됨로그를 남긴다고 해서 비즈니스 로직의 동작에 영향을 주면 안됨메서드 호출에 걸린 시간정상 흐름과 예외 흐름 구분예외 발생시 예외 정보가 남아야 함메서드 호출의 깊이 표현HTTP 요청을 구분HTTP 요청 단위로 특정 ID를 남겨서 어떤 HTTP 요청에서 시작된 것인지 명확하게 구분이 가능해야 함트랜잭션 ID (DB 트랜잭션X)남은 문제
TraceId동기화가 필요하다.TraceId의 동기화를 위해서 관련 메서드의 모든 파라미터를 수정해야 한다.begin()을 호출하고, 처음이 아닐때는beginSync()를 호출해야 한다.TraceId가 없다.All reactions