프로젝트/프로젝트 회고

[팀 프로젝트-배달어플] 기능 구현

코딩로봇 2025. 4. 29. 15:21

이번 프로젝트에서는 메뉴 기능을 담당하였다.

📋 API 명세

 

📌 주요 기능

  • 메뉴 생성
  • 메뉴 수정
  • 메뉴 삭제
  • 카테고리 순 조회 및 커서 기반 조회

 

📁 패키지 구조

 

Rest API 구조에 맞춰 controller,repository,service,dto 로 분리하였고 그안에서 Admin API 와 User API 를 따로 분리 하였다.

✅ DTO 

  • MenuSaveRequestDto : 저장 요청
  • MenuUpdateRequestDto: 수정 요청

 

🧱 Entity 설계

 

Menu.java

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.ORDINAL)
    @Column(nullable = false)
    private Category category;

    @Column(nullable = false)
    private String name;

    @Column
    private String content;

    @Column(nullable = false)
    private BigDecimal price;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private MenuStatus status;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "storeId")
    private Store store;

    @Column(columnDefinition = "TINYINT(1)")
    private boolean isDeleted = Status.EXIST.getValue();

 

  • Category , MenuStatus : 주기적으로 바뀌는 변수가 아닌 특정한 변수에 의존할 수 있도록 enum 으로 구현
  • isDeleted : soft Deleted 를 위한 TINYINT 타입 저장
  • Store : 일대다의 관계로 스토어에 많은 메뉴가 존재

 

 

✅ 카테고리 순 조회 및 커서 기반 조회

기본적은 CRUD 는 많이 구현해봤기 때문에 이번에 새로 구현해본 기능을 기록해볼려고 한다.

해당가게의 메뉴 목록을 카테고리 별로 나열해야하는데 앱을 보면 오프셋 페이징 보다는 스크롤을 내리며 메뉴를 선택한다.

사용자 UI 의 편리를 위한 방법이라고 생각한다.

 

그래서 이번에 무한스크롤페이징을 구현해보았다.

 

참고자료

https://computerreport.tistory.com/125

 

[Spring]오프셋 페이징보다 커서 페이징을 써야하는 이유

💡 커서 기반 페이징이 가장 효율적인 방법이며, 가능한 항상 사용되어야 한다-페이스북 개발자 이번 글에서는 커서 페이징이 무엇이고, 왜 요즘처럼 실시간 데이터가 중요한 시대에 커서 페이

computerreport.tistory.com

 

JPQL 을 활용하한 무한스크롤 방식이다.

 

@Query("""
            SELECT m FROM Menu m
            WHERE m.store.id = :storeId
            AND (:categoryCursor IS NULL
                   OR m.category > :categoryCursor
                   OR (m.category = :categoryCursor AND m.id > :lastId)
                  )
            AND m.deletedAt IS NULL
            ORDER BY m.category ASC, m.id ASC
 """)

 

  • 동작 방식:
    • storeId: 조회 대상 가게.
    • categoryCursor: 마지막으로 조회한 카테고리.
    • lastId: 동일 카테고리 내 마지막 메뉴 ID.
    • deletedAt IS NULL: 삭제된 메뉴 제외.
    • ORDER BY m.category ASC, m.id ASC: 카테고리 오름차순, 동일 카테고리 내 ID 오름차순.
    • Pageable: 페이지 크기 지정.
  • 장점:
    • 성능: 인덱스를 활용해 대량 데이터 처리 효율적.
    • 일관성: 실시간 데이터 변경 시 데이터 누락/중복 없음.
    • UX: 무한 스크롤로 사용자 친화적.

Category 별 순서를 명시하기 위해 Entity에 EnumType.ORDINAL 을 추가하여 정렬 순서를 해결 하였다.

 

이번 프로젝트에서는 5분 기록보드를 적극적으로 활용하면서 회고를 쓰기에도 도움이 됐고 전체적인 프로젝트 흐름을 볼 수 있어서 좋았다.

 

📎 마무리

이번 프로젝트를 통해 JPQL로 복잡한 쿼리를 작성하며 쿼리 최적화와 데이터 일관성의 가치를 배웠다.하지만 JPQL의 문자열 기반 쿼리는 가독성과 유지보수성 면에서 아쉬움이 남았다.

 

앞으로의 계획은 QueryDSL을 공부해 더 효율적이고 가독성 높은 쿼리 코드를 작성하는 것이다.

QueryDSL은 타입 안전한 쿼리 작성으로 컴파일 시점에 오류를 잡아주고, 메서드 체이닝으로 직관적인 코드를 만들 수 있어 JPQL의 한계를 보완할 수 있을 거라 생각한다.

또한, 대량 데이터 테스트로 커서 페이징의 성능을 검증하고, @DynamicUpdate로 엔티티 업데이트를 최적화하며, OpenAPI(Swagger)로 API 문서화를 자동화할 계획이다.

다음 프로젝트에서는 QueryDSL을 적극 활용해 깔끔하고 유지보수 가능한 코드를 목표로 하고싶다.