deVlog

[항해 플러스 백엔드 6기] 2주차 회고 - 레이어드 아키텍처 (with 클린 아키텍처를 곁들인...) 본문

Study/항해플러스 백엔드 6기

[항해 플러스 백엔드 6기] 2주차 회고 - 레이어드 아키텍처 (with 클린 아키텍처를 곁들인...)

은루밍 2024. 10. 7. 01:22

목차

     

     

    역시나 역시나 2주차도 힘들다...ㅎㅎ 그래도 평소에 해보지 못했던 거를 계속 도전하고 해결해나가는게 재미있긴 하다..

    백엔드 개발자로서의 역량이 채워지는 느낌이랄까.. 하지만 힘든건 어쩔 수 없다.

    그래도 끝까지 완주해낼 예정이다!!

     

    📚 과제 발제

    2주차의 발제는 아래와 같았다.

    더보기

    Default

    • 아키텍처 준수를 위한 애플리케이션 패키지 설계
    • 특강 도메인 테이블 설계 및 목록/신청 등 기본 기능 구현
    • 각 기능에 대한 단위 테스트 작성
    • 사용자 회원가입/로그인 기능은 구현하지 않습니다.

    STEP 3

    • 설계한 테이블에 대한 ERD 및 이유를 설명하는 README 작성
    • 선착순 30명 이후의 신청자의 경우 실패하도록 개선
    • 동시에 동일한 특강에 대해 40명이 신청했을 때, 30명만 성공하는 것을 검증하는 통합 테스트 작성

    STEP 4

    • 같은 사용자가 동일한 특강에 대해 신청 성공하지 못하도록 개선
    • 동일한 유저 정보로 같은 특강을 5번 신청했을 때, 1번만 성공하는 것을 검증하는 통합 테스트 작성

    상세 요구사항

    Description

    • 특강 신청 서비스를 구현해 봅니다.
    • 항해 플러스 토요일 특강을 신청할 수 있는 서비스를 개발합니다.
    • 특강 신청 및 신청자 목록 관리를 RDBMS를 이용해 관리할 방법을 고민합니다.

    Requirements

    • 아래 2가지 API 를 구현합니다.
      • 특강 신청 API
      • 특강 신청 여부 조회 API
    • 각 기능 및 제약 사항에 대해 단위 테스트를 반드시 하나 이상 작성하도록 합니다.
    • 다수의 인스턴스로 어플리케이션이 동작하더라도 기능에 문제가 없도록 작성하도록 합니다.
    • 동시성 이슈를 고려하여 구현합니다.

    API Specs

    1️⃣ (핵심) 특강 신청 API

    • 특정 userId 로 선착순으로 제공되는 특강을 신청하는 API 를 작성합니다.
    • 동일한 신청자는 동일한 강의에 대해서 한 번의 수강 신청만 성공할 수 있습니다.
    • 특강은 선착순 30명만 신청 가능합니다.
    • 이미 신청자가 30명이 초과 되면 이후 신청자는 요청을 실패합니다.

    2️⃣ 특강 선택 API

    • 날짜별로 현재 신청 가능한 특강 목록을 조회하는 API 를 작성합니다.
    • 특강의 정원은 30명으로 고정이며, 사용자는 각 특강에 신청하기 전 목록을 조회해 볼 수 있어야 합니다.

    3️⃣ 특강 신청 완료 목록 조회 API

    • 특정 userId 로 신청 완료된 특강 목록을 조회하는 API 를 작성합니다.
    • 각 항목은 특강 ID 및 이름, 강연자 정보를 담고 있어야 합니다.

    이번에도 저번주와 마찬가지로 동시성 제어 및 동시성 통합 테스트를 작성해야 했다.

    다른 점을 찾아보자면 이번 주차에서는 "다수의 인스턴스로 애플리케이션이 동작하더라도 기능에 문제가 없도록 작성했어야 한다" 라는 조건이 추가되었다.

     

    저번 주차에서는 애플리케이션 코드 레벨에서 Concurrent HashMap + Reentrant Lock 을 이용해서 동시성을 제어했었는데 이 방법은 동일 인스턴스 안에서만 유효하므로 쓸 수는 없었다.

     

    찾아보니 DB 락을 이용하면 다중 인스턴스 환경이더라도 동일한 DB 를 바라볼테니 적용해보면 적절하겠다 라는 생각이 들었다.

     

     

    🤔 과제를 진행하면서의 고민들

    패키지 구조는 어떤 식으로 해야할까?

    이번 주 발제에서는 코치님께서 클린 아키텍처에 대한 부분을 설명해주셨다. 이전에 따로 강의 들으면서 공부를 했기 때문에 익숙한 내용이었다. 그때 공부했을 때 "너무 구현할 코드가 많은데..? 실무에서 적용할 수 있을까?" 라는 생각이 들었었는데 코치님께서도 정석적인 방법보다는 레이어드 아키텍처를 기반으로 하되, 클린 아키텍처의 개념을 살짝 곁들인.. 구조를 설명해주셨다.

     

    내가 설계한 패키지 구조는 아래와 같다.

    ├── main
    │   ├── java
    │   │   └── io
    │   │       └── hhplus
    │   │           └── cleanarchitecture
    │   │               ├── lecture
    │   │               │   ├── application
    │   │               │   │   ├── dto
    │   │               │   │   ├── service
    │   │               │   ├── domain
    │   │               │   │   ├── model
    │   │               │   │   ├── exception
    │   │               │   │   └── repository (interface)
    │   │               │   ├── infrastruture
    │   │               │   │   └── persistence
    │   │               │   │       ├── entity
    │   │               │   │       └── repository (impl)
    │   │               │   └── presentation
    │   │               │       ├── api
    │   │               │       └── dto
    │   │               │           ├── req
    │   │               │           └── resp
    • application : 서비스 로직이 포함된 레이어로, 도메인 모델을 활용하여 실제 비즈니스 요구 사항을 처리하는 기능을 구현한다. 주로 서비스 클래스와 유스케이스가 위치한다.
    • domain : 핵심 비즈니스 로직을 담당하는 레이어이다. 엔티티, VO, 리포지토리 인터페이스와 같은 도메인 모델이 위치하며, 이곳에서 비즈니스 규칙을 정의한다.
    • infrastruture : 데이터베이스, 외부 API, 메시징 시스템 등 외부 시스템과의 상호작용을 담당하는 레이어이다. 리포지토리의 구현체나 외부 시스템과 통신하는 코드가 포함된다.
    • presentation : 사용자와의 인터페이스를 담당하는 레이어로, 주로 웹 요청을 처리하고 응답을 반환하는 컨트롤러가 위치한다.

     


    낙관적 락 VS 비관적 락

    동시성을 제어하기 위해서 DB 에서 적용할 수 있는 두 가지 방법이 있었다. 바로 비관적 락낙관적 락이다. 비관적 락의 경우 락을 미리 걸어버리고 다른 트랜잭션에서 데이터 접근을 할 수 없기 때문에 성능 이슈가 있다. 그래서 동시 요청이 발생하지 않는 경우라 판단된다면 비관적 락 보다는 낙관적 락을 많이 사용한다.

    하지만  "선착순으로 강의를 신청할 수 있다" 라는 요구사항이 있어서 동시 요청이 많을 것이라 판단을 했고 비관적 락을 사용하기로 했다.


    물론 낙관적 락 + 재시도 로직을 이용해서 구현할 수 있지만, 만약 100명이 동일 강의에 대해 동시에 수강 신청하게 되면 1명만 성공하고 남은 99명은 실패하고 재시도를 통해 처리해야 하는 상황이 있을 수도 있기 때문에 비관적 락을 사용하기로 했다.

    • 낙관적 락 (Optimistic Lock)
      • 트랜잭션 대부분 충돌이 발생하지 않는다고 가정하는 방법으로써 어플리케이션이 제공하는 방식
      • 읽는 시점에 Lock 사용하지 않기 때문에 데이터를 수정하는 시점에 다른 사용자에 의해 데이터가 변경되었는지 변경여부를 확인해야 한다.
      • 낙관적 락은 트랜잭션을 커밋하기 전까지는 트랜잭션의 충돌 여부를 없다.
    • 비관적 락 (Pessimistic Lock)
      • 자원에 대한 동시 요청이 발생하여 일관성에 문제가 생길 것이라고 비관적으로 생각하고 이를 방지하기 위해 우선 락을 거는 방식
      • 배타적 락(Exclusive Lock)공유 락(Shared Lock) 두가지 타입이 있다.
      • Shared Lock은 다른 사용자가 동시에 데이터를 읽을 수는 있지만 Write는 할 수 없다.
      • Exclusive Lock은 데이터를 변경하고자 할 때 사용되며, 트랜잭션이 완료될 때까지 유지되어 해당 Lock이 해제될 때까지 다른 트랜잭션은 해당 데이터에 읽기를 포함하여 접근을 할 수 없다.

     

     

    도메인 엔티티와 영속성 엔티티 분리

    이번 과제를 진행하면서 발제해주신 코치님의 말씀대로 공부 목적으로 좀 더 DDD 스럽게 JPA 엔티티(영속성 엔티티)와 도메인 엔티티를 분리하여 작업해보기로 했다.

    JpaEntity(영속성 엔티티) 와 도메인 엔티티를 나누고 JPA 엔티티끼리 연관 관계를 맺지 않고 논리적으로 키만 들고 있도록 구현해보았는데 이렇게 하다보니 객체 그래프 탐색이 불가능해 각각 조회를 해와서 서비스 레이어에서 조합을 해서 쓰거나 JPQL, QueryDSL을 이용하여 Join 을 통해 데이터를 가져오는 방법을 쓸 수 있었다.

    실제로 해보니 구현해야 할 코드도 많아지고, 서비스 로직에서 조합하여 쓰는 부분이 익숙치 않아서 힘들었다. 다음에는 멘토링 받은 코치님 말씀대로 생명주기가 같은 경우에는 연관관계 매핑을 해보려 한다. JPA 엔티티를 좀 더 활용하는 연습을 해보자!

     

     


    테이블 구조에 대한 고민

     

    위 그림은 내가 작성한 ERD 이다.

    여기서 나의 고민은 LECTURE_ENROLLMENT 에서 조회 성능을 높이기 위해 lecture_id 를 비정규화하여 갖고 있는데 해당 컬럼을 외래 키로 설정해야 하는가? 에 대한 고민이었다. 간단히 말하자면 조회용으로 비정규화된 컬럼에 외래키를 걸어야 하는가? 이다.

     

    구글링 및 코치님께 여쭤보니 굳이 외래키를 걸 필요가 없다는 이야기를 해주셨다.

    외래키를 추가하면 데이터의 무결성을 보장할 수는 있지만, 성능에 영향을 미칠 수 있고 필요 이상의 제약이 발생할 수 있다 라는 의견도 있었다.

     

    찾아보니 보통 성능을 위한 비정규화된 컬럼에는 외래키를 걸지 않고, 대신 애플리케이션 레벨에서 무결성을 관리하는 경우가 많다고 하다.

     

     

    🧑🏻‍🏫 이번 주 멘토링

    이번 멘토링 시간에도 우리 조에서 양질의 질문이 나왔다고 코치님께 칭찬을 받았다! 다음 타임 멘토링 하는 조에게 우리 조 멘토링 노트도 보여주셨다ㅎㅎ (6기 17조 최고다 진짜..!!! 너무 멋짐 😎) 

     

    다 이야기 할 수 없겠지만 기억 나는 부분을 적어보도록 하겠다.

    Q1. 엔티티 생성 시 생성자 vs 빌더 패턴 중 어떤 것을 선호하시는지 궁금합니다.
    A1. 생성자를 private 으로 감추고 정적 팩토리 메서드 패턴을 많이 사용한다고 하셨다. 기존의 나는 편의성 때문에 @Builder 를 남발하고 있었는데 좀 더 생각해보게 되는 멘토링이었다. 다음 주차에서는 활용을 해봐야겠다.

    Q2. 현업에서는 JpaEntity 끼리 연관 관계를 맺지 않는 경우가 있다고 하던데, 이러한 경우에는 JpaEntity 에만 관계를 맺지 않고 실제 DB 테이블에는 FK 를 거는지 궁금합니다.
    A2. Jpa 와 같은 ORM 은 객체 <---> DB 를 연결해주는 것인데 한 쪽에는 관계를 맺지 않고 DB 에서는 FK 를 설정하는게 패러다임에 어긋나는 것 같다. 1 ... N 관계에 있어, 1이 없어지면 N도 없어지는 생명주기라면 연관관계 맵핑 하지만, 1이 없어져도 N이 남아야 한다면 연관 관계 맵핑하지 않고, 논리적 맵핑을 한다. Long Nid;

    Q3. 교체할 가능성이 없어 보이는 서비스도 추상화를 꼭 해야 하나요? 아니면 혹시 모르니 무조건 해야 할까요?
    A3. 명확하지 않는건 심플하게 하자. 일단 class 로 만들고 추후에 요구사항이 들어오면 그 때 추상화를 고민해보자. "혹시" 라는 말이 가장 무서운 말이다..

    Q4. 지난 1주차에선 동시성 제어를 자바 단에서 했는데 이번 주차에선 DB쪽에서 낙관적 락/비관적 락을 사용해 처리하는거 같다고 생각합니다. 지난 1주차에서 처럼 하면 동시성 제어가 부족한건가요?
    A4. 데이터베이스 동시성 제어 VS 애플리케이션 동시성 제어 VS 분산락(Redis) 를 비교해보자.
           트랜잭션 간의 -> DB one row 동시성 제어. 여러 인스턴스
           다중 스레드 간의 -> 하나의 자원을 동시성 제어. 하나의 인스턴스


    Q5. 이번 과제에 다음과 같이 테이블 설계를 구조했는데, 적절한지 궁금합니다. 테이블 설계는 어떻게 공부하면 좋을까요?
    A5. 저는 DB 테이블 설계를 하지 않습니다. 객체지향프로그래밍에서 중요한건 메세지이고, 메세지를 사용하는 클라이언트가 누구이냐에 따라 데이터가 결정됩니다.

     

    굉장히 성심성의 껏 알려주셨다..! 렌 코치님 최고!!!
    가장 기억에 남는 부분은 DB 테이블 설계를 하는게 아니라 메시지를 먼저 정의를 하고 정의를 하다보면 도메인 모델링이 된다고 하셨던 말씀이다. 객체 지향 프로그래밍 관련 책에서 읽었던 적이 있었던 것 같은데 이렇게 코치님을 통해 다시 들으니 더 와닿았던 것 같다... 근데 어렵기도 함..ㅠ 연습해야지..!

     

     

    ⭐️ 결과물 및 피드백

    피드백

    피드백 진짜 꼼꼼하게 해주셨다.. 감동..🥹 코드 리뷰 요청 드리지 않았던 FakeTimeProvider 부분까지 세세하게 보시고 잘한 점과 더 찾아볼 수 있게 가이드도 주셨다! 최고다 진짜..

     

    코드 짜면서 고민했던 부분도 잘 짚어주셔서 한 번 더 생각해볼 수 있는 시간이 된 것 같다!! 다음 주차에는 더 보완해서 작업할 수 있을 것 같다!!

     

     

    결과물

     

    ERD : https://github.com/rueun/hhplus-clean-architecture/blob/main/docs/erd_overview.md

     

    hhplus-clean-architecture/docs/erd_overview.md at main · rueun/hhplus-clean-architecture

    항해 플러스 2주차 클린 아키텍처와 TDD 적용해보기. Contribute to rueun/hhplus-clean-architecture development by creating an account on GitHub.

    github.com

     

    깃 레포 : https://github.com/rueun/hhplus-clean-architecture

     

    GitHub - rueun/hhplus-clean-architecture: 항해 플러스 2주차 클린 아키텍처와 TDD 적용해보기

    항해 플러스 2주차 클린 아키텍처와 TDD 적용해보기. Contribute to rueun/hhplus-clean-architecture development by creating an account on GitHub.

    github.com

     

    이번 과제를 진행하면서 블루 배지 달성했다. 잠도 줄여가며 열심히 한 보람이 있다..!

    17조 모두 블루 배지!! 🌊🌊🌊 우리 팀원들 최고다...🤭🥰 팀원들이랑 무사히 항해를 끝마칠 수 있기를 바란다!

     

     

    🌸 이번 주 느낀 점

    • 1주차보다 테스트 코드에 대해 좀 더 익숙해진 것 같다. 테스트 코드를 작성해두니 리팩토링 과정을 거쳐도 뭐가 잘못된 거인지 눈에 바로 보이기 때문에 테스트 코드의 중요성에 대해 더 체득하게 된 시간이었다.
    • 미래의 불확실성 때문에 현재 요구사항 외에도 더 많이 생각하게 되는 경향이 있었는데, 현재의 요구사항에 더 집중을 하기로 생각이 들었다.
    • 테이블 설계를 하기보다는 객체 지향적으로 생각하는 방법을 연습해봐야겠다.

     

     


     

    항플 추천인 코드 :  KjKuTu