7 분 소요

?


개인 체형에 맞는 상품을 추천하고, 그 체형을 쉽게 분석할 수 있는 기능을 제공하는 쇼핑몰 서비스 핏터링(Fittering) 개발을
5월부터 11월까지 진행했다. 지금까지 개발한 프로젝트 중 적용한 기술이 가장 많기도 하고, 사용해본 경험이 없는 기술을 학습해서
적용하는 과정을 반복적으로 겪었는데 이때 발생한 에러가 꽤 많았어서 디버깅하느라 고생을 조금 했다. 그래도 사용해보고 싶었던 기술
들을 이번 기회에 활용해볼 수 있었고, 기획 단계에서 설정했던 모든 기술을 성공적으로 적용할 수 있었다는 점에서 의의를 두고 싶다.

첫 단계, 기획

?


쇼핑몰에서 원하는 옷을 찾을 때까지 드는 시간적 비용이 상당히 많다는 팀원 모두의 경험을 기반으로 프로젝트를 시작했다.
내 체형에 안맞거나, 상품 디테일이 맘에 안든다거나, 가격이 너무 비싸거나 등등 다양한 이유로 쇼핑하는 시간이 지나치게 길어지는
문제가 있었고 이를 해결해줄 수 있는 서비스가 있으면 좋지 않을까?라는 생각에서 출발했다.

디테일이 마음에 안드는 경우는 전체적인 스타일은 마음에 든다는 말로 해석되므로 선호하는 상품과 유사한 스타일의 상품을 추천받는
기능을 제공하기로 했고, 내 체형에는 맞지만 다른 스타일의 상품을 추천받고 싶을 수도 있으므로 비슷한 체형의 사용자가 선호하는
상품을 추천받는 기능도 제공하기로 했다. 마지막으로 내 체형을 정확히 모르는 경우 쉽게 측정할 수 있는 기능을 구현하기로 했다.
이 3가지가 핏터링의 메인 기능이라고 할 수 있고, 어떤 일련의 코드로 작성된 (일정한 매커니즘으로 결과를 산출하는) 알고리즘으로는
원하는 결과를 얻을 수 없거나 복잡도가 높아질 수 있다고 생각했기 때문에 머신 러닝 기술을 활용하기로 했다.
(물론 이 기능은 AI 개발을 맡아준 팀원 분께서 해주셨다)

백엔드 아키텍처

?


사실 백엔드 개발에서는 메인 기능 구현 동작에 대한 기여는 없다. 대신에 DB로의 접근은 백엔드에서만 이뤄지도록 했으므로, 서비스
내 모든 기능이나 렌더링에 필요한 데이터를 주고받는 API 구현은 모두 백엔드에서 이뤄졌다. DB 접근에 필요한 쿼리 작성은 Spring
Data JPAQuerydsl을 사용했다.

AWS 인프라에서 서버를 운영하기 위해 EC2, RDS부터 S3, CodeDeploy, CloudFront를 활용했다. 일부는 Github Actions
에 의해 CD가 이뤄지는 과정에서 사용됐고, CloudFront는 정적 데이터 캐싱 및 프론트에서 렌더링할 이미지 링크를 제공하기 위해
사용했다. 이때 경험이 없어서 프론트가 직접 S3에 접근해서 이미지 등의 데이터를 가져와 렌더링해야 하는줄 알았는데, 그렇게 하면
어떤 방식을 사용하든 간에 S3 액세스 키가 노출되는 불상사가 발생한다는걸 알게됐다. 주변에 조언을 구해보니 CloudFront를 통해
웹 배포된 링크를 활용해야 한다는걸 알게됐고, 처음 사용하게 됐는데 꽤 재밌었다. 프론트에서 그대로 활용할 수 있도록 가공해서 제공
해줘야 한다니! 보안적인 부분을 제외해도 프론트에서 바로 활용할 수 있게 백엔드에서 처리해줘야 한다는 인사이트를 알게돼서 기뻤다😊

RedisKafka 그리고 모니터링 및 데이터 수집 관련 기술(Grafana, Prometheus, ELK)은 서버와 통신하면서 동작해야
했는데 모두 Docker 컨테이너로 띄워서 관리했고, Redis는 캐싱 및 일부 API 요청들을 배치 업데이트하기 위한 임시 저장소로,
Kafka는 DB 간 동기화를 위해 사용했다. Kafka 때문에 고생한 적도 있었는데.. 디버깅 탭에 적어보겠다!

핏터링 서비스는 유저/상품/쇼핑몰 등 정해진 형태의 데이터를 갱신하거나 조회하는 요청이 많다보니 RDB를 주로 사용했고,
이전에 사용해봤던 MySQL을 활용했다!

꽤나 주저리주저리 썼는데🤔 간단히 말하면 사용 경험은 없지만 현업에서 활용하는 기술을 내 프로젝트에서도 사용해보고 싶다는 욕심이
있었고 그 욕심을 해소하기 위해 일단 아키텍처에 설정해놓고 시작했다. 프로젝트에 어떻게 적용했고 발생한 에러는 어떻게 트러블 슈팅
했는지 PR과 블로그에 기록하려고 노력했다! 내 것으로 만들기 위해 노력하는 과정이라고 생각하고 임했었는데 성공적으로 마칠 수 있었
던 것 같아서 만족스러웠다😆 과정이 순탄치는 못했지만 그만큼 유익했던 경험이었다. (그것이 개발이니까!)

중간 단계, 개발과 디버깅

지속적인 요구 사항 변경

요구 사항이 추가되거나 변경돼서 기존 코드를 수정해야 하는 경우가 잦았다. 기능에 필요한 데이터가 오지 않아서, 적절한 타입으로
오지 않아서, 충분한 협의가 이뤄지지 않아 통일되지 않은 데이터 이름으 도착해서, 프론트 개발에서 보다 편리한 유지보수를 위햐서
등등 여러 이유로 이미 구현된 API를 수정하거나 새로 뒤엎는 케이스가 종종 있었는데, 협업이니까 자주 이뤄질 수 있는 부분이라고
생각했다.

다만 내가 내 코드를 어떤 의도로 작성했는지 작성한 시점에서 시간이 많이 지났을 때 확인하려니 어려운 경우가 있었고
이를 해결하려고 전체 로직에서 특정 의도를 가진 부분 로직이 있다면 함수로 구분해서 구체적인 함수명을 지정한다던지, 파라미터에
들어가는 상수가 어떤 의미로 활용되는지 단번에 알 수 있도록 변수로 지정하는지 등 가독성을 높이기 위한 노력을 했었다. 실제로
이 작업이 이뤄지고 난 후에 로직 변경이 필요한 요구 사항이 왔을 때 분석하는 시간이 적게 들었고, 수정해서 발생하는 버그도 줄일
수 있었다. 전체 로직 작성 후 리팩토링보단 작성하면서 이를 적용할 수 있다면 더 좋겠다는 생각도 들었다!

Kafka : 동기화할 상품을 잃어버리는 문제

핏터링에서는 서비스에서 직접 활용하는 DB와 크롤링 상품을 담은 크롤링 DB 총 2개로 구성돼있다.
동기화를 위해 kafka를 적용했는데, 이때 크롤링 상품을 하나씩 접근해서 서비스 DB로의 업데이트를 해줘도 결과에는 차이가 없겠지만
한번에 업데이트하는 상품 개수가 천개 단위로 이뤄지다보니 동기화 작업 시간이 길어질거라 판단했고, kafka 내 message queue에
상품을 건당으로 나눠서 이벤트를 넣어준 뒤 consumertopic에 붙어서 발생한 이벤트를 비동기로 처리하도록 해 동기화 속도를
줄이고 싶었다.

하지만 일부 상품을 이벤트로 발행해주지 못하는 문제가 발생했고, 이에 대한 로그가 자세히 뜨지 않을 때도 있어서
원인을 확인하기가 어려웠다. 호출한 메소드 내부에 들어가서 로그를 찍어볼 수도 없었기에 한계도 있었고, kafka에 이를 해결해줄
설정이 있을지 찾아봤는데 follower복제본이 저장됐는지 체크한 뒤 이벤트 처리를 수행하도록 ACKS_CONFIG 설정을 -1로 수정
하면서 해결할 수 있었다. 원인을 찾는데 시간도 많이 걸렸고, 해결 방법을 찾기 어려워서 많이 힘들었던 문제였었다🥲

Kafka : 상품 중복 저장 문제

DB 간 동기화 작업 시 여러 cousumertopic에 있는 이벤트를 가져가게 되는데, 이때 비동기로 처리되다보니 같은 이벤트2번
이상 처리하는 케이스가 있었다. 서비스 DB에는 반드시 단 1번만 저장돼야 했으므로 중복 저장하지 않도록 처리해야했고, save()
Lock을 설정하면서 해결 자체는 간단히 할 수 있었지만 원인을 찾는게 어려웠던 문제였다. 동시성을 이런 케이스에서 고려해야 할지
몰랐는데 그래도 좋은 경험이었던거 같다!

JWT : 에러 코드, 잘못된 권한 체크 등

Spring Security에서는 기본 인증/인가 방식을 세션으로 설정한다. 나는 세션처럼 stateful 방식보다는 stateless 방식을
택하고 싶었고, 쿠키JWT 중 보안상 안전한 JWT를 적용했다. 적용하는 과정 자체는 어렵지 않았으나, 권한이 없는 사용자 (잘못
된 값을 갖거나 만료된 JWT)가 권한이 필요한 API 호출 시 403 Forbidden 에러가 나간다는걸 알게됐고 이 에러보다는
401 Unauthorized 에러가 나가는게 더 좋다고 생각해 찾아보다 JWT 필터를 Spring Security 설정 클래스에 적용하는 방법
을 알게돼 적용했다. 하지만 적용하고 나니 권한이 필요하지 않은 API를 호출하는데 권한이 없는 사용자도 필터에서 모두 체크해버려
401 응답을 받는 어이없는 상황을 겪었고.. 권한이 필요한 API는 URI에 /auth를 적었었기에 이를 포함하지 않는 API에 대한 요청
이라면 검증하지 않도록 필터 내에서 예외처리 해줌으로써 해결할 수 있었다. 필터가 참 일을 잘한다고도 느꼈고.. 꼼꼼하지 못하게
처리했던 나를 반성했던 시간이었다!

소셜 로그인을 지원하려고 기술 문서를 찾아보다가 OAuth2 방식에서 JWT를 사용하는걸 알게됐는데, 서버에서 발급해주는 JWT
우리 서버에서 그대로 사용하려면 기존에 발급하던 JWT와 담긴 내용이 달라 우리 서버에서 활용할 수 있도록 별도로 로직을 작성해줘야
한다고 생각이 들어서 인증만 맡기고 우리 서버에서 JWT를 발급해주는 방식으로 설정했다. 이번에 JWT를 처음 써봤지만 소셜
로그인에서도 의도한 방향으로 인증 흐름을 가져가도록 작성하는 재미가 있었다👍🏻

테스트 코드

?


처음엔 Business layer에 대해서 해피 케이스만 체크해줬었는데, 이렇게 하다보니 에러를 사전에 좀 확인하기 어려웠다.
그래서 3-tier에서 모두 해피/예외 케이스를 검증하도록 로직을 추가했고, Representation/Persistence layer에서는
Business layer와 다르기 다른 layer에 정의된 메소드를 직접 호출하지 않도록 Mockito를 활용해 mocking을 적용했고,
구현한 함수가 주어진 input에 대해 기대하는 output을 반환하는지 확인하도록 했다😄 이렇게 적용하고 나니 수정된 코드에서
발생하는 에러를 상당히 줄일 수 있었고, 테스트 환경 클래스를 정의하고 상속받도록 해 통합 테스트 환경을 설정하면서 테스트 시간
도 줄일 수 있었다!

권한이 필요한 API의 경우 권한이 있는 유저 정보가 필요해 애노테이션을 정의했던 것도 기억이 난다. 생각보다 잘 적용되지 않아서
많이 고생했었는데 지금 돌이켜보면 뿌듯하다😁

기록

?


학습한 기술들을 프로젝트에 어떻게 적용했는지 그리고 발생한 에러는 어떻게 트러블 슈팅했는지 핏터링 프로젝트 PR 또는
기술 블로그에 자세히 남기려 노력했다.

마무리, 버그 수정 및 최종 발표

메인 기능이 모두 구현되고 나서는 새로운 요구 사항에 따라 API를 수정하거나 간단한 버그를 잡으면서 프로젝트를 마감했다.
채용 프로세스때문에 워낙 분주한 시간을 보내다보니 추가적인 구현이 어려웠고, 최종 발표도 앞두고 있었어서 어떻게 무사히 마칠 수
있었다. 막바지에 시간이 많이 부족해서 개발에 집중하지 못했던 점이 조금 아쉽게 다가오지만, 그래도 그 전까지 거의 매일 새벽까지
개발하던 경험이 되게 재밌었던 것 같다. 앞으로도 이런 개발을 할 수 있었음 좋겠다는 생각도 든다.

뭘 잘했고, 뭘 못했을까

이번이 타 직렬 개발자와 첫 협업이었음에도 마찰없이 잘 마쳤다고 생각한다. 프로젝트 시작 전에 팀원들에게 이전 협업 경험에서
팀원에게 아쉬웠다고 느꼈던 점을 얘기해보는 시간을 가졌고, 주로 이전 팀원이 일을 제 시간에 마치지 못해서 본인 업무에 지장이 가는
등의 사례가 빈번했다고 들었다. 나는 이런 답답함을 제공하지 않았으면 하는 마음에 내가하는 일은 최대한 덜 영향이 가게끔 빠르게
마칠려고 노력했고, 요구 사항이 있다면 최대한 빨리 마무리지으려 노력했었다. 이런 점들이 나에게 신뢰를 가질 수 있게끔 했던 경험
이었던거 같고, 실제로 같이 팀으로 활동해서 좋았다는 평을 받으면서 훈훈하게 마무리지을 수 있었다🥹

나에게 아쉬웠던 건, 팀장으로서 방향을 제시해주는 역할을 강하게 하지는 못했다고 생각한다. 팀원들과 같이 얘기해보고 방향을 설정
한 뒤 같이 움직이는게 우리 팀의 주된 모습이었지 내가 설정하고 이끄는, 리더십과는 거리가 멀었던 행동을 해왔다고 느꼈다.
워낙 팀원들이 가진 역량이나 책임감이 좋다보니 신뢰하면서 일을 맡기고 내 업무에 몰입할 수 있었지만, 뭔가 이런 모습은 리더보다는
팀에 기여하려는 팀원의 모습에 가깝다고 느꼈다. 내 성향은 리더보다는 팔로워에 가깝지만, 리더를 맡게된 만큼 멋지게 이끌며 움직
이고 싶었는데 그것과는 거리가 멀었던 것같다. 그래도 다음에 리더 역할을 맡는다면 더 나은 성과를 보여주지 않을까 싶다.

앞으로 하고 싶은 것

간단히 적어보면!

  • kotlin + spring
  • 로그 추적기(Slf4j MDC) 적용
  • 서버 scale-out
  • 동시성 고려한 설계
  • 클린 아키텍처(?)
  • 백엔드 개발자와의 협업
  • 프론트 경험 (간단히)

이것저것 하나씩 하다보면 또 욕심이 나지 않을까!
이번엔 공부하고 적용하고 기록하는 과정을 반복했는데, 앞으로도 꾸준한 퍼포먼스를 가져가고 싶다😊

카테고리:

업데이트:

댓글남기기