우아한테크코스 7기/레벨2

[우아한테크코스 7기] 레벨2 미션1(방탈출 예약 관리) 회고

seaniiio 2025. 5. 4. 19:10

스프링 프레임워크를 배우는 Level2가 시작됐다.

 

나는 스프링 강의를 찍먹한 경험이 있지만, 정말 강의만 듣고 넘겼기 때문에 제대로 된 지식은 전혀 남아있지 않은 상태였다. 그래서 아예 처음 마주하는 상태와 다르지 않다고 생각한다.

 

Level2은 방탈출 예약 시스템이라는 도메인을 기반으로 쭉 진행되는 것 같다. 그 중에서 첫 번째 미션은, 방탈출 예약 관리 기능을 구현하는 것이었다.

 

1~3단계는 페어와 함께 진행하고, 4~9단계는 혼자 진행했다. (10단계도 존재하는데, 나는 9단계까지만 구현했다.)

👥 1~3단계 - 페어 프로그래밍

각 단계별로 내가 배운 내용을 정리해봤다.

  • 1단계: 컨트롤러 메서드를 통해 View를 내려주는 방법
    • static vs template
    • static은 스프링 내부 거치지 않고 바로 정적 html 띡 보내줌(CSR)
    • template은 템플릿 엔진을 통해 html 완성시켜서 보내줌(SSR)
  • 2단계: Read api 만들어보기
    • @ResponseBody / ResponseEntity로 html 아닌 데이터 내려주기
  • 3단계: Create, Delete api 만들어보기
    • @RequestBody - HTTP request body의 값 받아와서 객체로 변환
    • @RequestParam - 쿼리 파라미터(url의 parameters 부분)에서 추출한 값 받아옴
    • @PathVariable - Url에서 추출한 값 받아옴

주어진 기능을 구현하기 위해 사용한 애노테이션을 위주로 공부했다. 

🍵 밍트와의 페어프로그래밍

1~3단계는 구현 양 자체는 많지 않았고, 같이 공부하는 시간이 많았다.

 

밍트와의 페어 프로그래밍을 통해 나에게 필요한 학습 방법에 대해 많이 고민해볼 수 있었다.

 

우선 밍트와 나는 공부 성향이 달랐는데, 밍트는 깊게 파고들어 공부한다 했고, 나는 얕게 대충 공부하고 마는 성향이다. (내가 얕은 공부를 하는 이유는 더 궁금하다는 생각이 들지 않아서...)

 

이번에 LMS의 학습 자료랑 학습 테스트를 같이 공부하면서, 밍트가 공부하는 방법을 따라하며 깊은 학습을 할 수 있었다. 우선 배우고자 하는 내용에 대해 공식 문서를 천천히 읽다가, 이해가 가지 않는 부분은 gpt를 사용하고, 추가적인 개념이 나올 땐 그것도 공부했다. 그리고 그 날 배운 내용은 집에서 따로 노션에 정리했다.

 

중요한 포인트는 천천히 이해하려 노력하는 것이었다고 생각한다. 나는 참을성이 부족해 이 과정을 충분히 겪지 않았고, 그래서 내 지식으로 정리하기 어려웠다. 내가 혼자 공부했다면, 공식문서를 읽다가 쉽게 포기하고 바로 gpt를 이용했을 것 같다. 내가 지금까지 그런 방식으로 공부했어서 내 지식으로 만들지 못했던 것 같다. 그런데 밍트와 같이 공부한 내용은, 리뷰어에게 설명하거나 정리할 때 수월하게 적을 수 있었다. 

 

Level2 시작할 때 내게 맞는 학습 방법을 꼭 찾고 싶다고 생각했는데, 밍트 덕분에 그 목표에 가까워질 수 있었다.

 

정말 즐거웠고 많이 성장할 수 있었다! 🤙🏽

📚 학습 방식 찾기

이번 미션에서 내게 맞는 학습 방법과 어느 정도 깊이까지 공부해야 하는지에 대해 고민을 했다.

 

우선 나는 공부할 때 N이 되면 좋겠다고 생각했다. 그냥 받아들이고 넘어가지 말고, 의식적으로 계속 궁금증을 가지려고 했다.

 

예를 들어, @RequestBody에 대한 공식 문서를 읽는데, @Valid를 붙여서 유효성 검사를 할 수 있다는 내용이 있었다.

-> 오 이런 애노테이션도 있네? 하고 그 문서에 대해 읽기 시작했다

-> 이것도 사용해봐야겠다! 그런데 유효성 검사가 진행되는 시점이 언제지?

-> 오 컨트롤러 메서드가 실행되기 전에! 요청 body에 있는 값을 객체로 우선 만들어주고 나서, 진행하는구나.

-> 근데 요청 body에 있는 값을 어떻게 객체로 만들지?

-> Jackson이라는 애가 역직렬화 해주는구나.

...

 

Spring에 대해 잘 모르다보니, 이렇게 계속 모르는게 불어났다. 공부할게 계속 생기긴 했으나, 어느정도 깊이까지 공부해야하는지는 아직 감을 잡지 못하고 있다. 

 

그리고 개념적인 공부도 중요하지만, 지금은 스프링 코드 자체에 익숙해지는 것이 우선이다. 그걸 잊지 말자.

✏️ DTO 검증 vs Domain 검증

처음에는 request DTO과 도메인 객체 모두에서 유효성 검사를 진행했는데, 리뷰 과정에서 생각을 정리할 수 있었다. 

 

컨트롤러 메서드 내부 구현이 무거워서, 이걸 실행시키기 전부터 애초에 예외를 잡아버리고 싶다 -> dto 검증

View에 따라 요구사항이 달라짐 -> dto 검증

+) null 검사같은, 기본적이고 잘 변하지 않는 요구사항 -> dto 검증

 

유효성 검사를 웬만하면 도메인에서 수행하도록 하자고 결론을 내렸다. 일단 유효성 검증 로직이 분산된다는 것도 유지보수에 있어서 안 좋기 때문이다.

💫 4~9단계

step2에서는 아래의 작업을 했다.

 

- DB 적용, JdbcTemplate

- 엔티티 구조 수정

- 레이어드 아키텍쳐 적용

 

레이어드 아키텍쳐의 각 계층별로 어떤 책임을 부여해야 하는지에 대해 많이 고민했다.

 

✏️  레이어드 아키텍쳐

프로그램이 복잡해지면서 코드가 엉키면 유지보수가 어려워진다. 그래서 상대적으로 덜 중요한 계층이 중요한 계층을 의존하도록 해서, 중요한 계층의 변경을 최소화하게 하는 것이 레이어드 아키텍쳐이다.

 

계층별 역할을 정리해봤다.

 

Presentation - 사용자(브라우저)와 상호작용하는 계층

Application - domain과 infra 계층 연결, 흐름 처리, domain 내부에서 처리하지 못한 비즈니스 로직 조율

Domain - 도메인 모델 구현, 비즈니스 로직 수행

Infrastructure - 다른 계층에서 사용할 기술 구현

 

어디까지가 controller의 역할이고, 어디까지가 service의 역할인지 헷갈렸다. 요청 -> domain, db -> domain을 전부 service에서 처리한다면, 도메인 변환 책임을 한 곳으로 몰아줄 수 있어서 좋은 거 아닌가? 라고 생각했었다. 그런데 요청을 domain으로 바꿔주는 것은 사실상 view에 의존하는 코드라고 볼 수 있는데, 그걸 service에서 넣는게 맞을까? db에서 읽어온 데이터를 domain으로 바꿔주는 것도 db 설계에 의존하는 코드인데, infra 계층에서 처리하는게 맞지 않을까?

 

도메인 변환이라는 역할을 service에 몰아넣자! 라는 생각에 꽂혀서 의존 방향이 올바르지 않은 설계를 한 것 같다. 

 

✏️  성능 고려하기

이번 미션의 요구사항에 명시되어 있지는 않았지만, "같은 날짜, 시간에는 예약 불가"라는 요구사항을 추가했다. 

 

중복 예약 검증은 비즈니스 규칙이다. 도메인 계층에서 처리하는 것이 적절해 보인다.

 

그러나 이를 도메인 영역에 위임하려면, reservation 테이블의 모든 데이터를 읽어와서, Reservations라는 일급 컬렉션으로 변환한 후, 그 내부에서 검증 로직을 수행해야 한다. 예약을 추가하기 전에 항상 이 과정을 거쳐야 하는데, 예약 데이터가 매우 많아지면 성능이 더 안좋아질 것이다.

 

그래서 나는 insert하기 전에 쿼리를 통해 같은 시간, 날짜에 대한 예약 데이터가 존재하는지 확인하고, 존재한다면 예외를 발생시키는 코드를 service에 작성했다.

 

✏️  service 검증 vs db 검증

그리고 db에도 unique 제약조건을 통해 중복 예약에 대해 검증하도록 했다. service에서만 검증하면, db에 insert 쿼리를 직접 날리는 경우 중복 예약이 저장 가능하고, 동시성 문제에 대응하지 못하기 때문이다.

 

근데 그러면 db 제약조건만 걸어두면 되는 것 아닐까? 왜 service에서도 수행해야 할까?

 

만약 중복 예약을 service에서 걸러준다면 insert 쿼리를 수행하지 않아도 된다. write 쿼리는 비용이 크기 때문에, 상대적으로 비용이 적게 드는 read 쿼리 하나로 예방하는 것이다. 그런데 db에서만 검증한다면, 매번 insert 쿼리를 수행해야 할 것이다.

 

그런데 service에서 검증한다면 결국 쿼리를 2번 날리는 것이기는 하다. 그리고 만약 중복 예약에 대한 기준이 변경된다면, service와 db 모두를 수정해야 한다. 그럼 무슨 기준으로 service에서 검증을 하는게 좋을까? 중복 예약 시도가 많은 경우, 티켓팅같은 기능을 구현할 때는 service 검증을 수행하는게 좋을 것 같다.

 

✏️  Entity

이번 미션에서 많이 들렸던 키워드다. Entity는 id를 가지는 도메인 모델이다.

 

레벨1까지는 도메인 객체가 id를 갖지 않았다. 대신 우리는 그 객체를 주소값으로 식별했다. 그런데 레벨2에 들어오고 나서는 객체를 식별하기 위한 수단으로 id를 사용했다.

 

왜 주소로 식별하면 안 될까? 

 

우리는 이제 db에 값을 영속적으로 저장한다. 애플리케이션이 종료된다 해도 db에는 객체에 대한 데이터가 남아있는 것이다.

 

그런데 애플리케이션을 다시 실행시켜 그 데이터를 객체로 가져오면 주소값이 달라질 것이고, 의도상 같은 객체인데 다른 객체로 보게 될 것이다. 그래서 우리는 "이 객체가 생성된 이후부터 사라질 때까지 유일하게 식별 가능하도록 부여하는 값"이 id인 것이다.

 

추가적으로, 현재는 db에서 auto_increment 속성으로 생성해준 id를 사용하고 있다. 그런데 이것도 도메인이 db에 의존하는 것이다. 그래서 도메인 생성 시 id를 직접 생성해주고, 생성된 id를 db에 넣어줄 수도 있다.

 

🌙 회고 마무리

레벨2 OT에서, 스프링 학습도 중요하지만 나만의 학습 방법을 찾는 것이 더 중요하다고 말씀해주셨다. 내 현재 학습 방식에서의 문제점은 "생각 안 하고 공부하는 것"이다. 공부한 내용을 나만의 생각으로 정리하고, 공부 과정에서도 계속 궁금해하는 것의 필요성을 느꼈다. 그래서 현재 나에게는 "공부할 때 N 되기"라는 새로운 목표가 있다. 

 

스프링 고수들이 많기도 하고, 다들 나보다 뛰어난 것 같다는 생각에 주눅들기도 한다. 그럴 때마다 내가 제일 못하는 집단이 내가 제일 잘하는 집단보다 낫다는 최면을 걸고 있다. 그리고 외부 요인이 아니라, 내가 스스로 성장하는 것에 집중하고 싶다. 레벨2 유강스 목표도 "스스로 성장하는 재미 찾기"로 잡은 만큼, 레벨2에서 학습에 대해 더 많은 고민을 할 것 같다.

 

남은 레벨2 미션도 화이팅~~ 👊