시간이 정말 빠르다.. 레벨2의 중간지점을 넘어왔다.
👥 페어 프로그래밍
이번에는 같은 레벨2 데일리조인 두리와 페어가 되었다!
비록 칼퇴를 하지는 못했으나 두리와 3일간 재밌게 페어 프로그래밍을 했다고 생각한다. 🕺🏿
이번 미션부터는 내 코드와 페어 코드 중 하나를 선택해서 이어 구현해야 했다. 두리 코드를 사용했고, 아직 코드 양이 많지 않아서 그런지 내 코드와 많이 비슷해서, 이해하기 어렵지 않았다.
그런데 페어 프로그래밍이 끝난 후, 패키지 구조부터 컨벤션까지 내가 원하는대로 고쳤다. 너무 자연스럽게 내 기준에 맞게 리팩터링 했는데, 다른 크루들과 대화하다 보니 페어 코드를 사용한 경우 페어 컨벤션에 따라가는 경우가 많았다. 언제나 내 입맛대로 코드를 수정할 수 없다는 것을 인지하게 됐고, 앞으로 페어 코드를 사용하는 경우에는 독단적으로 리팩터링하지 않으려 한다.
+) 레벨2 시작하고 집중력이 더 떨어진 것 같다고 느낀다. 페어 프로그래밍 하다가 갑자기 눈에 아무것도 안 들어오는 때가 있다.. 페어 기간에는 진짜 잠 충분히 자고 컨디션 잘 유지해야겠다.
🙆♀️ 새로 적용해본 학습 방법
미션2 후반부터 gpt를 더 적극적으로 사용하기 시작했다. ai 수업때 프롬프트를 잘 짜는것에 감명받아서, 내 레벨2 목표인 "생각하면서 공부하기"를 이루기 위해 프롬프트를 이용해봤다.
이 개념은 왜 필요한 거야? 어떤 문제를 해결하기 위해 나온 거야?
어떤 상황에서 적용하는 게 적절하고, 어떤 상황에서는 오히려 안 쓰는 게 나아?
이 개념이 없으면 어떤 어려움이나 한계가 생기는 거야?
이 개념을 실제 코드나 시스템에 어떻게 적용할 수 있는지 예시로 보여줘.
이 개념과 비슷하거나 대체 가능한 다른 개념은 뭐가 있어? 차이점도 알려줘.
이 개념을 만든 사람이나 사용한 개발자들은 어떤 고민이나 배경을 갖고 있었을까?
지금 내가 이걸 제대로 이해하고 있는지 간단한 퀴즈나 체크리스트로 확인해볼 수 있을까?
생각과 궁금증을 더 이끌어낼 수 있는 템플릿을 만들어봤는데, 개념에 대해 질문할 때 더 잘 읽히고 추가적인 궁금증도 잘 파생되는 것 같다. 그런데 지피티를 이용해서 학습을 쭉 하다보니 지식이 금방 휘발되는 것을 느꼈다. 항!상! 스스로 하는 말이지만 내 생각으로 정리 잘 하자..
솔라의 학습 방식 찾기 워크숍에 참여했는데, 내가 언제 배움의 재미와 성취감을 느끼는지 파악한 후, 이를 잘 이용할 수 있는 실험 계획을 세웠다.(조원분들이 세워주셨다)
- 실제로 겪지 않은 문제라고 하더라도, 해당 지식을 '어느 상황에서 적용할 수 있을지' 에 대한 해답을 얻은 후에 학습 마무리하기
- 새로운 개념 → 프로젝트를 지피티한테 넣어주고, 이 개념을 적용할 수 있는 부분 어디가 있을지 물어보기.
나는 어디에 사용할 수 있을지 모르는 추상적인 개념을 깊게 학습하는 것을 힘들어하는 것 같다. 당장 사용할 수 있고, 어디에 적용할 수 있겠다! 하고 떠오르는 개념을 배울 때 그나마 재미를 느끼는 것 같아서, 적용해보면 좋을 실험계획이라 생각한다.
프롬프트에 추가해둬야겠다.
📚 알게된 것 정리
이번 미션에서 배운 키워드를 뽑아보았다.
- 예외 처리 핸들링
- RESTful api(솔라가 이번 미션에서 REST를 꼭 배웠으면 좋겠다 하셨는데.. 깊게 공부하지 못했다.. 왜 점점 밀리기만 하는거지..?)
- 인증 / 인가
- 토큰, 세션
- argument handler, interceptor
...
✏️ 테스트 대역
레벨1 블랙잭 미션에서 페어가 제안해줘서 테스트에 dummy, stub 방식을 사용했었는데, 지금 생각해보면 정말 아무것도 모르고 썼던 것 같다.
단위테스트를 하려는 대상이 의존성을 갖고 있는 경우, 테스트 대역을 이용하여 의존을 대체할 수 있다.
나는 이번에 Fake를 이용해서 service를 단위테스트하고자 했다.
service에는 검증해야 할 로직이 존재하는데, repository에 대한 의존성이 존재한다. 따라서 service를 단위 테스트하기 위해서는 repository(dao)를 fake로 만들어야 했다.
public class ReservationDaoFake implements ReservationDao {
private static final AtomicLong IDX = new AtomicLong();
private static final Map<Long, Reservation> RESERVATIONS = new HashMap<>();
...
메모리에 데이터를 저장하는 FakeDao를 구현해서 테스트에 사용했다. 그런데 내가 작성하는 Fake를 믿을 수 있는지에 대한 의문이 들었다. 테스트에서 안 깨지고 통과했는데, Fake가 잘못 구현되어서 그런 것일 수도 있는 것 아닌가?
내 코드를 믿지는 못하지만 일단 사용하는 수밖에 없다. 그래서 Fake를 이용한 단위 테스트를 할 때, 실제 의존성을 이용한 통합 테스트도 필요하다는 것을 느꼈다.
✏️ 커스텀 예외
이번 미션에서 @ExceptionHandler를 도입하면서 커스텀 예외를 활용하기 시작했다.
처음에는 도메인별로 커스텀 예외를 다 만들었다가 클래스 수가 너무 많아지고 복잡해져서 예외를 상황별로 분리했다. 그런데 각 도메인별로 다른 메시지를 던져주고 싶어졌고(중복 테마 -> 해당 테마 이름이 존재합니다 / 중복 예약 -> 해당 테마, 날짜, 시간에 대한 예약이 존재합니다 ...) 다시 도메인별로 커스텀 예외를 만들었다. 그냥 예외 메시지 넣어주면 되지 왜 커스텀 예외를 만들었을까?
중복 예약에 대한 검증은 service에서 비즈니스 검증 로직을 통해, unique 제약조건을 통해 두 번 이루어진다. 따라서 처리해주는 곳도 service와 repository 두 곳이었다. 그리고 이 두 곳에서 같은 예외 상황임을 나타내기 위해, 커스텀 예외를 이용했다.

만약 예외 메시지를 변경해야 하는 경우, 수정 포인트를 한 곳으로 응집시키고 싶었다. 그런데 이러한 리뷰를 받았다.

고민해봐야겠다...
✏️ 도메인에서 Null 검증
지금까지 나는 도메인 생성자에서 null 검증은 기본으로 해줘야 한다고 생각했었다. 그래서 이번에도 마찬가지로 생성자에서 필드가 null인 경우 예외를 던지게 했고, 아래와 같은 리뷰를 받았다.

HTTP 요청을 거친다면, RequestDto의 @NotNull 검증에서 걸리기는 한다. 그래도 개발자가 애플리케이션 내부에서 객체를 직접 생성하다가 필드를 null로 잘못 넣는 경우가 있을 수 있지 않나..? 라고 생각했다. 그래서 리뷰어께 다시 여쭤봤는데 아래와 같은 답변을 받을 수 있었다.

요청에서는 예기치 못하게 null인 값이 들어올 수도 있다. 그러나 개발자가 코드 내부에서 null인 객체를 만들지 않을 것임은 신뢰 가능하다. 그렇기 때문에 도메인 내부에서 null 검증은 꼭 필요하지 않을 수 있고, 모든 도메인 모든 필드에서 null 검증 해주는 것은 가독성을 해칠 수 있다.
이 당시에는 받아들이고 null 검증 로직을 지우고 넘어갔는데, 지금 다시 생각해보면, 검증 메서드를 분리하면 그렇게 가독성을 해치지 않을 것 같고, 편의성을 위해 안전성을 조금이라도 해치는 것이 옳은가? 싶다...
✏️ 인증 / 인가
이번에 로그인과 관리자 기능을 구현하는 과정에서 인증, 인가 개념을 적용했다.
인증은 등록된 회원인지, 로그인 가능한지 확인하는 것이고, 인가는 해당 회원의 권한을 확인하는 것이다.
로그인 방식은 Basic / Session / Token이 있는데, 미션에서는 Token을 사용하라고 했다.
각 로그인 방식을 정리해보면...
- Basic
- 인증이 필요하다는 것을 서버가 알려주면, 브라우저는 요청 시 아이디 + 비밀번호를 Base64로 인코딩해서, Authorization 헤더에 담아서 보냄
- 정해진 인코딩 방식 → 누구나 해석 가능함 → 절대 HTTPS(SSL/TLS) 없이 쓰면 안 된다.
- Session
- 서버가 세션 Id(사용자 로그인 정보)를 세션 저장소에 저장 -> 서버가 stateful하게 됨
- (클라이언트) 로그인 요청 -> (서버) 사용자 확인 -> (서버) 세션 저장소에 저장. id 쿠키에 담아서 return -> (클라이언트) 쿠키 받아와서 이후 요청에 보냄 -> (서버) 클라이언트 요청의 쿠키에 담긴 세션 id 보고 인증
- 쿠키에 담기 때문에 모바일 앱, api 서버와의 통신에서는 불편함.
- Token
- 토큰을 해석해서 인증
- 서버는 저장하지 않음 -> 서버는 stateless
- (클라이언트) 로그인 요청 -> (서버) 사용자 확인 -> (서버) 토큰 생성. 토큰을 응답으로 return -> (클라이언트) 토큰 저장해놓고 이후 요청에 Authorization header에 담아서 보냄 -> (서버) 클라이언트 요청의 헤더의 담긴 토큰 보고 인증
우리 미션에서는 토큰을 쿠키에 담게 했는데, 보통은 Authorization Header로 받아온다고 한다.
+) localStorage에 저장하면 XSS 공격에 취약하고, 쿠키(HttpOnly)에 저장하면 CSRF 공격 가능성이 있다는데, 각각 공격에 대해 조금씩만 정리해봤다.
- XSS → Cross Site Scripting
- js 파일에 원하는 js 코드를 끼워넣음 → 토큰을 다른 사이트에 보내는 코드 → 악용자는 토큰을 알 수 있음
- localStorage, sessionStorage에 저장하지 말자. js에서 접근 가능한 저장소이기 때문에.
- CSRF → Cross Site Request Forgery
- 브라우저가 쿠키를 들고 있고 요청시 쿠키를 자동으로 보낸다는 것을 악용
- 위조 요청 보내기 → 자동으로 사용자의 쿠키와 함께 전송 → 서버는 진짜 사용자 요청으로 착각
✏️ interceptor / argument resolver
나는 이번 미션에서 interceptor 2개, argument resolver 1개를 사용했다.
사용자 인증을 확인하는 Interceptor, 관리자 권한을 확인하는 interceptor, Member 객체를 반환해주는 argument resolver
관리자도 결국 인증이 필요하기 때문에, 아예 로그인 확인을 하는 interceptor를 맨 앞에 두고, 관리자 권한이 필요한 기능만 관리자 interceptor를 한번 더 거치게 했다.
🌙 회고 마무리
키워드가 쏟아지다보니 대충 배우고 넘어가는게 점점 생기고 있다. 회고글 쓰면서도 미션2에서 배운게 벌써 기억이 잘 안나서 당황스러웠다. 나는 마음 급해지는 순간 많은 것을 놓치게 되는 것 같다. 여유를 갖고 하자 여유를...
레벨2 초반에는 캠퍼스에 남아서 공부할 때도 집중이 잘 됐는데, 지금은 그냥 집에 일찍 와서 하는게 집중 잘 되는 것 같다. 역시 집이 최고야 ~~🏠
'우아한테크코스 7기 > 레벨2' 카테고리의 다른 글
| [우아한테크코스 7기] 레벨2 회고 (4) | 2025.06.21 |
|---|---|
| [우아한테크코스 7기] 레벨2 미션4(방탈출 결제 / 배포) 회고 (6) | 2025.06.16 |
| [우아한테크코스 7기] 레벨2 미션3(방탈출 예약 대기) 회고 (8) | 2025.06.02 |
| [우아한테크코스 7기] 레벨2 미션1(방탈출 예약 관리) 회고 (2) | 2025.05.04 |