Skip to content

소셜 블로깅 사이트 Backend API

Notifications You must be signed in to change notification settings

DSS-1st/realworld

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RealWorld

개요

  • 소셜 블로깅 사이트(e.g. Medium.com) Backend API 구현 프로젝트
  • 기본 CRUD, 태그, 팔로우, 좋아요, 토큰 인증 구현
  • 테스트 코드를 기반으로 실무자 PR 리뷰를 통한 완성도 제고
  • 2명으로 2달간 진행(2023.04.11~2023.06.13)
  • 오픈 소스 RealWorld Backend API 사양 준수

담당 영역

  • 올찬: DB 모델링, Article, Tag, 예외 처리, 인증
  • 풀바셋: User, Profile, Comment

실무자 4명의 코드 리뷰 적용

트러블슈팅

Testcontainers를 통한 멱등성 있는 테스트 환경 구축
  • 이슈: 다른 사람이 프로젝트를 내려 받아 테스트 코드를 실행하면, DB 관련 테스트는 전부 실패
  • 원인: 테스트 환경과 동일한 DB를 다른 로컬 환경에서 자동으로 구성하도록 구현하지 못함
  • 해결 과정
    • 코드 리뷰를 통해 힌트를 얻어 Testcontainers 라이브러리를 적용하여 테스트 시 자동으로 DB를 구축하는 데에 성공했지만, 테스트 클래스마다 Docker 컨테이너가 실행되고 종료되어 테스트가 지연
    • 모든 테스트가 종료될 때까지 컨테이너를 재사용할 수 있는 방법이 필요하여 Testcontainers 공식 문서와 여러 기술 블로그 확인 결과, application.yml 파일에서 데이터 소스를 Testcontainers로 지정하면 재사용할 수 있음을 확인
    • 개발 환경과 테스트 환경의 DB 설정을 분리하여 application.yml 파일을 작성하고 @ActiveProfiles를 통해 테스트 코드에 적용
  • 결과: 컨테이너를 재사용하도록 최적화하여 테스트 소요 시간을 1/3로 축소, 다른 환경에서 모든 테스트가 통과됨을 확인, 이를 통해 다른 사람이 테스트를 실행하면서 애플리케이션이 어떻게 동작하는지 파악 가능
@JsonTypeInfo 사용으로 DTO 구조 단순화
  • 이슈: API 요청/응답에 사용되는 DTO 내부의 이너 클래스 사용으로 코드 복잡도 증가
  • 원인: API Spec에 맞게 JSON 데이터 이름을 정하려면 DTO 내부에 이너 클래스 생성 필요
  • 해결 과정
    • @JsonRootName을 통해 이너 클래스 없이 JSON 데이터 이름을 지정할 수 있도록 개선
    • 하지만 @JsonRootName이 필요 없는 경우에도 적용되어 API Spec 위반 사례 발생
    • 일부 DTO만 적용될 수 있도록 @JsonTypeInfo, @JsonTypeName을 함께 사용하여 @JsonRootName 대체
  • 결과: DTO 구조 단순화 및 코드 가독성 개선

ERD

ERD_realworld.png

핵심 로직

Slug
  • Slug 정의
    • URL에서 웹페이지 내용을 함축하는 단어로 이루어진 구절, 검색 엔진 최적화에 사용됨
    • 프로젝트 내 Article, Comment 관련 API Endpoint에 포함(총 8곳)
      • POST /api/articles/:slug/favorite
      • GET /api/articles/:slug/comments
  • 요구사항
    • Slug가 Article(게시글) 내용을 함축하고 유일성을 갖게 해야 함
  • 구현과정
    • 게시글 내용을 함축하는 제목을 기반으로 Slug를 생성하도록 구현
    • 유일성 문제는 게시글이 생성될 때 만들어지는 PK 번호가 접미사로 붙도록 하여 해결
    • 그런데 insert 되기 전에 PK 번호를 알아내어 insert 하는 MySQL 쿼리가 존재하지 않음
    • 이에 Article 테이블의 마지막 PK를 조회 후 Slug 접미사 생성 파라미터에 +1을 추가하도록 구현(해당 코드)
    • Article Update의 경우 Slug 접미사에 Article PK가 그대로 사용되어야 하므로 Slug 생성 시 기존 PK 사용하도록 구현(해당 코드)
    • 향후 Slugify 라이브러리를 적용하여 완성도 향상
List Articles
  • 요구사항
    • 게시글 작성자, Tag, 게시글을 좋아한 사람으로 검색할 수 있어야 함
    • 조회 결과를 페이징할 수 있어야 함(기본값: 1페이지당 게시글 20개)
    • 로그인 여부에 따라 게시글 좋아요, 작성자 팔로우 여부가 다르게 표시되어야 함
  • 구현과정
    • 검색조건과 연결된 테이블은 총 4가지(article_tag, tag, article_users, users)이며 article을 driving table로 left join 하여 검색 결과에 해당하는 article이 조회되도록 구현(해당 코드)
    • 위에서 얻은 article 목록을 태그, 팔로우, 좋아요 정보와 결합(해당 코드)
    • 결합한 데이터를 API 응답 형식에 맞게 DTO로 반환(해당 코드)
    • 검색 결과가 없으면 빈 객체를 반환, 있으면 stream을 통해 domain 계층의 데이터를 DTO 객체로 변환

사용 기술

  • Java 11
  • Spring Boot 2.7.10
  • JUnit 5
  • Spring Security 5.7.7
  • Java JWT 4.4.0
  • MySQL Server 8.0.29
  • MyBatis 3.5.11
  • Gradle 7.6.1

프로젝트 설치 및 사용 방법

사전 설치
  • Java 11
  • Docker Desktop: Testcontainers에서 DB 구동을 위해 필요
    • Gradle로 프로젝트 빌드 시 Testcontainers를 통해 테스트 코드 실행됨
  • MySQL Server 8.0.29
프로젝트 구동
  • 프로젝트 파일을 다운받아 압축 해제 후 터미널로 gradlew build 실행
  • 빌드가 완료되면 다음 경로에서 테스트 결과 확인 가능
    • 프로젝트 루트 폴더/build/reports/tests/test/index.html
  • MySQL DB 초기화
    • Workbench를 통해 루트 계정으로 realworld schema 생성 필요
    • application-dev.yml에서 username, password 알맞게 수정
      spring:
        datasource:
          driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
          url: jdbc:log4jdbc:mysql://localhost:3306/realworld
          username: ********
          password: ********
    • 앱 구동 전 src/main/resources/db/V1_create.sql 파일로 테이블 생성 필요
  • 프로젝트 루트 폴더/build/libs로 이동 후 터미널로 java -jar realworld-0.0.1-SNAPSHOT.jar 실행
  • http://localhost:8080/ 을 호스트 주소로 하여 API Endpoints 사용 가능
  • 실행 후 터미널에서 Ctrl + C 입력하여 종료 가능

참고 문서

About

소셜 블로깅 사이트 Backend API

Resources

Stars

Watchers

Forks

Languages