Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

반경 내 주차장 조회 기능 구현 #13

Merged
merged 44 commits into from
Apr 3, 2024
Merged

Conversation

youngh0
Copy link
Contributor

@youngh0 youngh0 commented Feb 6, 2024

👍관련 이슈

🤔세부 내용

GET /parkings?정렬조건
API 구현했습니다.

  • repository 의 JPQL 부분을 보면 MYSQL 에서 지원해주는 ST_CONTAINS, ST_BUFFER, ST_DISTANCE_SPHERE 를 사용했습니다.
    • ST_BUFFER(POINT, radius): POINT 중점으로 부터 반지름이 radius 인 원을 생성해줍니다.
    • ST_CONTAINS(원, POINT): POINT 가 원 안에 있는지 판단합니다. 여기서 원은 ST_BUFFER 로 생성된 원을 사용하도록 했습니다.
    • ST_DISTANCE_SPHERE(POINT, POINT): POINT 간의 거리 차이를 계산합니다. 이 함수는 가까운 순으로 정렬조건을 줬을 때 사용합니다.

🫵리뷰 중점사항

  • 필터링 하는 부분을 별도의 ParkingDomainService 로 분리했습니다. (네이밍은 생각이 안나서 일단 ParkingDomainService 로 했어요..)

    • 필터링 하는 조건이 추가되면 DB 와 통신하는 service 를 건드리지 않고 domainService 만 변경해서 요구사항을 충족할 수 있을것같아 분리해봤습니다.
  • 필터링 조건 중 cardEnabled는 전에 얘기한대로 애매한 부분이 있어서 제외했습니다.

  • 필터링 조건 중 유.무료 부분은 요금계산 로직이 완료되면 그걸 토대로 필터링 조건 추가하면 될 것 같습니다.

  • 응답 값에서 예상 요금, 도보 시간, 즐겨찾기는 아직 구현되지 않은 부분이라 null 을 반환하도록 했습니다.

더미 데이터 지도 사진

스크린샷 2024-02-06 오후 5 59 51

거리순 정렬 X

스크린샷 2024-02-06 오후 5 55 24

거리순 정렬 O

스크린샷 2024-02-06 오후 5 56 10

@youngh0 youngh0 added the 📝feature 기능 추가 label Feb 6, 2024
@youngh0 youngh0 self-assigned this Feb 6, 2024
Copy link
Contributor

@jundonghyuk jundonghyuk left a comment

Choose a reason for hiding this comment

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

각각 커멘트 남겼씁니다 _

Comment on lines 18 to 19
public static final String DISTANCE_ORDER_CONDITION = "가까운 순";
private final ParkingRepository parkingRepository;
Copy link
Contributor

Choose a reason for hiding this comment

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

라인 분리 어떨까요?

Comment on lines 21 to 22

public ParkingLotsResponse findParkingLots(ParkingQueryRequest parkingQueryRequest) {
Copy link
Contributor

Choose a reason for hiding this comment

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

트랜잭셔널이 없어도될까요 ?

Copy link
Contributor

@This2sho This2sho left a comment

Choose a reason for hiding this comment

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

굳이요 ~ 👍

import java.util.List;
import lombok.Getter;

@Getter
Copy link
Contributor

Choose a reason for hiding this comment

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

@NoArgsConstructor(access = AccessLevel.PRIVATE)

import org.springframework.web.method.support.ModelAndViewContainer;

@Component
public class ParkingQueryArgumentResolver implements HandlerMethodArgumentResolver {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

youngh0 added 3 commits March 13, 2024 01:16
# Conflicts:
#	src/main/java/com/example/parking/domain/parking/ParkingRepository.java
#	src/main/resources/application.yml
#	src/test/java/com/example/parking/auth/AuthServiceTest.java
#	src/test/resources/application.yml
Copy link

github-actions bot commented Mar 13, 2024

Test Results

127 tests   127 ✅  2s ⏱️
 25 suites    0 💤
 25 files      0 ❌

Results for commit 4fe6e61.

♻️ This comment has been updated with latest results.

@youngh0
Copy link
Contributor Author

youngh0 commented Mar 14, 2024

  • 사용자 조회조건 중 payType 이 주차장의 payType 에 포함되는지 확인하는 필터링 기능 구현
  • 예상 요금 계산 로직 호출하여 요금 계산 기능 추가
  • 주차장과 목적지 간 도보 예상 시간 계산 기능 구현
    • 주차장과 목적지 사이 거리를 km 로 구한 뒤, 이를 도보 평균 속도 5km 로 나누어 구했습니다.
    • 소수점의 경우 넉넉하게 올림으로 처리했습니다.

@youngh0 youngh0 requested a review from This2sho March 14, 2024 04:33
@youngh0
Copy link
Contributor Author

youngh0 commented Mar 22, 2024

커밋 목록

  • query parameter 중 하나라도 Null 이면 기본 값을 가진 객체 반환하도록 했습니다.

    • null 체크하고, 배열을 바꾸는 코드는 절차지향적으로 짤 수 밖에 없더라구요..
  • ParkingSearchConditionRequest 의 기본 값을 설정할 때 enum(ParkingType, OperationType, payType) 의 원소들을 List 형태로 어떻게 들고올지 여러분 의견이 궁금합니다.

    현재는 기본값을 설정할 때 각 enum(ParkingType, OperationType, payType) 에 getAllValues() 메서드를 정의해서 모든 원소를 List 으로 반환하도록 하여 사용했습니다. 그래서 ParkingSearchConditionRequest 의 base() 메서드를 보시면 이를 활용하고 있는데요. 이렇게 사용한 이유는 enum 에 원소가 하나 추가될 경우 enum 에만 원소를 추가하면 ParkingSearchConditionRequest 를 손댈 필요가 없기 때문입니다.

    그런데, 저 enum 의 원소가 바뀔일은 거의 없을 거 같아서 ParkingSearchConditionRequest 와 enum 의 의존성을 아예 끊어서 ParkingSearchConditionRequest 에서 바로 List.of("노상", "노외") 이렇게 기본값을 설정하는 것도 좋아보이더라구요.

    뭐가 더 괜찮아 보이시나여??

Copy link
Contributor

@This2sho This2sho left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 영호씨 🙇‍♂️
리뷰 남겼어여 확인부탁드려요

@@ -16,6 +18,13 @@ public enum OperationType implements SearchConditionAvailable {
this.description = description;
}

public static List<String> getAllValues() {
Copy link
Contributor

Choose a reason for hiding this comment

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

중복 메서드 SearchConditionAvailable에 아래 메서드 만들어서 쓰는게 나을거 같아여

static <E extends SearchConditionAvailable> List<String> getAllValues(E... values) {
        return Arrays.stream(values)
                .filter(e -> e != e.getDefault())
                .map(E::getDescription)
                .toList();
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

어떤식으로 가능할지를 몰라서 못했는데 감사합니다~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

적용했습니다~

@@ -26,6 +26,25 @@ public static List<String> getAllValues() {
.toList();
}

public static List<PayType> collectMatch(List<String> descriptions) {
Copy link
Contributor

Choose a reason for hiding this comment

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

이것도 SearchConditionAvailable 모으는게 나을거 같아여

static <E extends SearchConditionAvailable> List<E> collectMatch(List<String> descriptions, E... values) {
        return descriptions.stream()
                .map(d -> (E) find(d))
                .toList();
    }

Copy link
Contributor

Choose a reason for hiding this comment

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

이게 싫으면 SearchConditionAvailable 인터페이스에는 필요한 메서드만 두고 SearchConditionMapper에 Enum <-> String 간 변환 로직 둬도 괜찮을 거 같아여.
아래는 생각해본 예시임다,

public interface SearchConditionAvailable {

    String getDescription();

    <E extends SearchConditionAvailable> E getDefault();

    <E extends SearchConditionAvailable> E[] values();
}
public class SearchConditionMapper {

    public static  <E extends SearchConditionAvailable> List<E> toEnums(E sc, List<String> descriptions) {
        return descriptions.stream()
                .map(description -> toEnum(sc, description))
                .toList();
    }

    public static <E extends SearchConditionAvailable> E toEnum(E sc, String description) {
        return (E) Arrays.stream(sc.values())
                .filter(e -> description.startsWith(sc.getDescription()))
                .findAny()
                .orElse(sc.getDefault());
    }

    public static <E extends SearchConditionAvailable> List<String> toStrings(E sc) {
        return Arrays.stream(sc.values())
                .filter(e -> e != sc.getDefault())
                .map(e -> ((SearchConditionAvailable) e).getDescription())
                .toList();
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

이것도 어떤식으로 코드를 짜야될지 몰라서.. 감사합니다 ㅎ

Copy link
Contributor Author

Choose a reason for hiding this comment

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

모던 자바의 신이시네여; 코드 그대로 썼습니다.. 감사해요..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

적용했습니다~

@@ -22,19 +22,17 @@ public class ParkingDomainService {

Copy link
Contributor

Choose a reason for hiding this comment

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

ParkingDomainService 역할이 짬뽕된거 같아여
이름은 Domain, 어노테이션은 Component라서 도메인 서비스 같아 보이지만
패키지는 application에 있고 반환도 response 반환하고 있어서 어플리케이션 서비스 같이도 보여여
하나로 맞춰주셨으면 합니다

Copy link
Contributor Author

Choose a reason for hiding this comment

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

조회 조건에 맞게 필터링하는 절차지향적인 로직이라 별도의 Service 를 만들어서 ParkingService 를 조금 더 간결하게 해보려고 했는데 ParkingService 내부 private 메서드로 수정하는게 더 나을까요??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

일단 제가 의도한건 Application Service 가 더 맞는거같아서 네이밍 수정할게요~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

List 을 응답 dto 로 만들기 위해서 예상 요금, 도보 시간 계산, 즐겨 찾기 유무 확인해서 만드는 로직이 절차지향적이라 이 메서드를 ParkingService 의 private 메서드로 옮길지 ParkingApplicationService 에 둘지 계속 고민되네여,, 뭐가 더 괜찮아보이시나여?

Copy link
Contributor

Choose a reason for hiding this comment

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

새로 서비스를 나눠서 구현한건 좋은 거 같습니다.
그렇지만, 조회 조건에 맞게 필터링하는 건 ParkingFilterService 같은 도메인 서비스를 두고 ParkingResponse로 변환해주는 코드는 Application Service 내부에 private 메소드로 둬도 괜찮을 거 같아여


@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParkingMemberId {
Copy link
Contributor

Choose a reason for hiding this comment

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

MemberAuth 랑 역할이 동일한거 같은데
무슨 차이인가여?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

기존의 MemberAuth 는 잘못된 인증 정보면 예외를 던지는데 주차장 조회 기능에선 memberId 가 없으면 즐겨찾기 값을 전부 다 false 로 반환해야됩니다.

그래서 기존 MemberAuth의 잘못된 인증정보 시 예외던지는 코드를 주차장 조회 기능 때문에 null 을 반환하게 하면 MemberAuth 를 사용하고 있는 다른 로직에서 반환된 값의 null 체크를 하고 예외처리를 해야되기 때문에 예외 말고 null 을 반환하도록 새로 만들었습니다.

Copy link
Contributor

@This2sho This2sho Mar 27, 2024

Choose a reason for hiding this comment

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

같은 목적의 어노테이션을 두개로 늘리기 보다는
아래처럼 필드를 추가해주고

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MemberAuth {
    boolean required() default true;
}

아규먼트 리졸버에서 required 값을 판단해서 아래처럼 내려주면 될 거 같습니다

@Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        MemberAuth memberAuth = memberAuthFrom(parameter);
        if (memberAuth.required()) {
            String sessionId = webRequest.getHeader(JSESSIONID);
            MemberSession session = authService.findSession(sessionId);
            return session.getMemberId();
        }
        return null;
    }
    
    private MemberAuth memberAuthFrom(MethodParameter parameter) {
        return parameter.getParameterAnnotation(MemberAuth.class);
    }

그러고 회원 정보가 필수가 아닌 컨트롤러에서 적용해주면 될 거 같습니다

@GetMapping("/parkings")
    public ResponseEntity<ParkingLotsResponse> find(
            @ParkingQuery ParkingQueryRequest parkingQueryRequest,
            @ParkingSearchCondition ParkingSearchConditionRequest parkingSearchConditionRequest,
            @MemberAuth(required = false) Long memberId
    ) {
        ParkingLotsResponse parkingLots = parkingService.findParkingLots(parkingQueryRequest,
                parkingSearchConditionRequest, memberId, LocalDateTime.now());
        return ResponseEntity.ok(parkingLots);
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오 이건 생각 못했는데 아이디어 감사합니다 ㅎ

private static final String RECOMMEND_ORDER_CONDITION = "추천 순";

private final List<String> operationTypes;
private final List<String> parkingTypes;
private final List<String> feeTypes;
private final String feeTypes;
Copy link
Contributor

Choose a reason for hiding this comment

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

유료, 무료 주차장 둘 다 보는 방법은 없는거야?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

요건 제가 놓쳤네염. 감사합니다~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

적용했습니다~

return false;
}

private List<String> toCollection(String[] parameters) {
Copy link
Contributor

Choose a reason for hiding this comment

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

toList()는 어때요 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

고민했었는데 toList로 바꿀게여 ㅋㅋㅋㅋ


List<ParkingResponse> parkingResponses = new ArrayList<>();
for (Parking parking : parkingLots) {
Fee fee = parkingFeeCalculator.calculateParkingFee(parking, now, now.plusHours(hours));
Copy link
Contributor

Choose a reason for hiding this comment

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

저의 사랑스러운 아이가 들어가 있군요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

야무지더라구요


@RequiredArgsConstructor
@Component
public class ParkingDomainService {
Copy link
Contributor

Choose a reason for hiding this comment

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

먼가 따로 분리해야했을까? 라는 생각이 들긴합니다 ! 이유가 있을까요 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

호이 리뷰에 커멘트 달았슴다~

Comment on lines +115 to +127
double deltaLatitude = Math.abs(parkingLatitude - destination.getLatitude()) * toRadian;
double deltaLongitude = Math.abs(parkingLongitude - destination.getLongitude()) * toRadian;

double sinDeltaLat = Math.sin(deltaLatitude / 2);
double sinDeltaLng = Math.sin(deltaLongitude / 2);
double squareRoot = Math.sqrt(
sinDeltaLat * sinDeltaLat +
Math.cos(parkingLatitude * toRadian) * Math.cos(destination.getLatitude() * toRadian)
* sinDeltaLng
* sinDeltaLng);

return 2 * radius * Math.asin(squareRoot);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

계산로직이군여

Copy link
Contributor

Choose a reason for hiding this comment

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

그러게여 ParkingFeeCalculator처럼 목적지까지 거리나 시간 측정하는거 만들어서 빼도 될거 같아여

Copy link
Contributor Author

@youngh0 youngh0 Mar 27, 2024

Choose a reason for hiding this comment

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

출발 위치 정보를 받고 주차장까지의 도보 예상 시간을 구하는 책임이라 Parking 에 있는게 자연스럽다고 생각했는데 별도의 Calculator 가 낫다고 생각하시나여??

Copy link
Contributor

@This2sho This2sho left a comment

Choose a reason for hiding this comment

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

긴 과정 고생하셨습니다 영호씨 👍

@This2sho This2sho added the 🎅🏼deploy 해당 PR 머지시 배포 label Apr 3, 2024
@This2sho This2sho merged commit 3741d54 into main Apr 3, 2024
3 checks passed
@This2sho This2sho deleted the feat/10-find-parkingLots branch April 3, 2024 05:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🎅🏼deploy 해당 PR 머지시 배포 📝feature 기능 추가
Projects
None yet
Development

Successfully merging this pull request may close these issues.

위도 경도 기반 반경 주차장 조회
3 participants