[TIL 22th] 확신에 차진 않지만 걷는 걸음 / 스프링 심화주차 개인과제 완료
by 노실언니내 생각에 현재 나는 스프링과 JPA에 관한 이해도가 많이 떨어진다고 생각한다
나의 이해속도보다 부트캠프의 진행속도는 빠르다.
어? 나 아직 이거 잘 모르는데 다음 스텝으로 가자고요?
그래서, 버거울 때도 많고... 공부하다보니 해가 떠있어서 그 다음 날을 망칠 때도 많았다.
그럼에도 불구하고, 스윽스윽, 여러번 칠해야 완벽해지는 페인트의 첫번째 칠을 부트캠프에서 한다는 생각으로 스윽스윽 가고 있다.
처음에 들어왔을 때의 결심은 배우는 모든 걸 완벽하게 흡수해서 부트캠프를 종료하면 내게 자신있는 상태로 취업하자였었는데
이제는 결심이 조금 바뀌었다.
부트캠프를 종료하더라도 나는 한 번의 칠을 한 것뿐이니
취준은 취준대로 하면서도 계속 더 배우고 다시 덧칠해야겠다는 방향으로
나의 부족함은 배우는 속도이고, 자신있는 부분은 기록이다.
사장님은 기록을 잘 쓰든 말든 빨리빨리 더 잘 개발하는 사람을 좋아하겠지만...
내가 기록에 열을 내는 이유는 두 가지인데
첫 번째로는 기록을 해야 나의 개발과정을 내가 이해할 수 있다는, 기록 자체가 내게 가져다주는 가치때문이고
두 번째로는 기록을 해야 팀 모두가 개발과정을 이해할 수 있고 사용자가 프로그렘을 이해할 수 있다는, 타인에 관한 부분때문이다.
여러 번의 팀 프로젝트를 하면서 더더욱 느꼈다. 기록이 중요하다는 것을
팀원 분들께서 개발을 잘 하냐 마냐와는 별개로
내 사람들이 무슨 개발을 어떻게 했는지 마이크로 조금 소통을 하긴 했더라도
나중에는 다 휘발되어서 모르겠었다.
딱 프로젝트가 종료되고 보니, 무엇이 어떻게 진행되어 완료되었는지 파악이 불가능했다.
완료는 했지만 말이다.
왜 그런 것일까? 기록을 안 해서.
프로젝트 기간이 짧다보니, 그래서 내 기능을 구현하느라 발등에 불이 떨어졌다보니
Git Flow 를 따르는 것, 이슈를 적는 것, PR을 적는 것, 타인의 PR을 읽고 코드리뷰를 하는 것들이
모두 번거롭고 시간 아깝게 느낄 수 있겠다는 생각이 들 것 같다.
팀 프로젝트일수록 코드를 작성하는 것만이 개발이 아니라는 것을 느낀다.
당장 내가 내 파트 개발하느라 바빠 죽겠을 때는 아쥬그냥 뵈는게 없다. 내 꺼 하는 것도 급하다.
그런데, 그거는 다른 사람들도 똑같다. 다 바쁘다.
그러니까, 더 내가 나의 개발기록 즉 Issue와 PR을 잘 써야한다는 것이다.
나중에 숨 돌릴 수 있을 때, 그 때 나와 내 팀원들이 다 적어놓은 Issue, PR 기록들을 보면
- 전체적인 개발흐름이 느껴지고 우리 팀이 어떻게 개발하고 있구나 라는게 느껴지면서 망망대해 속에서 개발하는 느낌이 지워진다.
- 누군가가 더 많이 기능을 개발하고 있고 버그를 고치고 회색지대를 해결하고 있다는 것도 그렇지 않은 사람도(아마..나?) 명백하게 보인다.
그러니, 각자 자기가 한 것을 기록하면서 개발하는 것은 나에게도 팀에게도 다 도움이 된다고 생각한다. 일타쌍피!
트러블 슈팅을 뭘 쓸지 고민할 필요도 없다. Issue와 PR에 어떤 문제였고 어떻게 해결했는지 다 나와있으니 복붙만 하면 된다.
리드미에 뭘 쓸지 고민할 필요도 없다. Issue에 구현한 기능이 다 나와 있고 누가 뭘 구현했는지 다 나와있으니 말이다.
TIL 역시 마찬가지 뭘 구현하려했는지 Issue에 다 있고 실제로 뭘 구현했는지 PR에 다 나와있는데 복붙하면 된다.
그래서 더 기록에 열을 올린다. 내 성장의 수단이자 도움의 수단이기에
나는 항상 배우는 사람이고 싶고 (누가 배움을 포기하는 순간 늙는다고 함 늙기 싫오요)
누군가에게 도움이 되는 사람이고 싶다. 이거는 개발자를 떠나서 내 인생모토이다.
기록은 내 인생모토에 꼭 필요한 수단이다.
내가 뭘 하고 있는지 트래킹하듯 기록하는 것은 나에게 도움이 된다.
- 기록 자체로 복습이 되고,
- 또. 어떤 트러블이 발생해서 어떻게 해결했는지, 개발이 종료된 지금 그러한 해결과정이 최선이었는지 복기할 수 있는 수단이 된다.
- 또, 나를 객관적으로 파악할 수 있어, 이후 학습 방향성을 정할 수도 있다.
그리고, 다른 사람들에게도 도움이 된다.
- 개발 기록들은 내 프로젝트 설명서인 리드미를 작성하는데에 도움이 된다.
- 또, 구두가 아닌 문서로 각자 작업기록을 공개함으로써 프로젝트의 진행상황이 베일에 싸이지 않게 된다.
- 각자 적은 자신의 개발기록, 이슈트래킹 기록이 한 곳에 합쳐져 있으면 그 자체로 엄청난 자산이 된다.
나중에 이력서 적을 때 한번 스륵 보는 것만으로도 기억이 촤촥 날테고
포폴에 링크로 남길 때 리드미만 어찌저찌 번지르르하게 적었더라도 이슈하나없고 PR하나 없는 프로젝트는 뭔가 이상해보인다.
이슈와 PR기록은 주작이 불가능하니까.
부트캠프에 왔을 때의 내 결심이 변했던 것처럼
기록하자! 라는 생각이 언제까지 갈지는 모르겠지만 아직은 그렇다.
아니 TIL에 쓸 내용은 이게 주가 아니었는데...!
이번 과제할 때의 마음가짐이 그랬어서 그런가보다.
도전기능까지 다 못해도 괜찮으니깐, 문제 하나하나 제대로 풀기
주석 잘 쓰고, 커밋단위잘끊고, 커밋이름 잘 짓고, PR 잘 하고, README.md 잘 쓰기
이제는 스프링 심화주차 개인과제를 어떻게 했는지 좀 떠올려 본다.
주어진 기한은 7일, 스프링 심화주차 과제를 하기 전에 베이직반은 베이직 반에서 받은 Filter기능 과제를 먼저 하라고 했다.
근데, 나란 사람...!
사실 기본 CRUD도 버거운 상황이었음ㅋㅋㅋㅋㅋ퓨ㅠㅠㅋㅋㅋㅋㅋ
얼렁뚱땅 CRUD 실습이랑 베이직 반 과제를 다 하고 본격 심화주차과제에 돌입하려니 이틀이 남았다 ^_^
나의 허우적을 보시고 답답하셨을 대현 담임튜터님과 선용 튜터님께 무한 감사와 사죄를....
캬 포기직전에 있던 나를 구제해준 미틴 관리력 이게 내배캠아임니까
못나서 죄송해요!!!!
암튼 그 허우적이 끝날쯔음 필수 과제정도는 다행히 답이 보이게 되었다.
주어진 문제마다 커밋을 2개씩 올렸다. [전체 커밋 링크]
- 첫 번째 커밋은 주석으로 전체 코드를 분석하고 코드에 어떤 문제가 있어서 어떻게 해결해야하는지 적었다.
- 두 번째 커밋은 실제로 문제를 해결한 코드를 적었다.
이렇게 적으니까 공부를 한 번 더 하는 느낌이었다.
PR[링크]을 작성하면서는 적어뒀던 주석들을 복붙하면 되니깐 작성이 편했고, 그래서 공부를 또 한 번 더 하는 느낌이었다.
README.md[링크] 를 작성할 때는 적어둔 PR을 복붙하면 되니깐 또또또 공부를 또 하는 느낌이었다.
Lv 1. 의 1, 2번 문제 → Procedural 하게 봤을 때 불필요한 흐름 만들지 말기
Lv 1-1의 코드흐름은 아래와 같다.
1. 입력받은 비밀번호 암호화하기
2. 입력받은 회원Role을 Enum으로 저장하기
3. 입력받은 아이디가 이미 DB에 있는지 확인해서, 있다면 중단하고 예외 날리기
그런데, 만약에 중복된 아이디가 들어와서 3번 과정에서 예외가 퉷 하고 나온다면
1번 과정인 비밀번호는 왜 암호화했으며, 2번과정인 회원 역할은 왜 Enum으로 변환해야했을까.
1, 2번 과정이 다 헛수고가 되는 것이다.
개발을 할 때에는 같은 기능을 구현하는 여러 개의 프로그램이 있다면 효율 좋은 프로그램이 선택된다.
효율이 중요한만큼 프로그램 상에서 불필요한 흐름은 안 만드는 것이 당연히 맞겠다.
Lv 1-2도 마찬가지이다.
1. 외부 API에 가서 데이터 받아오기
2. 받은 데이터 중 body 부분만 분리하기
3-1. if 만일 받은 데이터에 대한 상태코드가 비정상이면 예외 발생시키기
3-2. else 비정상은 아니어도,
3-2-1. { if 만일 데이터의 body 부분이 null이거나 비어있으면 예외 발생시키기 }
2번 과정에서 body 부분을 분리하는데,
만일 3-1번 과정에서 status가 비정상이라 예외가 발생하면 2번 과정은 헛수고가 된다.
그러니, 불필요한 흐름을 없애주면 된다. 아래와 같이
1. 외부 API에 가서 데이터 받아오기
2-1. if 만일 받은 데이터에 대한 상태코드가 비정상이면 예외 발생시키기
2-2. else 비정상이 아니면 받은 데이터 중 body 부분만 분리하기
2-2-1. if 만일 데이터의 body 부분이 null이거나 비어있으면 예외 발생시키기
그런데 아직도 불필요한 흐름이 있다.
if 를 빠져나온 다음의 코드는 else를 안 붙여도 else 와 같은 의미를 가진다.
그러니 else 속에 if 를 다시 만들어서 코드를 복잡하게 짜는 것보다 if 문과 if 문으로 간결하게 분리해주는 것이 좋다고 생각한다.
자료의 상태코드를 확인하는 과정과 / 자료의 body 부분이 null인지를 확인하는 과정이 독립적이기도 하고 말이다.
1. 외부 API에 가서 데이터 받아오기
2. if 만일 받은 데이터에 대한 상태코드가 비정상이면 예외 발생시키기
3. 받은 데이터 중 body 부분만 분리하기
4. if 만일 데이터의 body 부분이 null이거나 비어있으면 예외 발생시키기
이런 것도 코드의... 깊이라고 해야하나?
굳이 1 -> 2-2 -> 2-2-1 이런 식으로 안 들어가고
1 -> 2 -> 3 -> 4 진행되니 흐름을 파악하는 것이 깔끔하다.
Lv1. 의 3번 문제 → 무언가 구현을 할 때에는 층들의 목적을 고려해서 적절한 층에 구현하자
3번 문제에는 비밀번호 유효성 검사가 service 층에 구현되어있었다.
Controller 층은 외부(사용자)로부터 요청과 입력을 받아오는 층이고
Service 층은 요청을 본격적으로 수행하는 층이다. (비즈니스 로직을 구현한다고 함)
그러면, 사용자가 입력한 값의 유효성 여부는 Controller 선에서 처리하여, 유효한 값만 Service층으로 가도록 해서
Service 층은 값의 유효성보다는 요청을 수행하는 것에 신경쓰도록 만드는 것이
3-layered Architecture 방식에 맞춘 것이라고 볼 수 있다.
이 방식에 꼭 안 맞춰도 되지만, Spring JPA 을 사용한다는 것 자체가 해당 아키텍처에 개발방식을 맞추겠다는 것이니깐
입력한 값의 유효성 체크는 DTO에 마련해서 Controller 선에서 처리되도록 하는 것이 좋겠다.
그래서, 비밀번호 변경Request DTO에 유효한 비밀번호 양식을 정의해두고 (@size, @pattern 사용)
controller에서 비밀번호 변경Request DTO를 생성할 때에 유효성을 체크하도록 (@valid 사용)
바꿔주었다.
Lv2. N+1 문제
ManyToOne + LAZY loading + 다건 조회 → N+1
N+1 문제란 코드 상으로 적은 쿼리는 한(1) 개인데
실제로는 N+1 개의 쿼리가 진행되어서 성능이 떨어지는 현상을 말한다고 한다.
이런 문제가 발생하는 조건은 아래와 같다.
1. 특정 엔티티가 ManyToOne 상황에서 부모인 One 엔티티가 LAZY loading 으로 설정되어 있고
2. 그러한 엔티티를 다건 조회(findAll...)하여 개별접근할 때 발생한다.
1회 : Count쿼리 = 총 데이터 개수(N개) 파악
예) SELECT COUNT(*) FROM todo;
N회 : 각 데이터에 대한 부모 데이터 조회 X N 번
예) SELECT * FROM user WHERE id = ?; X N 번
이 문제를 해결하는 방법에는 FETCH JOIN, @EntityGraph, @BatchSize 등이 있다.
FETCH JOIN은 이 한 번의 쿼리로 연관 데이터까지 같이 가져올 수 있게 하는 것이고
@EntityGraph는 JPA가 자동으로 연관 엔티티의 LAZY loading 설정을 무시해서 연관 데이터까지 한 번에 조회하는 JPA 표준 기능이고
@BatchSize는 한 번의 IN 쿼리로 여러 개의 연관 데이터를 조회하도록 해서 N+1 문제를 조금 줄이는 방식이다.
N+1 문제 + FETCH JOIN + Paging → 경고 & 성능이슈
FETCH JOIN이 가장 간단하지만, 문제가 있는데 Paging 과 같이 쓰면 문제가 생긴다는 것이다.
Hibernate쪽에서 페이징 쿼리를 실행하다가 문제가 발생하고 성능 저하 문제가 발생한다.
@EntityGraph 는 페이징과 함께 사용할 수 있다.
그러니까 아래와 같은 공식을 알아두자.
ManyToOne + LAZY loading + 다건 조회 + Paging 안 씀 → FETCH JOIN 도 괜찮
ManyToOne + LAZY loading + 다건 조회 + Paging → @EntityGraph 쓰기
다건 조회할 때는 거의 페이징을 쓸 꺼다보니깐 @EntityGrapy와 친해지는 것이 좋겠다.
주어진 문제에서는 todo-user가 ManyToOne 이었고 user가 LAZY loading 으로 설정되어 있었다.
그리고, todo를 조회하는 두 개의 메소드가 있었는데 하나는 다건조회(findAll) 하나는 단건조회(findById)였다.
그러면, ManyToOne + LAZY loading + 다건 조회 → N+1 의 규칙(?)에 따라 두번째 메소드는 손댈 필요가 없다.
첫번째 메소드인 다건조회 메소드는 FETCH JOIN 으로 N+1 문제를 해결하고 있는데 문제는 Pageable 을 사용한 상태라는 것이다.
페이징을 하기때문에 이건 FETCH JOIN이 아닌 @EntityGraph 로 해결해주어야 한다
이렇게 문제를 풀면서, JPA를 사용하면서 마주칠 수 있는 문제(레이어에 맞지않는 구현 & 페이징과 FETCH JOIN 을 같이 쓴 상황)에 대해서 한 번 더 생각해볼 수 있었다.
마지막으로는 테스트 코드연습이다.
어떻게 코드를 짤지 생각하는 파트라기보다는 문법적으로 테스트 코드와 친해지는 연습을 했다.
이렇게 해서 또 빡센 개인 과제의 시간이 지나갔다.
더 빡센 팀프로젝트가 기다리고 있다 두근두근
오늘 아침에 수술을 쪼꼼 크게 하는데 잘 깨어나서, 계속 부캠에 참여할 수 있기를...!
'Today I Learned' 카테고리의 다른 글
[TIL 21th] LeetCode 584. Find Customer Referee (0) | 2025.02.03 |
---|---|
[TIL 20th] 프로그래머스 : 로또의 최고 순위와 최저 순위 (0) | 2025.01.31 |
1월 1, 2주차 우수 TIL에 선정되다 (예? 제가요?) (0) | 2025.01.23 |
[TIL 19th] 벡터의 주소를 가리키는 포인터를 사용하면 낭패본다 (0) | 2025.01.20 |
[TIL 18th] 재귀 & 유클리드 호제법 다시 사용해보기 (0) | 2025.01.18 |
블로그의 정보
노력하는 실버티어
노실언니