스프링캠프 2024 [Track 1] 5.구해줘 홈즈! (이영규) 정리

겔로그·2024년 8월 30일
0

카카오 뱅크 레거시 시스템 Hexagonal 아키텍처 도입 영상 정리 글입니다.

고민 사례

1. 외부 응답 캐시

  • 외부 서비스 호출해 응답을 캐시해야 할 경우
    • application layer vs adapter layer
    • 캐시를 하는 이유
      • 중요한 비즈니스 정책인가?
      • 항상 캐시해야 하는 데이터인가?

  • 일반적으로 application layer는 단순히 adapter 를 통해 데이터 조회를 원하기 때문에 캐시는 adapter layer에서 다룬다.
  • application layer 에 캐시를 둘 경우, 다른 application layer에서 조회할 때 캐시 존재 여부를 파악해야 됨

반례1. (11:20)

  • 오픈뱅킹 잔액은 조회할 때마다 비용이 발생
  • 은행별 트래픽 제한도 존재

동일한 잔액 조회에도 비즈니스 정책에 따라 캐시 여부가 분리될 수 있기 때문에 어플리케이션 layer에서 캐시 정책을 다루는게 좋을 것 같다 판단.

반례2 (12:20)

  • 각 필드별로 다른 곳에서 조회를 해야 한다면 캐시는 누구의 책임이 되는게 좋을까?
  • 외부 호출 및 조합은 누구의 역할일까?

  • AccountPort를 만들어 조합해 반환한다.
  • 각 필드별 Port를 만들어 서비스에서 어플리케이션 조합한다.

=> 이는 도메인의 경계를 어떻게 나눌 것인지에도 영향을 준다.

  • 도메인에 각 정보가 포함되어 있으면 AccountPort에서 조합해 응답하는 것이 맞다고 생각함
  • 별개의 도메인이라 판단되면 Port를 분리해야 함

=> 도메인 정의 내용에 따라 Adapter의 분류 기준이 나뉜다.

결론

  • 비즈니스 정책에 따라 캐시 정책을 어플리케이션 layer에 둘지, adapter layer에 둘지 고려함
  • 도메인을 설계할 떄 어떻게 설계했는지에 따라 조합 로직이 위치하는 layer가 변경될 수 있다.

=> 따라서, 도메인 설계에 따라가는게 가장 바람직하다.

2. 성능적 문제: 많은 외부 호출과 성능

기존에는 legacy 코드가 monolith 아키텍처여서 조합해 홈 화면에 노출시키는게 쉬웠음

각 서비스가 MSA로 분리되다 보니 홈 화면에서 각각의 서비스 데이터를 조회해야 되는 상황 발생

  • 네트워크 구간이 많아져 성능상 우려로 다가옴

시도한 내용

동시성을 도입하자.

선택지

  • Spring @Async
  • Spring Webflux
  • Kotlin Coroutine

  • Webflux는 팀 내부적으로 부정적인 인식을 가지고 있어 제외

  • 여러 고려사항을 검토했을 떄, coroutine을 적용하기로 결정

Coroutine을 적용해보자 - suspend

1. controller suspend

최초에는 controller부터 모든 method에 suspend를 전체 메서드에 추가하고자 함

  • MDC와 같은 ThreadLocal이 정상적으로 동작하지 않음
  • Dispatchers.Unconfined를 넣어 ThreadLocal을 사용할 수 없음...
    • 현재는 CoWebFilter를 커스텀하게 수정해 사용 가능함!

2. outPort suspend

  • 호출부의 빌더 or suspend 사용이 강제됨

3. async/await 패턴 도입

  • Coroutine이 context를 전파하지 않기 떄문에 호출부가 직접 관리해줘야 하는 장점이자 단점이 존재

  • 동시성이 필요한 부분은 외부 서비스를 호출하는 구간만 필요하다는 것을 인지
  • 동시성이 전파되지 않게 구현하기 위해 async/await 을 도입하기로 결정

일반적인 케이스에선 suspend를 사용하지만, 주어진 환경과 제약에 알맞는 선택을 하는 것이 중요하기 때문에 async/await 을 도입하기로 함

  • 결과적으로 thread 사용량이 늘어났지만 성능은 개선됨

3. 안정적 이관 전략

요청/응답은 변경되지 않지만, 내부적으로 새롭게 만들어지는 서비스기 때문에 안정적인 이관 전략이 필요함

  • 기존 서비스의 응답과 신규 서비스의 응답을 비교하는 서비스를 신규로 추가함
    • 두 개 서비스 호출 시점에 데이터가 변경이 되어 검증 실패되는 케이스가 발생함
    • 이건 어쩔 수 없이 확인....

  • 검증을 위한 비용이 시스템에 성능 영향을 미치게 됨

  • 검증하는 표본을 100% -> 0.1%로 변경해 시스템의 부하를 줄임
  • 점진적으로 응답 비교 비율을 확대하며 각종 지표를 모니터링을 수행함

  • 실제 서비스를 완전히 전환하기 이전에 Gateway에서 트래픽을 분배하여 서비스가 되도록 처리

하이럼의 법칙

  • 기존 서비스에서는 제공 중이지만 신규 서비스에서 제공하지 않는 케이스가 존재했었음
  • 다른 시스템에서 기존 서비스에서 제공하는 기능을 사용 중이어서 문제가 발생

  • 문제 발생시 기존 서비스로 fallback 처리 되도록 구성
  • 운영 전환간 nginx 가 다운되는 이슈가 존재했는데 fallback 기능 떄문에 안정적으로 서비스가 운영되었음

정리

  • 쉬운 코드도 보일러플레이트 코드가 너무 많이 생기는 단점이 있었음

Q&A

Q: 외부에 조회하는 방식이 달라졌을 떄 어플리케이션 레이어에서 조합하는게 좋을지? 어댑터 레이어에서 조회하는게 좋을지? 궁금하다.

A: 정답이 있지 않는것 같지만 계정이라는 도메인의 필드 정보를 각기 다른 시스템을 호출해 조합하는 거라면 어댑터 레이어에 별도의 Facade 레이어를 만들어서 조합하는게 맞지 않을까라는 생각을 가지고 있다.

일반적으로라면 out adapter에서 구현할 것 같으나 도메인에 따라 다를 수 있을 것 같음

Q: 내부 여러 외부 시스템을 호출해서 조합할 떄 일부 데이터 조회에 실패한 경우 어떻게 하는지 ?

A: 실패해도 응답을 나가자하는 케이스와 아닌 케이스를 구분해서 실패가 발생하더라도 기본값을 응답주는 케이스가 있고, 중요한 정보는 API 실패 처리를 하도록 중요도에 따라 나눔

Q: 신규 서비스와 기존 서비스의 검증 서비스를 어떻게 관리했는지 궁금합니다.

A: 별도의 모니터링 환경을 구축하지는 않고, 검증 서비스에서 데이터가 다른 경우 로그를 남기도록 해 sentry에서 확인하고, 기존 알람 시스템을 활용해 실패를 확인함

Q: 개발환경에서도 충분히 검증할 수 있었던 것 같은데 왜 운영에서도 이러한 검증을 수행했는지 궁금하다

A: 테스트 환경에서는 우리가 사전에 정의한 값들로만 테스트하지만, 운영 상황에서는 정말 다양한 edge 케이스와 시나리오가 존재함.
이러한 케이스까지 고려하여 안정적으로 운영 환경으로 전환하기 위해서는 운영 환경 트래픽으로 검증해봐야 된다는 생각을 가져 운영환경에서도 검증을 수행함.

느낀점

  • 현재 팀에서 진행하는 Hexagonal 아키텍처에서 어떻게 처리할 것인지에 대한 고민이 일부 담겨져 있어 공감이 많이 되는 영상이었습니다. application layer에 둘 것인지 adapter layer에 둘 것인지도, 조회 api가 실패했을 경우 응답을 어떻게 줘야 할지 등 다양한 부분에서 비슷한 고민을 했었는데 비슷한 답안이 나온것 같네요. (어댑터에 퍼사드 패턴 적용건까지... 비슷비슷합니다.)

  • 캐시 처리나 성능 처리 관련해서는 아직까지 크게 검토하지 않은 상황인데 검토하는 과정에서 이 영상에 나온 일부 내용들을 적절하게 반영하면 좋을 것 같아 영상을 잘 봤다는 생각이 들었습니다.

  • 추가로... 코루틴에 대해 아직까지는 팀 내부적으로 검토를 많이 하지 않았었는데 성능상의 이점을 위해서라도 적극적으로 반영할 준비를 해야될 것 같습니다. 코루틴에 대한 이해도를 다함께 높이는 시간도 가져봐야겠네요.

profile
Gelog 나쁜 것만 드려요~

0개의 댓글