Skip to content

battlecruisers/yanullja

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

83 Commits
 
 
 
 
 
 
 
 

Repository files navigation

🏠 숙박 예약 서비스 야널자 Backend

야놀자 백엔드 클론 프로젝트

개발기간 : 2024-01-08 ~ 2024-02-02

API 스웨거 링크 : https://api.yanullja.com

📖 프로젝트 소개

야놀자 숙소 예약 서비스의 백엔드 클론 프로젝트입니다.

조건에 맞게 필터를 설정하여 자신에게 필요한 숙소를 검색하고 찾아볼 수 있습니다.

숙소를 예약하고 후기를 작성하여 평점을 남길 수 있습니다.


💡 기술적 도전 & 고민 사항

김대현

문제 무중단 배포를 위해 백엔드 서버를 재시작해도 유저에게 피해를 주지 않으려면 무엇을 고려해야 하는가?
해결 서버를 재시작해도 인증 / 인가에 영향을 받지 않도록 유저 세션 데이터를 Redis와 같은 외부 DB에 저장한다.
문제 Client가 Authorization이 필요한 api에 접근할 경우, DB의 멤버 테이블을 조회하는 빈도 수를 어떻게 줄일 것인가?
해결 세션을 Redis에 연결하여 Member 정보를 바꾸지 않는 api 요청에 대해서는 Redis의 세션 정보를 신뢰한다.
문제 프로그래머의 실수에 의해, 멤버 정보가 바뀌는 api를 호출하고 난 후에도 Redis에 저장된 세션 정보를 갱신하지 않아 발생하는 버그를 어떻게 방지할 것인가?
해결 해결책을 모색하는 중입니다.
문제 JWT를 통해 인증을 구현했다고 가정하자. 회원 탈퇴를 어떻게 구현할 것인가? 클라이언트는 여전히 유효한 JWT 토큰을 가지고 있다.
해결 상황에 따라 여러 방법이 있지만, 최근 회원 탈퇴한 유저들의 목록을 Token Revocation List에 담아 관리하고 추가 검증하는 방식을 구현한다.

자세한 내용은

https://abalone-coneflower-269.notion.site/Yanullja-Backend-a9a8719eccfe4146a50e4fe10da8fa40?pvs=4

를 참고바랍니다.

김민우

문제 포토 후기만을 조회하기 위해 1대N 테이블 간에 페이징을 적용해야 하는 상황
해결 후기 테이블(1)과 이미지 테이블(N)을 조인하여 이미지가 있는 모든 후기 데이터를 조회한 뒤, distinct 처리를 통해 중복을 제거. JPA의 Lazy Loading으로 이미지 Entity를 초기화. ( N + 1 문제가 발생하지 않도록 Batch size 설정 )

문제 QueryDsl에서 동적으로 JOIN을 적용해야 하는 상황
해결1 여러개의 메소드를 만들어서 상황에 따라 맞는 메소드를 호출하도록 구현.
해결2 조건에 따라 동적으로 JOIN을 적용하는 메소드를 만들어 적용.
가독성을 증진시키기 위해 들여쓰기를 활용하여 기존의 JOIN 형태와 비슷하게 유지.

해결2 코드

List<Review> r;

JPAQuery<Review> selectQuery = query
    .select(review)
    .distinct()
    .from(review)
    .join(review.member, member).fetchJoin()
    .join(review.room, room).fetchJoin();
r = innerJoinIfPhotoOnly(selectQuery, cond.getHasPhoto())
    .where(
        review.place.id.eq(cond.getPlaceId()),
        roomIdEq(cond.getRoomId())
    )
    .orderBy(reviewSort(cond))
    .offset(pageable.getOffset())
    .limit(pageable.getPageSize() + 1)
    .fetch();

이비안

문제 숙소 예약 시, 사용 기간 동안 숙소(Room)가 사용 가능한지 확인하는 기능 구현
해결 [기술적 도전] 특정 기간 내 숙소 예약 가능여부 판단 (이비안)

임현우

문제 다양한 검색조건을 받아서 검색을 진행하는데 너무 많은 if~else문이 존재
해결 [전략(Strategy) 패턴 사용으로 해결] -> 각 검색 조건을 하나의 인터페이스를 상속받는 별도의 클래스로 분리하여 구현하는 방식으로 리팩토링을 진행해 확장성, 유지보수성 증가.

자세한 내용은

https://scalloped-answer-62b.notion.site/fb25c0fa7ae54fae81556684ae469e78?pvs=4

를 참고바랍니다.

염금성

문제 클래스간 변환작업 중 발생하는 반복적인 코드를 어떻게 줄일 것인가?
해결 MapStruct를 사용해 컴파일 타임에 클래스간 변환작업을 수행하는 매핑 코드를 자동으로 생성한다.

클래스간 변환작업이 필요할 때 일반적으로 다음과 같은 코드를 작성하게 된다.

예시코드:

    couponDto.setDiscountRate(coupon.getDiscountRate());
        couponDto.setDiscountLimit(coupon.getDiscountLimit());
        couponDto.setDescription(coupon.getDescription());
        couponDto.setRegion(coupon.getRegion());
        couponDto.setRoomType(coupon.getRoomType());
        couponDto.setIsValid(coupon.getIsValid());
        couponDto.setIsRegistered(coupon.getIsRegistered());
        couponDto.setValidityStartDate(coupon.getValidityStartDate());
        couponDto.setValidityEndDate(coupon.getValidityEndDate());
        return couponDto;
    }

위와 같은 방식은 생산정 저하 및 개발자의 실수를 유발할 수 있는 문제점이 있음. 이런 상황에서 MapStruct를 사용하면 다음과 같이 간단하게 인터페이스를 정의하는 것으로 매핑 작업을 처리할 수 있다.

@Mapper
public interface CouponDtoMapper {
    CouponDtoMapper INSTANCE = Mappers.getMapper(CouponDtoMapper.class);
    CouponDto toCouponDto(Coupon coupon);
}

위 인터페이스를 작성함으로써 매 컴파일 타임에 MapStruct를 구현한 구현체 클래스를 생성해준다.

MapStruct 구현체 예시 코드

@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-02-18T21:59:43+0900",
comments = "version: 1.5.5.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-8.5.jar, environment: Java 17.0.10 (Eclipse Adoptium)"
)
@Component
public class CouponDtoMapperImpl implements CouponDtoMapper {

@Override
public CouponDto toCouponDto(Coupon coupon) {
    if ( coupon == null ) {
        return null;
    }

    CouponDto couponDto = new CouponDto();

    couponDto.setRoomId( couponRoomId( coupon ) );
    couponDto.setId( coupon.getId() );
    couponDto.setName( coupon.getName() );
    couponDto.setMinimumPrice( coupon.getMinimumPrice() );
    couponDto.setDiscountPrice( coupon.getDiscountPrice() );
    couponDto.setDiscountRate( coupon.getDiscountRate() );
    couponDto.setDiscountLimit( coupon.getDiscountLimit() );
    couponDto.setDescription( coupon.getDescription() );
    couponDto.setRegion( coupon.getRegion() );
    couponDto.setRoomType( coupon.getRoomType() );
    couponDto.setIsValid( coupon.getIsValid() );
    couponDto.setIsRegistered( coupon.getIsRegistered() );
    couponDto.setValidityStartDate( coupon.getValidityStartDate() );
    couponDto.setValidityEndDate( coupon.getValidityEndDate() );

    return couponDto;
}

private Long couponRoomId(Coupon coupon) {
    if ( coupon == null ) {
        return null;
    }
    Room room = coupon.getRoom();
    if ( room == null ) {
        return null;
    }
    Long id = room.getId();
    if ( id == null ) {
        return null;
    }
    return id;
}
}

결과적으로 MapStruct를 사용함으로써 코드 생산성을 크게 향상시키는 결과를 얻게 된다.

좀 더 자세한 내용은 노션을 참고바랍니다.
( 링크 : https://www.notion.so/bc8f4c65b042459bb22736d25da181dc )


Architecture


ERD



⚙ 기술 스택

  • Backend: Java 17 Spring Boot 3.2.1 Spring Security Spring Data JPA QueryDsl
  • Frontend: React TypeScript
  • DB: PostgreSQL Redis
  • Server: AWS EC2
  • Tools: Intellij IDEA
  • Collaborations: Github Projects

협업 전략

  • Squash Merge Pull Request만을 사용한다.
  • GitHub flow 전략을 사용한다.
  • 한 사람 이상의 Accepted Review가 있어야만 PR을 머지할 수 있다.

회의록

링크 : https://www.notion.so/0956e741633e4862bd355ac2eadbd2ad


백엔드 DEMO

Important

현재 프로젝트는 백엔드 api만을 작성한 상태이며, 프론트 코드는 완성되지 않은 상태입니다. 하단에 보이는 데모 스크린샷은

https://github.com/Yanolja-MiniProject-10/Yanolja-clone-fe

위 프로젝트의 프론트 코드를 그대로 가져와서 로컬에 돌려서 api endpoint를 맞춘 후 동작을 확인한 내용입니다.

저희 팀원 5명은 프론트적인 역량이 없다는 것을 명시합니다!

로그인



숙소 조회



예약



리뷰 조회




👊 팀 Battlecrusiers

김대현 김민우 염금성 이비안 임현우
@vimkim @yukicow @Venus1234567 @gumgu @hyunwoo0318
건강한 신체에 건강한 코드가 깃든다. 모두가 행복한 체덕지 프로그래밍 화이팅! 꾸준하게 공부해서 끝없이 성장하자. 많은 문제에 부딫혀가며 꾸준히 성장하는 개발자 되기 믿을 수 있는 동료 개발자가 되겠습니다! 화이팅!! 항상 꼼꼼하고 행복하게 코딩하자~

About

야놀자의 클론 프로젝트입니다.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages