3 분 소요

 인프런 스프링 핵심원리 - 고급편을 학습하고 정리한 내용 입니다.

스프링 MVC 2편을 다 들었다.. 스프링을 사용 한지 2년이 넘었지만 배울 때마다 새로운 것 같다.

이번에도 잘 배워서 내 걸로 만들고 싶다.

프로젝트 생성

스프링 부트 스타터에서 프로젝트 생성

  • 프로젝트 선택
    • Project : Gradle - Groovy
    • Language : Java
    • Spring Boot : 3.3.1
  • Project Metadata
    • Group : hello
    • Artifact : advanced
    • Name : advanced
    • Package name : hello.advanced
    • Packaging : Jar
    • Java : 21
  • Dependencies: Spring Web, Lombok

강의와 현재는 시간이 차이가 나서 스프링 부트 2.xx → 3.xx 로 바뀌었고, 자바도 11 → 17, 21로 바뀌어서 이를 주의하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
plugins {  
    id 'java'  
    id 'org.springframework.boot' version '3.3.1'  
    id 'io.spring.dependency-management' version '1.1.5'  
}  
  
group = 'hello'  
version = '0.0.1-SNAPSHOT'  
  
java {  
    toolchain {  
       languageVersion = JavaLanguageVersion.of(21)  
    }  
}  
  
configurations {  
    compileOnly {  
       extendsFrom annotationProcessor  
    }  
}  
  
repositories {  
    mavenCentral()  
}  
  
dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-web'  
    compileOnly 'org.projectlombok:lombok'  
    annotationProcessor 'org.projectlombok:lombok'  
    testImplementation 'org.springframework.boot:spring-boot-starter-test'  
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'  
}  
  
tasks.named('test') {  
    useJUnitPlatform()  
}

프로젝트 준비는 끝났다.

예제 프로젝트 만들기 - V0

간단한 예제 프로젝트를 만들어보자.

상품을 주문하는 프로세스로 가정하고, 일반적인 웹 애플리케이션에서 컨트롤러 > 서비스 > 리포지지토리로 이어지는 흐름을 최대한 단순하게 만들어 보자.

다음 패키지에 만든다.

리포지토리

hello.advanced.app.v0.OrderRepositoryV0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Repository  
@RequiredArgsConstructor  
public class OrderRepositoryV0 {  
  
    public void save(String itemId) {  
        // 저장 로직  
        if (itemId.equals("ex")) {  
            throw new IllegalStateException("예외 발생!");  
        }  
        sleep(1000);  
    }  
  
    private void sleep(long millis) {  
        try {  
            Thread.sleep(millis);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}
  • sleep(1000) : 리포지토리는 상품을 저장하는데 약 1초 정도 걸리는 것으로 가정하기 위해 1초 지연을 주었다.
  • 예외가 발생하는 상황도 보기 위해 itemIdexIllegalStateException가 발생하도록 했다.

서비스

hello.advanced.app.v0.OrderServiceV0

1
2
3
4
5
6
7
8
9
10
@Service  
@RequiredArgsConstructor  
public class OrderServiceV0 {  
  
    private final OrderRepositoryV0 orderRepository;  
  
    public void orderItem(String itemId) {  
        orderRepository.save(itemId);  
    }  
}

많은 로직이 있다 가정하고 그냥 마지막에 save 호출한다.

컨트롤러

hello.advanced.app.v0.OrderControllerV0

1
2
3
4
5
6
7
8
9
10
11
12
@RestController  
@RequiredArgsConstructor  
public class OrderControllerV0 {  
  
    private final OrderServiceV0 orderService;  
  
    @GetMapping("/v0/request")  
    public String request(@RequestParam("itemId") String itemId) {  
        orderService.orderItem(itemId);  
        return "ok";  
    }  
}

RestController로 만들었고 별 다른 건 없다.

주의
스프링 부트 3.2 부터는 @RequestParam("itemId") String itemId 이걸 꼭 지정해 주자.
원래 예제) request(String itemId)

실행 결과

이름을 받으면 ok 응답이 된다.

ex를 넣으면 예외가 발생 된다.

그럼 이제 이걸로 뭘 만들지 알아 보자.

로그 추적기 - 요구 사항 분석

만약에 새로운 거대한 프로젝트에 투입되었다고 가정해 보자.

전체 소스 코드는 수 십만 라인이고, 클래스 수도 수백개 이상이다. 나에게 처음 맡겨진 요구 사항은 로그 추적기를 만드는 것이다.

어떤 부분에서 병목이 발생하는지, 그리고 어떤 부분에서 예외가 발생하는지 를 로그를 통해 확인할 수 있도록 이 부분을 개선하고 자동화 하는 것이 미션이다.

요구 사항

  • 모든 PUBLIC 메서드의 호출과 응답 정보를 로그로 출력
  • 애플리케이션의 흐름을 변경하면 안됨
    • 로그를 남긴다고 해서 비즈니스 로직의 동작에 영향을 주면 안됨
  • 메서드 호출에 걸린 시간
  • 정상 흐름과 예외 흐름 구분
    • 예외 발생 시 예외 정보가 남아야 함
  • 메서드 호출의 깊이 표현
  • HTTP 요청을 구분
    • HTTP 요청 단위로 특정 ID를 남겨서 어떤 HTTP 요청에서 시작된 것인지 명확하게 구분이 가능해야 함
    • 트랜잭션 ID (DB 트랜잭션X), 여기 서는 하나의 HTTP 요청이 시작해서 끝날 때 까지 를 하나의 트랜잭션이 라함

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
정상 요청
[796bccd9] OrderController.request()
[796bccd9] |-->OrderService.orderItem()
[796bccd9] | |-->OrderRepository.save()
[796bccd9] | |<--OrderRepository.save() time=1004ms
[796bccd9] |<--OrderService.orderItem() time=1014ms
[796bccd9] OrderController.request() time=1016ms

예외 발생
[b7119f27] OrderController.request()
[b7119f27] |-->OrderService.orderItem()
[b7119f27] | |-->OrderRepository.save()
[b7119f27] | |<X-OrderRepository.save() time=0ms
ex=java.lang.IllegalStateException: 예외 발생!
[b7119f27] |<X-OrderService.orderItem() time=10ms
ex=java.lang.IllegalStateException: 예외 발생!
[b7119f27] OrderController.request() time=11ms
ex=java.lang.IllegalStateException: 예외 발생!

다음시간에 로그 추적기를 만들어 보자.

댓글남기기