첫 회사에서 처음으로 맡아본 프로젝트가 마무리됐다.
항상 풀스택을 목표로 했었지만 진짜 실무에서 풀스택으로 밑바닥 설계부터 끝까지 맡는다는건 역시 생각보단 고난했다.
반년이 조금 넘은 지금 회고록을 작성해보기로 했다.
사내에서 기존에 사용하던 웹 관련 프론트, 백엔드의 기술스택을 마이그레이션하게 됐다.
상세하게 프론트는 Angular에서 React로, 백엔드는 Flask에서 NestJS로 옮기게 됐다.
이런 선택을 하게된 이유는 아래와 같다.
결정적으로 회사에서 서비스를 확장하려고 하는 단계였으며 DB, 서버, 프론트에 대규모 수정사항이 생길 구조적 변경 요구사항이 생기게 됐다.
현재 상태를 유지하면서 추가 개발을 진행하는 것은 장기적으로 고려해봤을때 결국은 기술부채를 계속해서 늘리기만 할 뿐이라 판단했으며 이에 마이그레이션 작업을 제안하여 진행하게 됐다.
앞서 말했듯이 레거시 서버는 라우팅, 비즈니스 로직, DB 제어, 응답 등 모든 로직이 하나의 함수에서만 이루어져 있었으며 당연하게도 재사용성은 미비했고 중복 로직이 다수 존재했다.
API 문서 또한 존재하지 않았기 때문에 처음에 분석하고 재설계하는 과정에서 굉장히 많이 고생했던 기억이 아직도 난다.
DB 구조에서도 무분별한 인덱스 설정과 사용되지 않는 컬럼, 잘못 사용되는 컬럼 등 많은 문제점이 존재하는 것을 확인할 수 있었다.
같은 TypeScript로 구성할 수 있는 Express를 선택할 수도 있었겠지만 이미 위의 문제점들을 겪어본 입장에서 장기적으로도 아키텍처가 지켜질 수 있도록 프레임워크 차원에서 지원받고 싶어 NestJS를 선택했으며 어떻게 만들어야 유지보수, 추가 기능 개발에 용이할 수 있을지 찾아보았고 클린 아키텍처라는 것을 공부해 도입해보기로 결정했다.
각 레이어를 뚜렷하게 정의하여 나누고 분리된 의존성을 토대로 따로따로 개발하며 이를 이용한 이점으로 테스트 코드를 작성해보고.. 처음엔 모든 것이 좋은 느낌이었다. 하지만 얼마 안가 여러 고민들이 깊게 자리하게 됐으며 일주일 내내 구조만 계속 변경했던 시기도 있었다.
클린 아키텍처로 구성했다는 많은 깃 레포, 블로그 자료 등을 참조해보며 생각해본 결과 이런 의문들이 계속 생겨나는 근본적인 이유는 '이미 DB가 완성되어 있었으며 사실상 DB 스키마에 의존하여 단순한 CRUD만 진행하는 서비스'였기 때문에 더더욱 그런쪽으로 밖에 생각하지 못하게 됐던 것 같았다.
이를 교정하기 위해 '만들면서 배우는 헥사고날 아키텍처 설계와 구현' 이라는 책을 많이 참고했었으며 덕분에 클린 아키텍처를 온전하게 사용하는 법을 터득했다.
결국 해당 프로젝트 안에서 DTO를 어떻게 만들고 사용할지에 대한 컨벤션을 정했으며 명확한 기준을 나눠서 인터페이스와 클래스를 적절히 섞어서 사용하게 되었다.
통일성 관점에서도 별로 좋진 않은 것 같고 클래스에 static 메서드(안쪽 레이어의 DTO로 변환하는 함수)가 포함되어 있었기 때문에 DTO의 정의랑도 잘 맞지 않는 거 같았지만 DTO 관련 논쟁은 우리 뿐만이 아니라 다른 사람들도 많이 겪는 문제인 것으로 보아 확실하게 구분지어 정의할 순 없는 영역이라 생각했고 여러 시행착오 끝에 도달한 그나마 납득 가능한 정의들이었다.
해당 방식은 pvarentsov/typescript-clean-architecture 레포지터리에서 참조했다.
클린 아키텍처를 효과적으로 사용하기 위해선 고려해봐야 되는 점이 생각보다도 굉장히 많았으며 이런 개념이 생겨난 이유가 많이 와닿았다. 역시 최우선적으로 지켜야 될것은 '의존성의 흐름, 방향과 제어' 라고 생각해 그것을 중점으로 뒀으며 결과적으로 나름 납득 가능한 선에서의 아키텍처를 정의해냈고 이로 인해 유닛 테스트를 작성하는데에 많은 이점을 가져올 수 있었다.
보통 NestJS로 프로젝트를 구성할 때 TypeORM을 많이 사용하며 공식문서에서도 이를 기반으로 설명하고 있다.
실제로 프로젝트를 구성했을 단계에서 여러 ORM을 비교해봤을 때 TypeORM이 여러면에서 적합하다고 판단되어 도입했지만 정말. 정말... 여러 문제가 많았다........
프로젝트의 중반부터는 정말 하루에도 몇번씩이나 TypeORM... 또 TypeORM 너야..... 라는 생각을 달고 살았던 것 같다. 이쯤부터 Prisma를 잠깐씩 공부해보고 있었는데 물론 프리즈마도 깊게 파고 들어가면서 프로젝트를 진행해보면 문제점이 생기긴 하겠지만 그래도... 정말 TypeORM에 비해서는 천사처럼 느껴질 정도로 사용 경험이 굉장히 좋았다.
개인적으로 TypeORM은 결국 스프링 JPA의 엄청난 하위호환일 뿐이라는 느낌을 굉장히 많이 들게했으며 이걸 사용할 바에는 차라리 스프링으로, JPA를 사용해서 구현하는게 낫겠다 싶었다..
그에 비해 Prisma는 확실하게 독자적인 방식을 채택하며 좀 더 TypeScript, NodeJS스럽다 라는 느낌을 많이 받을 수 있어 만족스러웠다.
비록 NestJS도 TypeORM도 스프링 환경을 참조해서 만들었다곤 해도 결국 독자적인 방향을 추구하지 않는다면 '그거 쓸바엔 그냥 스프링 쓰면 되는 거 아냐? 어차피 스프링에 비해서 기능도 굉장히 빈약하고 불안정하던데' 에 그치지 않을까 라고 생각한다.
입사 후 3~4주차. 아직도 그때의 괴로운 기억이 선명하게 남아있었다.
레거시 프로젝트의 배포 프로세스를 로컬에서 테스트해보고 실제 배포를 진행했던 날이었는데 정말 끔찍한 경험이었다.
덕분에 정말 이것만은 개선을 꼭 해내야겠다 라고 마음을 먹었었으며 결과는 아래와 같다.
AWS의 여러 기능들을 처음 써보게 되었으며 보름 가까이 코드와는 멀어져 공부만 했던 것 같다.
인프라, 데브옵스 관련 정보글들은 찾아보면 항상 추상적으로 설명만 하고 구체적인 설정 예제가 없어 굉장히 막막하기도 했고 제대로 구축하고 있는게 맞을까 잘못되면 어떡하지 하는 걱정에 겁이나서 하루종일 손도 못댔던 기억이 난다.
더군다나 AWS 콘솔 환경이 신버전으로 업그레이드하고 있는 중이었으며 구버전과 신버전을 번갈아가면서 특정 기능이 존재하는지 찾아봐야 했다......
그래도 결국 공식문서에는 모든 답이 있었고(그렇게 친절하진 않았지만) 결국은 완전히 구축을 해내 실제 배포가 진행되고 수정사항이 생겼을 때도 main 브랜치에 push가 되는 순간 파이프라인이 돌고 n분 뒤에 새 버전으로 배포가 성공적으로 진행되는 것을 확인했을 때의 성취감은 정말...
직접 구축하고 나니 그 수많은 정보글들이 왜 추상적으로만 알려줬는지 이해가 됐었다.
배포 환경에서의 아키텍처도 일부 수정된 부분이 있었는데 원래 레거시 구조에선 컨테이너로 띄워져있던 traefik이 로드밸런싱을 담당하고 있었으며 라우팅은 가비아를 사용했었다.
여기서 컨테이너들이 고정된 포트를 사용했기 때문에 당연히 오토스케일링이나 무중단 배포는 사실상 불가능한 구조였다.
이를 해결하기 위해 AWS의 여러 기능들을 검토해서 적극적으로 사용해 개선했으며 현재 서비스는 언제든 확장이 편리하게 가능한 상태로 개선해냈다.