Skip to content

joonfluence/ably-assignment

Repository files navigation

에이블리 백엔드 과제 - 이준호

  • 이 프로젝트는 Kotlin과 Spring을 사용하여 개발된 서버 애플리케이션입니다.

🛠 기술 스택

  • Language: Kotlin 1.9 (JVM 17)
  • Backend: Spring Boot 3.2.5, JPA
  • Database: MySQL 8.0
  • API Testing: JUnit5
  • Containerization: Docker, Docker Compose
  • 인메모리 데이터 그리드: Redisson

ERD

img.png

확인 순서

  1. Docker 실행 (Docker 설치 방법 : https://docs.docker.com/get-docker/)
  • Docker Desktop을 설치하고 실행합니다.
  • Docker Compose 를 설치하고 실행합니다.
docker-compose up -d 
  • MySQL 서버 시작: MySQL 서버가 시작되면 애플리케이션은 MySQL을 사용하여 데이터를 저장합니다.
  • Redis 서버 시작: 먼저 로컬 머신에서 Redis 서버를 시작해야 합니다. Redis 서버가 시작되면 애플리케이션은 Redis를 사용하여 분산 락을 구현합니다.
  • DDL 실행
    • (만약 테이블이 정상적으로 생성되지 않았다면) 아래 파일을 확인하여 MySQL 서버 내에 테이블을 생성합니다.
    • /src/main/resources/sql/ddl.sql
  • DML 실행
    • /src/main/resources/sql/dml.sql
    • 500 여개의 상품 데이터를 추가합니다.
  1. 애플리케이션 실행
./gradlew bootRun
  1. 테스트코드 실행 확인
./gradlew test
  1. API 호출: Postman API 문서를 사용하여 이러한 API를 호출할 수 있습니다.
    • Postman API 문서 : API 문서
    • 프로젝트를 Workspace로 Import하여 API 문서를 확인할 수 있습니다.
      • API 문서에서는 회원가입, 로그인, 찜 서랍 조회, 찜 서랍 등록, 찜 서랍 삭제, 찜하기, 찜 목록 조회 API를 실제로 동작시켜 확인할 수 있습니다.

로그인 이후, 토큰을 발급받아 아래와 같이 헤더에 추가하여 API를 호출할 수 있습니다. (사진 참고)

img.png

발급 받은 토큰을 Authorization에 Bearer Token으로 추가하여 API를 호출합니다.

img5.png

주요 기능

  • 최종 구현된 범위입니다
  1. 회원가입 및 로그인 : 이메일과 비밀번호로 회원가입 및 로그인을 할 수 있습니다.
  2. 찜 서랍 조회, 등록 및 삭제 : 내 찜 서랍을 조회하고 등록, 삭제할 수 있습니다.
    • 중복방지 : 단, 이미 있는 내 찜 서랍의 이름으로 생성할 수 없습니다.
  3. 찜 하기
  • 상품을 찜하거나 해제 할 수 있습니다.
  • 내 찜 서랍의 찜 목록을 볼 수 있습니다. 이 때, 페이지네이션 및 무한 스크롤로 탐색이 가능해야 합니다.
  • 찜한 상품이 내 다른 찜 서랍에 있을 경우, 찜할 수 없습니다.
    • 동시성 방지를 위해 분산락을 사용했습니다.
  • 찜 서랍이 하나도 없을 경우, 상품을 찜할 수 없습니다.

주요 설명

세션 기반 인증이 아닌 토큰 기반 인증을 사용한 이유

  • stateless한 서버 구조를 위해 토큰 기반 인증을 사용했습니다.
  • 세션 기반 인증에 비해, 보안성은 떨어지나 서버의 확장성이 높은 구조이므로 사용

동시성 보장 : 찜 서랍 등록, 찜하기 API

  • Redis와 Kotlin의 Trailing Lambdas를 사용한 동시성 처리
    • Redisson의 RLock으로 동시성 보장을 구현하면서 락이 획득되지 않는 상황을 고려해 타임아웃과 예외 처리를 포함한 안전한 분산 락 로직을 설계함
    • Kotlin의 Trailing Lambdas를 활용해 락 획득-해제의 코드 구조를 간결하게 처리하고, 가독성을 개선함
    • 락 구현을 공통화하여 찜 서랍 등록 외 다른 서비스에서도 재사용 가능하도록 설계한 점은 유지보수성과 확장성을 확보함
    • 분산 락을 활용하면서도, 서비스 레벨에서만 적용함으로써 필요한 최소 범위에서만 락을 사용하는 최적화 설계를 적용함
  • 멀티 유니크 키 제약 조건
    • 찜 서랍의 이름과 회원 ID를 조합한 유니크 키 제약 조건을 통해, 동일한 회원이 동일한 찜 서랍을 중복 생성하는 것을 방지함
    • 데이터베이스 레벨에서 유니크 키 제약 조건을 설정하여, 동시성 문제를 데이터베이스 레벨에서 해결함

주요 로집 테스트 코드 커버리지 90% 달성

  • 40 여가지의 주요 케이스 검증
    • 내 찜 서랍 등록, 삭제, 조회
    • 찜 등록, 해제, 조회
    • 로그인, 회원가입

img_2.png

  • 테스트 코드 작성
    • JUnit5를 사용하여 테스트 코드를 작성함

img_3.png

  • 전체 테스트 코드 라인 커버리지 80% 이상 달성
    • 주요 어플리케이션 로직에 대한 테스트 코드를 작성하여, 서비스 품질 보장
    • Controller 레벨에서의 통합 테스트
    • Service 레벨에서의 유닛 테스트를 작성하여 서비스 검증

조회 성능 개선을 위한 인덱스 설계

  • 찜 서랍 조회 API 성능 최적화
    • 회원 ID를 기준으로 인덱스를 생성
    • 전체 테이블을 검색하는 방식 대신, 인덱스를 활용하여 필요한 데이터를 빠르게 조회
CREATE TABLE IF NOT EXISTS wishlists
(
    id         bigint auto_increment
        primary key,
    name       varchar(255) null,
    user_id    bigint       not null,
    created_at datetime(6)  not null,
    updated_at datetime(6)  null
);

CREATE INDEX idx_wishlists_user_id
    on wishlists (user_id);
  • 찜 목록 조회 API 성능 최적화
    • 이를 위해 회원 ID와 찜 서랍 ID, 회원 ID에 각각 인덱스를 걸어줌
    • 회원 ID를 기준으로 찜 목록을 조회할 수 있기 때문에, 회원 ID에 대한 인덱스를 추가함으로써 빠르게 조회 가능함
    • 찜 서랍 ID에 대한 인덱스를 추가하여, 찜 서랍 ID를 기준으로 찜 목록을 조회할 수 있도록 함
    • 상품 ID에 대한 인덱스를 추가하여, 상품 ID를 기준으로 찜 목록을 조회할 수 있도록 함
CREATE TABLE IF NOT EXISTS wishes
(
    id          bigint auto_increment
        primary key,
    created_at  datetime(6) null,
    product_id  bigint      not null,
    updated_at  datetime(6) null,
    user_id     bigint      not null,
    wishlist_id bigint      not null,
    is_deleted  bit         not null,
    constraint wishes_product_id_user_id_uindex
        unique (product_id, user_id)
);

CREATE INDEX idx_wishes_product_id
    on wishes (product_id);

CREATE INDEX idx_wishes_user_id
    on wishes (user_id);

CREATE INDEX idx_wishes_user_id_wishlist_id
    on wishes (user_id, wishlist_id);

CREATE INDEX idx_wishes_wishlist_id
    on wishes (wishlist_id);
  • 기타 테이블
CREATE TABLE IF NOT EXISTS users
(
  id       bigint auto_increment
    primary key,
  email    varchar(255) not null,
  name     varchar(255) not null,
  password varchar(255) not null,
  constraint users_email_uindex
    unique (email)
);

CREATE TABLE IF NOT EXISTS products
(
    id        bigint auto_increment
        primary key,
    name      varchar(255)   not null,
    price     decimal(38, 2) not null,
    thumbnail varchar(255)   null
);

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages