Skip to content
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@
## 캐치테이블 사이드 프로젝트


API명| url |
--|--|
상점 목록조회 | https://github.com/f-lab-edu/ys-catchtable/issues/6|
상점 단건 조회 | https://github.com/f-lab-edu/ys-catchtable/issues/4


## 간단한 도메인 구성도
![img.png](etc/도메인구성도.png)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yscp.catchtable.application.amenity;

import com.yscp.catchtable.domain.amenity.repository.AmenityRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class AmenityReadService {
private final AmenityRepository amenityRepository;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.yscp.catchtable.application.amenity;

import com.yscp.catchtable.application.amenity.dto.StoreAmenityDto;
import com.yscp.catchtable.domain.amenity.entity.StoreAmenity;
import com.yscp.catchtable.domain.amenity.repository.StoreAmenityRepository;
import com.yscp.catchtable.exception.BadRequestError;
import com.yscp.catchtable.exception.CatchTableException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class StoreAmenityReadService {
private final StoreAmenityRepository storeAmenityRepository;

public List<StoreAmenity> findByStoreIdx(Long idx) {
if (idx == null) {
throw new CatchTableException(BadRequestError.NULL_EXCEPTION, "상품이 존재하지 않습니다.");
}

return storeAmenityRepository.findByStoreIdx(idx);
}

public List<StoreAmenityDto> findAmenityDtoByStoreIdx(Long idx) {
return findByStoreIdx(idx)
.stream()
.map(StoreAmenityDto::from)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.yscp.catchtable.application.amenity.dto;

import com.yscp.catchtable.domain.amenity.entity.StoreAmenity;

public record StoreAmenityDto(
String code,
String memo,
String image
) {

public static StoreAmenityDto from(StoreAmenity amenity) {
return new StoreAmenityDto(amenity.getAmenity().getCode(),
amenity.getMemo(),
amenity.getAmenity().getImageUrl());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yscp.catchtable.application.location.dto;

import org.locationtech.jts.geom.Point;

public record LocationDto(
String addressCode,
String name,
double xCoordinate,
double yCoordinate
) {
public LocationDto(String addressCode, String name, Point point) {
this(addressCode, name, point.getX(), point.getY());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

포인트의 맥락에서는 x, y가 맞는 것 같기는한데요

location의 xCoor~와 다르게 한 이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@f-lab-k
라이브러리에서 제공해주는 메소드를 쓰고있는 부분인데 location에서 동일한 형태로 맞출 때
메소드명이 구체적이지 않은 것 같아.. 우선 임의로 수정했습니다!

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.yscp.catchtable.application.menu;

import com.yscp.catchtable.application.menu.dto.MenuDto;
import com.yscp.catchtable.domain.menu.entity.Menu;
import com.yscp.catchtable.domain.menu.entity.sort.MenuSort;
import com.yscp.catchtable.domain.menu.repository.MenuRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class MenuReadService {
private final MenuRepository menuRepository;

public List<Menu> findByStoreIdx(Long idx, MenuSort menuSort) {
Objects.requireNonNull(idx, "Store idx must not be null");
Copy link

@f-lab-k f-lab-k Apr 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 현재 이렇게 예외를 발생시키면 client한테 의도한 대로 예외가 잘 도달하나요?
  2. 요거는 그냥 의견인데 sort가 함수명에 들어갈 필요가 있을까 싶어요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 음.. NPE에 대하여 클라이언트에게 의도된 메세지를 전달해주지 못하는 이슈가 있을 것 같네요..
    보여주고 하는 메세지를 반환할 수 있도록 NPE관련 에러 핸들러를 구현했습니다.

추가적으로 도메인에 대한 NPE 관련 에러는 미리 만들어두고 사용하는 편일까요?
유효성 검증에 대한 에러에 대하여 반복적으로 사용하는 에러일 경우,도메인별로 에러를 정의하는지 궁금합니다!
(도메인별 NPE, 빈값 체크등, ㄷ유효성관련검사)

  1. 말씀해주신 오버로딩하여 동일한 역할을 하는 메소드임을 나타내는게 더 좋을 것 같네요!!
    조회하는 역할만 하는 메소드를 하나 지어
    매개변수만 다르게 한다면 해당 도메인을 찾는 메소드에 대하여 메소드는 하나임을 인지시킬수 있고,
    다양한 매개변수를 통해 여러가지 방식을 지원한다고 나타낼 수 있을 것 같습니다!

return menuRepository.findByStoreIdx(idx, menuSort.getSort());
}

public List<MenuDto> findMenuDtoByStoreIdx(Long idx, MenuSort menuSort) {

return findByStoreIdx(idx, menuSort)
.stream()
.map(MenuDto::from)
.toList();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.yscp.catchtable.application.menu.dto;

import com.yscp.catchtable.domain.menu.entity.Menu;

public record MenuDto(
Long idx,
String name,
String menuImageUrl
) {
public static MenuDto from(Menu menu) {
return new MenuDto(menu.getIdx(), menu.getName(), menu.getImageUrl());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.yscp.catchtable.application.reserve;

import com.yscp.catchtable.application.reserve.dto.ReserveDto;
import com.yscp.catchtable.application.reserve.dto.StoreReserveDto;
import com.yscp.catchtable.domain.reserve.repository.ReserveRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,9 +18,16 @@
public class ReserveService {
private final ReserveRepository repository;

public Map<Long, List<StoreReserveDto>> reserveDtoMapByStoreList(List<Long> idxes, LocalDate maxDate) {
List<StoreReserveDto> storeReserveDtos = repository.storeReserveDtoBeforeMaxDate(idxes, maxDate);
public Map<Long, List<StoreReserveDto>> findReserveDtoMapByStores(List<Long> idxes, LocalDate maxDate) {
List<StoreReserveDto> storeReserveDtos = repository.findStoreReserveDtoListBeforeMaxDate(idxes, maxDate);
return storeReserveDtos.stream()
.collect(Collectors.groupingBy(StoreReserveDto::getStoreIdx, Collectors.toList()));
}

public List<ReserveDto> findReserveDtos(Long idx, LocalDate localDate) {
return repository.getStoreReserveDtoBeforeMaxDate(idx, localDate)
.stream()
.map(ReserveDto::from)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.yscp.catchtable.application.store;

import com.yscp.catchtable.application.store.dto.StoreBusinessDto;
import com.yscp.catchtable.domain.store.entity.value.DayType;
import com.yscp.catchtable.domain.store.repository.StoreBusinessHourRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
Expand All @@ -17,10 +19,15 @@
public class StoreBusinessHourService {
private final StoreBusinessHourRepository storeBusinessHourRepository;

public Map<Long, StoreBusinessDto> findBusinessMap(List<Long> idxes) {
return storeBusinessHourRepository.findByStoreIdxIn(idxes)
public Map<Long, StoreBusinessDto> findBusinessMap(List<Long> idxes, LocalDate localDate) {
DayType day = DayType.from(localDate);
return storeBusinessHourRepository.findByStoreIdxIn(idxes, day)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocalDate.now()가 있어서 테스트가 어려워 질 수 있겠는데용?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제일 외부 층인 컨트롤러에서 시간을 가져올 수 있도록 기능을 수정하겠습니다!

.stream()
.map(StoreBusinessDto::from)
.collect(Collectors.toMap(StoreBusinessDto::getStoreIdx, Function.identity()));
}

public List<StoreBusinessDto> findByStoreIdx(Long storeIdx) {
return storeBusinessHourRepository.findByStoreIdx(storeIdx);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.yscp.catchtable.domain.store.entity.Store;
import com.yscp.catchtable.domain.store.repository.StoreRepository;
import com.yscp.catchtable.domain.store.repository.StoreSearchRepository;
import com.yscp.catchtable.exception.CatchTableException;
import com.yscp.catchtable.exception.NotFoundError;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -20,4 +22,9 @@ public class StoreReadService {
public List<Store> findBySearchDto(StoreSearchDto searchDto) {
return storeSearchRepository.findBySearchDto(searchDto);
}

public Store findByIdx(Long idx) {
return storeRepository.findById(idx)
.orElseThrow(() -> new CatchTableException(NotFoundError.NOT_FOUND_STORE));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.yscp.catchtable.application.store;

import com.yscp.catchtable.application.amenity.StoreAmenityReadService;
import com.yscp.catchtable.application.amenity.dto.StoreAmenityDto;
import com.yscp.catchtable.application.location.dto.LocationDto;
import com.yscp.catchtable.application.menu.MenuReadService;
import com.yscp.catchtable.application.menu.dto.MenuDto;
import com.yscp.catchtable.application.reserve.ReserveService;
import com.yscp.catchtable.application.reserve.dto.ReserveDto;
import com.yscp.catchtable.application.reserve.dto.StoreReserveDto;
import com.yscp.catchtable.application.store.dto.StoreBusinessDto;
import com.yscp.catchtable.application.store.dto.StoreDetailDto;
import com.yscp.catchtable.application.store.dto.StoreDtos;
import com.yscp.catchtable.domain.menu.entity.sort.MenuSort;
import com.yscp.catchtable.domain.store.entity.Store;
import com.yscp.catchtable.domain.store.entity.Stores;
import com.yscp.catchtable.presentation.store.dto.request.StoreSearchRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class StoreReader {
private final StoreReadService storeReadService;
private final StoreBusinessHourService storeBusinessHourService;
private final ReserveService reserveService;
private final MenuReadService menuReadservice;
private final StoreAmenityReadService amenityReadService;

public StoreDtos getStoreListDto(StoreSearchRequestDto storeSearchRequestDto, LocalDate date) {
Stores stores = Stores.from(storeReadService.findBySearchDto(storeSearchRequestDto.toSearchDto()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어디는 list를 사용하시고 아래에는 es라는 복수를 사용하시네요
일관성이 있으면 더 좋을 것 같아요


List<Long> idxes = stores.idxes();

Map<Long, List<StoreReserveDto>> reserveDtoMap = reserveService.findReserveDtoMapByStores(idxes,
date.plusDays(14));

Map<Long, StoreBusinessDto> businessHourMap = storeBusinessHourService.findBusinessMap(idxes, date);
return StoreDtos.of(stores,
reserveDtoMap,
businessHourMap);
}

public StoreDetailDto getStoreDetailDto(Long idx, LocalDate date) {
Store store = storeReadService.findByIdx(idx);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기는 get or find을 붙이지 않는 이유는 무엇인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dto라는 네이밍으로 유추할 수 있다고 생각했는데, 동사로 시작해야 하는 규칙을 깬 것 같아 수정하겠습니다!

List<ReserveDto> reserveDtoList = reserveService.findReserveDtos(store.getIdx(), date.plusDays(14));
// 메뉴 조회
List<MenuDto> menuList = menuReadservice.findMenuDtoByStoreIdx(store.getIdx(), MenuSort.ORD_DESC);
// 편의시설 조회
List<StoreAmenityDto> storeAmenities = amenityReadService.findAmenityDtoByStoreIdx(store.getIdx());
// 영업시간 조회
List<StoreBusinessDto> businessDto = storeBusinessHourService.findByStoreIdx(store.getIdx());
// 위치정보 조회
LocationDto locationDto = new LocationDto(store.getAddressCode(),
store.getLocationName(),
store.getPoint());

return new StoreDetailDto(
store.getIdx(),
store.getCategory().getName(),
store.getTel(),
store.getIntroduce(),
store.getFeeInformation(),
locationDto,
reserveDtoList,
menuList,
storeAmenities,
businessDto
);
}
}
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
package com.yscp.catchtable.application.store.dto;

import com.yscp.catchtable.domain.store.entity.StoreBusinessHour;
import com.yscp.catchtable.domain.store.entity.value.DayType;
import lombok.Builder;
import lombok.Getter;

@Getter
public class StoreBusinessDto {
private final Long idx;
private final Long storeIdx;
private final Integer lunchStartTime;
private final Integer lunchEndTime;
private final Integer dinerStartTime;
private final Integer dinerEndTime;
private final DayType dayType;
private final String lunchStartTime;
private final String lunchEndTime;
private final String dinerStartTime;
private final String dinerEndTime;

public static StoreBusinessDto from(StoreBusinessHour storeBusinessHour) {
return new StoreBusinessDto(storeBusinessHour.getIdx(),
storeBusinessHour.getStore().getIdx(),
storeBusinessHour.getDay(),
storeBusinessHour.getLunchStartTime(),
storeBusinessHour.getLunchEndTime(),
storeBusinessHour.getDinerStartTime(),
storeBusinessHour.getDinerEndTime());
}

public StoreBusinessDto(Long idx, Long storeIdx, Integer lunchStartTime, Integer lunchEndTime, Integer dinerStartTime, Integer dinerEndTime) {
@Builder
public StoreBusinessDto(Long idx, Long storeIdx, DayType dayType, String lunchStartTime, String lunchEndTime, String dinerStartTime, String dinerEndTime) {
this.idx = idx;
this.storeIdx = storeIdx;
this.dayType = dayType;
this.lunchStartTime = lunchStartTime;
this.lunchEndTime = lunchEndTime;
this.dinerStartTime = dinerStartTime;
this.dinerEndTime = dinerEndTime;
}

public Integer startTime() {
public String startTime() {
return lunchStartTime == null
? dinerStartTime
: lunchStartTime;
}

public Integer endTime() {
public String endTime() {
return dinerEndTime == null
? lunchEndTime
: dinerEndTime;
Expand Down
Loading