2024-07-09-스프링 핵심 원리 - 고급편 - (1) 예제 만들기
인프런 스프링 핵심원리 - 고급편을 학습하고 정리한 내용 입니다.
스프링 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초 지연을 주었다.- 예외가 발생하는 상황도 보기 위해
itemId가ex면IllegalStateException가 발생하도록 했다.
서비스
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: 예외 발생!
다음시간에 로그 추적기를 만들어 보자.
댓글남기기