마지막으로 면접 준비 이전에 도움이 될만한 질문 몇가지 리스트 정리하여 공유드려 봅니다. 답변은 글자 제한이 없이 아주 길어도 무방하니 상세하게 부탁드립니다!
1. 최종 프로젝트 팀에서 어려운 점과 잘했다고 생각하는 점이 있다면 각각 어떤 점들이 있을까요?
A. 최종 프로젝트를 하면서 어려웠던 점은 적극적인 피드백을 하지 않았던 점입니다. 깃 전략을 세울 때 풀리퀘스트가 올라오면 코드를 리뷰하기로 했었습니다. 하지만 이부분이 잘 적용되지 않았습니다. 제가 생각했던 문제점은 다음과 같습니다.
1. 커밋과 풀리퀘스트가 잘게 쪼개지지 않아 한번에 올라오는 코드의 양이 많았습니다. 깃 전략상 커밋과 풀리퀘스트를 잘게 쪼개서 하자고 했었으나, 잘 지켜지지 않아 한번에 봐야할 코드가 너무 길어졌습니다. 이렇게 길어진 코드를 이해하는데 어려움이 있었고, 시간적인 부담감도 있었습니다. 자주/짧게 코드 리뷰 하는 것과 가끔/길게 코드 리뷰 하는 것 중 자주/짧게 보는게 더 좋다고 생각했습니다.
2. 리뷰에 대한 자신이 없었습니다. 리뷰란 내 의견을 피력하고, 상대의 의도를 파악해 더 좋은 코드를 짤 수 있게 하는 문화 라고 알고있습니다. 하지만, 제 실력적으로 자신이 없었습니다. 따라서 내가 하는 리뷰가 올바른 리뷰인지 자신이 없어 근걍 넘겼던 적이 있었습니다. 결과적으로 제가 지적하지 않았던 코드들이 후에 문제가 생겨 버그를 야기했던 적이 있습니다. 이번 경험을 통해 내가 하는 리뷰가 맞고 틀리고의 문제를 따지기 보다는, 상대가 이코드를 왜 이렇게 짰는지를 물어 봄으로서 상대의 의도를 알 수도 있고, 내가 생각하는 문제(걱정)을 이야기 해 더 빠르게 문제를 파악하고, 해결할 수 있다고 배웠습니다.
3. 코드 리뷰에 대해 진지하게 임하지 않았습니다. 리더로서 코드컨벤션에 맞지 않는 코드나, 버그의 위험이 있을 수 있는 코드를 주의깊게 봤어야 했지만, 내 기능 구현이 더 급하다고 생각해 코드 리뷰를 진지하게 보지 않았습니다. 위에 서술했던 것과 같이 제때 잡지 않은 위험한 코드는 버그를 야기했고, 문제를 해결하는데 더 오래 걸렸습니다.
이번 프로젝트를 진행하면서 코드 리뷰에 대해 많이 배웠습니다.
1. 과감하게 의견을 표출해야합니다. 하지만 따뜻하게 해야합니다. 내가 틀릴 수 있다고 생각해 의견을 말하지 않는 것보단, 틀릴 수 있어도 말할 수 있어야 된다는 것을 배웠습니다.
2. 동료의 코드를 보는것도 중요합니다. 하지만 저의 코드 리뷰도 적극적으로 부탁 드려야 됩니다. 제 코드는 거의 리뷰가 달리지 않았습니다. 하지만 제 코드에도 부족하고, 위험한 코드가 많았습니다. 가령 페이징 처리를 하지 않거나, 연관관계를 많이 맺고 있는 엔티티를 조회할 경우 N+1이 발생해 서버에 무리를 줬습니다. 이런 경우 , N+1의 경우 배치 사이즈를 설정해 프로젝트 디폴트로 정할 수 있었다고 생각했습니다. 프론트엔드 작업자에게 코드 리뷰를 부탁했다면 프론트 엔드에서 페이징 처리를 할 수 있었습니다. 하지만 뒤늦게 제가 오류를 수정해 프론트 엔드 작업자가 작업을 한번 더 하게 됐었습니다.
이번 프로젝트를 진행하면서 좋았던 점은 팀원들과의 적극적인 소통이었습니다.
코드 리뷰에는 아쉬움이 있었지만, 팀원들과 소통이 원할했습니다. 제가 겪는 문제를 팀원들과 공유하고 팀원들과 같이 해결했던 경험은 동료들의 의미를 다시금 깨닫게 된 경험이었습니다.
1. 팀원들과 어려운 문제들을 나누고, 의견을 활발히 교류해 더 좋은 방향으로 프로젝트가 진행된 경험이 있었습니다.
뒤에 서술할 아키텍쳐에 대한 고민, 외부 API 연동 문제를 임시방편으로 다른 API를 찾아줘서 MVP 달성을 앞당겼던 경험이 있습니다.
2. 프로젝트를 진행하며 가장 기억에 남는 트러블 슈팅이 있다면 그 내용과 해결과정, 학습한 내용이 잘 드러나게 답변해주세요.
A.
1. 프로젝트 아키택쳐에 대한 고민이 있었습니다. 저희는 코드 컨벤션상 다른 도메인의 Entity를 가져올 때, 각 도메인의 Service에서 public getTopster(Long topsterId)와 같이 public으로 열어놔 다른 도메인에서도 사용할 수 있게 하기로 정했습니다. 이때 연관관계에 있는 두 도메인이 순환참조를 해 문제를 겪었습니다.
간단한 해결방법으로는 Service를 참조하지 않고, Repository를 참조해 각 도메인에서 Entity를 찾는 방법이 있습니다. 하지만 이는 코드의 중복을 야기 하기 때문에 좋지 않는 방법이라고 생각했습니다. 그럼 한쪽 방향으로 Service를 참조해야 된다고 생각했습니다.(1:N의 관계에서 한쪽 방향만 참조하도록 설계하자!)
이를 튜터님께 어떻게 문제를 풀어야 할지 여쭤봤습니다. 하지만 제 생각과는 반대 방향으로 참조해야 한다고 답을 주셨습니다.
튜터님의 말씀을 듣고 바로 적용할 수 있는 부분이었지만, 저는 팀원들과 현재 상황을 공유하고, 제 의견을 말했습니다. 제가 생각하기엔 튜터님과 반대방향으로 참조하는게 맞다고 의견을 표했습니다. 부리더인 경남님도 제 말에 동의를 해주셨고, 같이 튜터님께 다시질문을 하러 갔습니다. 튜터님이 잘 설명해 주셨지만, 저희는 잘 이해가 되지 않았습니다. 튜터님이 주신 키워드를 통해 정보를 찾아서 공부 했지만 크게 와닿지 않았습니다. 그래서 저희는 아키텍쳐를 하나 더 추가하기로 했습니다. 연관관계를 맺는 서비스들을 관리하는 새로운 서비스를 만들었습니다. 하지만 이 방식도 좋은 방식은 아니었습니다. Topster라는 Entity는 많은 Entity들과 연관관계를 맺고 있습니다. Like, TopsterAlbum, Album, Post 모두 관리하는 Service를 만드는 것은 결국 하나의 Service가 너무 많은 책임을 지며, 테스트도 어렵다고 판단했습니다.
이에 저희는 처음 저희가 정했던 N에 해당하는 도메인이 1에 해당하는 도메인의 Service를 의존하기로 정하고 반대 방향의 의존은 막는 방식으로 정했습니다. 아직 이런 방향이 어떤 부작용을 일으킬지 경험하지 않아 예측이 어렵지만, 이방향의 의존이 각 도메인의 책임을 한다고 생각했습니다.
이런 경험은 Facade Pattern이라는 새로운 아키텍쳐를 도입하는 계기가 됐습니다.
저희 프로젝트에서는 Topster Entity를 저장할 때, TopsterAlbum Entity와, Album Entity를 생성(조회)해야 했습니다. 따라서 TopsterService는 AlbumService, TopsterService를 의존해야 했습니다. 이는 위에서 말했던 반대 방향의 의존을 하게 됩니다. 따라서 저희는 TopsterCreateFlowService라는 Service를 추가했습니다. 이는 Controller와 Service 사이의 새로운 계층으로 Topster Entity를 생성할 때 Entity간 연관관계를 맺어주는 서비스 입니다. 이를 통해 각 도메인의 Service는 각 도메인의 Entity만 생성(조회)를 하고, TopsterCreateFlowService로 올려주면 TopsterCreateFlowService에서 연관관계를 맺어주어 Topster를 저장합니다.
이런 아키텍쳐의 변경은 클래스 구조가 복잡해진다는 문제점이 있지만, 장점으로는 클래스의 책임이 명확해지고, 각 도메인의 Service는 각 도메인의 Repository만 의존할 수 있따는 장점이 있습니다. 또한 테스트 코드를 작성하는 것도 굉장히 편해졌습니다.
3. 개발을 진행하며 어떤 문제가 발생하면 어떤식으로 해결하시나요?
A. 저희 팀 규칙으로 1시간 이상 고민하지 말기가 있었습니다. 한시간동안 구글링, 챗지피티, 유튜브등 최대한 많은 정보를 얻고, 실제 테스트를 해봤습니다. 그래도 문제가 풀리지 않으면 팀원들과 문제를 공유했습니다.
스포티파이 API를 이용하기 위해선 스포티파이의 Access Token이 필요합니다. 이를 발급받기 위해서 Client ID와 Client Secret Key가 필요합니다. Client ID와 Client Secret Key는 공개되면 안되는 정보이기에 환경변수로 설정했습니다.
하지만 이때 문제가 있었습니다.
1. Invalid Client 오류 발생
이 오류는 Client ID가 틀렸다는 오류였습니다. 복사 붙여넣기를 통해 넣은 값이기에 이 오류가 발생했다면 코드단에서 문제가 있을 거라 생각했습니다. 이를 해결하기 위해 정보를 모았고, 깃허브의 오픈 소스를 찾을 수 있었습니다.
오픈 소스를 이용해도 같은 오류가 있었습니다. 따라서 오픈소스가 문제가 있다고 판단해 해당 소스의 Github를 공부했습니다. 하지만 업데이트 시점도 최근이었고, 다른 블로그에서도 같은 코드로 간단히 Access Token을 받아오는 것을 확인했습니다.
2. 환경변수
이때부터는 팀원들과 함께 디버깅을 하며 문제를 해결했습니다.
Client ID와 Client Secret Key 값은 틀릴 수 없는 환경이기에 다른 변수를 고민했습니다.
기존의 방식은 필드값 위에 @Value를 달아 yml 파일의 환경 변수 값을 넣어주고 있었습니다.
@Value를 이용해 주입하지 않고, 필드에 직접 주입을 했습니다. 결과는 성공이었습니다. 그렇다면 문제는 환경변수가 제대로 적용되지 않는 다는 것이라 판단했습니다.
다시 @Value를 넣고 환경 변수가 들어가지 않는지 확인했습니다. 디버그를 하면서 필드값에 값이 잘 들어가는지 확인했습니다. 하지만 필드값이 잘 들어가는 것을 확인할 수 있었습니다.
그렇다면 환경변수도 값을 잘 넣어준다는 얘기였습니다.
3. 환경변수가 @Value를 넣어주는 타이밍
튜터님꼐 질문도 하고, 다른 팀의 잘하는 동료에게도 물어보고 있을 때, 경남님이 답을 찾아주셨습니다.
환경변수가 값을 잘 넣어주는 확인했기 때문에 다음 스텝은 순서가 틀렸다였습니다. 그제서야 떠올랐던 경험이 있었습니다.@Value값을 넣을 때 @PostConstruct를 이용해 Bean이 등록되는 시점에 @Value값을 넣어준다는 것이였습니다.
Bean이 등록될 때 @Value값이 바로 주입 되는 것이 아닌, Bean이 등록이 되고, BeanPostProcessor가 후에 등록해준 다는것을 알았습니다.
@PostConstruct 어노테이션을 단 메서드를 하나 추가해 Bean이 등록되는 시점에 필드값을 주입하는 방식으로 문제를 해결 할 수 있었습니다.
이런 경험은 제 머릿속에 각인이 되어 @Value가 언제 주입되는지 다시 공부할 수 있었습니다.