우아한테크코스 level3 levelog

sojukang·2022년 8월 31일
0
post-custom-banner

팀 프로젝트

인프라

  • 모니터링
    • 이상 동작으로 서버에 문제가 발생해도 메트릭 수집, 모니터링이 이뤄지지 않아서 원인을 파악하기 힘들었으며, 대응하기까지의 시간도 오래 걸렸다. 그래서 모니터링을 해야 할 필요성을 느껴서 도입하였다.
    • AWS의 EC2를 사용하고 있었기 때문에 CloudWatch가 후보에 올랐으나 수집 관련 권한이 필요하고 비용이 크게 부과되는 문제가 있어서 사용할 수 없었다. 그래서 오픈소스 환경으로 무료로 도입 가능하고, 트러블 슈팅에 필요한 자료가 많아야 하며, 실시간 모니터링에 특화된 기능이 필요했다. 그래서 Prometheus를 통해 메트릭을 수집하고 Grafana를 사용하여 대시보드를 구성하였다.
  • 지속적 통합, 배포
    • CI: 지속적 통합. 여러 명이 같은 프로젝트의 코드를 접근하면 다른 사람이 작성한 코드와 통합할 때 전체 어플리케이션이 완벽하게 동작할지 일일히 검증하기는 힘들다. 그래서 코드를 통합하는 과정에 부담이 커져서 통합 주기가 늦어진다. 그 결과로 버그를 알아채는 주기가 늘어나고 큰 버그가 발생할 확률도 높아진다. 따라서 통합시 빌드, 테스트를 자동화하고 코드 퀄리티를 검증하는 과정을 도입하여 코드 통합에 드는 비용을 감소시키기 위해 지속적 통합 인프라를 구축하였다.
      왜 Github Action, Jenkins를 사용하였는가?
      • 코드 버전 관리 도구로 Github을 이용하고 있었다. Github에서의 PR, Push등의 동작에 트리거를 설정하여 CI를 동작시키기 매우 편했고, Github 측의 리소스를 사용할 수 있어서 합리적이라고 생각하였다.
        Jenkins를 사용한 건 Github Action 환경과 우리의 EC2 환경이 완벽히 동일하다고 믿을 수 없었기 때문에 우리의 EC2 환경에서도 검증하기 위해 Jenkins를 사용하였다. 또한 Github Action이 우리 EC2에 접근하여 동작을 수행하기 위해서는 권한이 필요했기 때문에 Github Action은 Trigger가 발생하면 Push로 Http 요청을 보내 알리는 역할 까지를 하였고, 그 이후 CD 과정은 EC2에 설치된 Jenkins에서 처리하여 권한을 조정하지 않아도 되었다.
    • CD: 지속적 배포를 직접 구현할 경우 한정된 개발 인력을 배포 관련한 업무에 투입해야하는 문제가 있었습니다. 어플리케이션 개발에 자원을 투입하는 게 가장 효율적이라 생각하여 관련된 많은 기능을 제공하고 여러 기능을 플러그인으로 제공하는 Jenkins를 사용하여 배포하였습니다.

API

  • 클라이언트에 친화적이되 너무 종속적이지 않도록 설계
    • Restful하게 API를 설계하였나요?
      • URI에 delete 등 동작을 명시하는 게 아니라 컬렉션을 이용하는 것처럼 자원으로 URI에 접근하고, Http 메소드를 통해 행위를 구분하게 하여 API를 사용하고 설계하는 측에서 범용적으로 결과를 예측할 수 있게 하는 설계라고 생각하고 있습니다.
    • 레벨 3 팀미션은 프론트와 백엔드의 물리적, 소통의 거리가 너무 가까워서 클라이언트의 요구 사항이 최대한 반영된 API를 설계할 수 있었습니다. 이 때 발생한 문제로
      • 클라이언트에 필요한 Data를 최대한 하나의 API로 제공하려고 하다보니 서버 측에서 여러 객체를 조합할 필요가 생겨서 의존 관계가 복잡한 컨트롤러가 탄생했습니다. 그 결과로 API를 재활용하는게 힘들어졌고 코드를 파악하기도 힘들어졌습니다.
      • 그래서 최대한 클라이언트가 사용하기 쉬운 API를 설계하되, 클라이언트에서는 적절하게 구분된 API를 조합하여 사용할 수 있도록하여 서버측의 뷰 의존성을 줄이고 객체 관계를 정리했던 경험이 있습니다.

스프링

DB 트랜잭션

  • 전파 속성 정하기

    • Required, Required_New, Nested
      • Required: 부모 트랜잭션이 있으면 부모 트랜잭션에 통합. 하나의 물리적, 논리적 트랜잭션으로 진행
      • Required_New: 자식 트랜잭션이 각각의 물리 트랜잭션으로 진행. 자식 트랜잭션 롤백이 일어나도 부모 트랜잭션이 진행됨.
      • Nested: 자식 트랜잭션이 하나의 물리적 트랜잭션으로 통합. 자식은 논리적 트랜잭션으로 각각 적용되어 롤백이 일어나도 부모 트랜잭션이 진행됨.
  • 격리 수준 정하기

    • Read_Uncommitted, Read_Committed, Repeatable_Read, Serializable
      • Read_Uncommitted: 커밋되지 않은 데이터로 락이 걸리지 않아서 읽을 수 있는 상태. 더티 리드 발생 가능
      • Read_Committed: 커밋되지 않은 데이터는 변경되지 않은 데이터를 읽을 수 있는 상태. Non-Repeatable Read 발생 가능(ex: 커밋 되기 전에 읽은 데이터와 커밋 된 후에 읽은 데이터가 다를 수 있음)
      • Repeatable_Read: 한 트랜잭션 내에서 조회한 데이터가 항상 같도록 보장하는 상태. MVCC로 언두 로그를 관리하는 MySql InnoDB같은 경우 계속 같은 버전의 언두 로그를 읽어서 보장한다. 이 특성상 일반적인 Phantom Read를 방지할 수 있다(Select for Update 등으로 읽을 경우 언두 로그가 아니라 실제 데이터를 읽는다). 그러나 다른 DB의 경우 Phantom Read를 방지하지는 못 한다.
      • Serializable: 비관적 락을 사용하여 수정 뿐 아니라 조회에도 공유 락이 설정되어 접근하지 못한다. 동시성을 상당히 제한한다. Phantom Read를 방지한다.
  • 롤백 정책 정하기

    • 스프링의 기본 정책은 Runtime Exception, Error에 대해 기본적으로 롤백한다. 이때 예상하지 못한 Checked Exception이 발생할 경우 롤백하지 않기 때문에 이를 방지하려면 Exception에 대해 롤백하도록 지정해줘야 한다.
    • 예를 들어 SqlException이 Checked Exception인데, 데이터 접근 기술로 JPA나 JdbcTemplate을 사용한 경우 SqlException을 Runtime Exception으로 변환하여 반환하기 때문에 문제가 발생하지 않지만, 다른 접근 기술을 사용할 경우에도 일관적으로 트랜잭션 롤백을 보장하기 위해 Checked Exception에 대해서도 지정해줄 필요가 있다.

테스트

  • 모든 빈을 등록하지 않고 통합 테스트
    • 통합 테스트를 위해 SpringBootTest를 사용할 수 있는데, 이 경우 실제 어플리케이션을 실행할 때의 비빈을 모두 등록하기 때문에 테스트가 무거워지고 리소스가 많이 소요된다. 따라서 통합테스트에서 필요한 빈들만을 등록하기 위해 MockMvcTest를 사용하거나 DataJpaTest를 사용하였다.
  • 테스트별 데이터 격리
    • 컨텍스트 오염 문제를 막기 위해 가장 쉽게는 테스트마다 새롭게 어플리케이션 컨텍스트를 띄우는 방법이 있는데, 테스트 시간이 너무 오래 걸리고

JPA

지연로딩

  • Transaction 경계에서 주의할 점
  • OSIV 관련하여 주의할 점
  • 동일성 검증 관련 주의할 점
    a.equels(b)

영속화

  • DB 반영 시점
  • 연관 객체 영속화
  • 테스트에서 주의할 점
profile
기계공학과 개발어린이
post-custom-banner

0개의 댓글