지하철 노선도 미션

스프링 과정 실습을 위한 지하철 노선도 애플리케이션


🚀 Getting Started

Usage

application 구동

./gradlew bootRun

✏️ Code Review Process

텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정


🐞 Bug Report

버그를 발견한다면, Issues 에 등록해주세요 :)


📝 License

This project is MIT licensed.

기능 요구사항

지하철역

  • 지하철 역을 등록한다.
    • [예외] 같은 역의 이름으로 중복될 수 없다.
    • [예외] 역 이름은 10글자를 넘길 수 없다.
    • [예외] 역 이름은 2글자 이상이어야 한다.
    • [예외] 역 이름은 한글과 숫자의 조합이어야 한다.
  • 지하철 역을 조회한다.
  • 지하철 역을 삭제한다.
    • [예외] 삭제할 역이 있어야 한다.
    • [예외] 해당 역과 연결된 구간이 있을 경우 삭제할 수 없다

지하철 노선

  • 지하철 노선을 등록한다.
    • [예외] 같은 노선의 이름으로 중복될 수 없다.
    • [예외] 색깔은 중복 될 수 없다.
    • [예외] 노선 이름은 10글자를 넘길 수 없다.
    • [예외] 노선 이름은 3글자 이상이어야 한다.
    • [예외] 노선 이름은 한글과 숫자의 조합이어야 한다.
    • [예외] 이름과 색깔이 있어야 한다.
    • 지하철 구간을 등록한다
      • [예외] 상행 지점, 하행 지점, 거리가 있어야 한다.
  • 지하철 노선 조회
    • [예외] 조회할 노선이 있어야 한다.
    • 노선이 가진 구간들을 정렬해서 반환한다.
  • 지하철 노선 목록을 조회한다.
    • 노선들이 가진 구간들을 정렬해서 반환한다.
  • 지하철 노선 수정
    • [예외] 같은 노선의 이름으로 중복될 수 없다.
    • [예외] 색깔은 중복 될 수 없다.
    • [예외] 노선 이름은 10글자를 넘길 수 없다.
    • [예외] 노선 이름은 3글자 이상이어야 한다.
    • [예외] 노선 이름은 한글이어야 한다.
    • [예외] 이름과 색깔이 있어야 한다.
  • 지하철 노선 삭제
    • 해당 노선과 연결된 구간도 삭제한다

지하철 구간

  • 구간을 등록한다.
    • [예외] 존재하는 노선이어야 한다.
    • [예외] 추가하려는 구간이 상행역이나 하행역이 있어야 한다.
    • [예외] 상행역과 하행역이 둘다 존재할 수 없다.
    • [예외] 추가하려는 거리가 기존 구간의 거리보다 짧아야 한다.
    • [예외] 거리는 음수일 수 없다.
  • 구간을 삭제한다.
    • [예외] 구간이 포함된 역의 구간이 2개 이상이어야 한다.
    • 삭제하려는 구간이 종점일 경우 이전역이 종점이 된다.
    • 중간역이 삭제되는 경우 재배치된다.

지하철 경로 조회 API

1단계 : 경로조회 기능

  • 경로를 조회한다.

    • jgrapht 라이브러리를 활용한다.
      • 점(vertex)과 간선(edge), 그리고 가중치 개념을 이용
        • 정점: 지하철역(Station)
        • 간선: 지하철역 연결정보(Section)
        • 가중치: 거리
      • 최단 거리 기준 조회 시 가중치를 거리로 설정
    • 최단 경로 및 거리를 반환한다.
    • 경로가 존재하는지 검증한다.
  • 요금을 계산한다.

    • 기본운임(10㎞ 이내): 기본운임 1,250원
    • 이용 거리 초과 시 추가운임 부과
      • 10km~50km: 5km 까지 마다 100원 추가
      • 50km 초과: 8km 까지 마다 100원 추가
      • 지하철 운임은 거리비례제로 책정된다.
  • 9km = 1250원

  • 12km = 10km + 2km = 1350원

  • 16km = 10km + 6km = 1450원

  • 58km = 10km + 40km + 8km = 2150원

2단계 : 추가된 요금 정책

DB 테이블 변경

  • LINE 테이블에 추가 요금 컬럼 추가
  • LineRequest에 추가 요금 필드 추가
  • LineUpdateRequest에 추가 요금 필드 추가
  • LineResponse에 추가 요금 필드 추가
  • Line에 추가 요금 필드 추가
  • LineEntity에 추가 요금 필드 추가

노선별 추가요금

  • 추가 요금이 있는 노선을 이용 할 경우 측정된 요금에 추가
    • ex) 900원 추가 요금이 있는 노선 8km 이용 시 1,250원 -> 2,150원
    • ex) 900원 추가 요금이 있는 노선 12km 이용 시 1,350원 -> 2,250원
  • 경로 중 추가요금이 있는 노선을 환승 하여 이용 할 경우 가장 높은 금액의 추가 요금만 적용
    • ex) 0원, 500원, 900원의 추가 요금이 있는 노선들을 경유하여 8km 이용 시 1,250원 -> 2,150원

연령별 요금 할인

  • 청소년: 운임에서 350원을 공제한 금액의 20% 할인
    • 청소년: 13세 이상~19세 미만
  • 어린이: 운임에서 350원을 공제한 금액의 50%할인
    • 어린이: 6세 이상~13세 미만
  • 아기: 공짜
    • 아기: 6세 미만

TODO

목표

  • service 단의 for/if문 -> dao 안에서 새 메소드(...all) 생성, batch... 사용해 한번에 처리
  • 테스트코드 패키지 위치 맞추기
  • repository 중 메인 repository 설정 -> sectionEntity를 바로 section으로 변경
  • 요금 정책 인터페이스, 이후 정책 별로 클래스 분리
    • miro로 미리 설계

질문사항

1단계-1 피드백

  • 한번만 쓰이는 private 메소드의 경우 쓰이는 곳 밑에 두기
  • 엔티티의 id는 새로 생성된 경우 null로 나타내기 위해 Long 타입을 사용 -> 다른 필드도 Long으로 나타낼 필요가 있을까요?
    • 이미 값이 엔티티에 들어올 때 Long으로 들어오기 때문에 그대로 뒀다. 오토박싱/언박싱 과정에서 시간이 소요될 것 같았다.
    • 혹시 그럼에도 박싱된 클래스를 쓰지 않는 편이 낫다면 그 이유는 무엇인가요?
  • 무엇을 validate하는지 혹은 어떤 조건을 validate하는지 메서드명에 나타내보면 어떨까요?
  • Update는 동사인데, updatedSections를 찾는 메서드일까요?
  • 메서드를 호출하는 입장에서는 next 파라미터가 무엇을 의미하는지 알 수 있을까요?
  • find 뿐만 아니라 인자로 받은 result에 add도 하고 있네요. 이 메서드에 더 적절한 이름이 있을까요?
  • 매직 넘버는 상수로 분리하면 어떨까요?
  • DB 엔티티와 도메인 객체 사이의 변환을 책임지는 계층을 별도로 두었을 때 어떤 장점이 있었나요?
  • id가 LineUpdateRequest 내에 포함되면 어떨까요?
  • 2단계 요구사항이 반영된 걸까요?
  • 예외 시 log.error(); 추가

1단계-2 피드백

  • Dto setter 제거
  • SectionEntity의 경우 생성자에서 id를 제외한 필드는 long 타입으로 선언돼 있는 걸로 봐서는 null을 허용할 필요는 없어보이기도 하네요.

2단계-1 피드백

  • AgeDiscountPolicy 인터페이스 제거
  • 새 엔티티의 id를 null 대신 0으로 초기화하는군요. id 필드는 더이상 Long 타입일 필요가 없을까요? -> 모두 long으로 수정!
  • 호출하게 되면 sections.isStationIn(station)처럼 호출하게 될 텐데, 코드가 부자연스러워보이네요.
    • sections.contains(station)처럼 호출할 수 있으면 어떨까요?
  • getFare가 여러번 호출되면 계산도 매번 발생할텐데요. 매번 계산할 필요가 있을까요?
    • baseFare는 기본요금일까요?
  • Generator -> 매직 넘버는 상수로 분리하면 어떨까요?
  • UnderTenKMPolicy 에서도 같은 상수가 선언돼 있는데, 필요한 중복일까요?
  • removeStationById -> StationService 대신 이곳에 위치한 이유가 있을까요?
    • 해당 역들을 지울 때, 역이 노선 구간들 안에 포함되어있는지 확인하는 과정이 필요하기 때문에 총괄적인 repository들을 참조하는 lineService 안에 해당 부분을 넣었습니다!
  • extraFare INT 칼럼에 NOT NULL 추가
  • PathService.java
    • 만약 정책 중에 정해진 금액을 할인하는 대신 정해진 비율(e.g., 20%)로 할인하는 정책이 추가된다면 이곳이 변경될까요?
    • -> 이미 나이할인 시 운임요금 공제 후 일정비율의 할인이 들어감
    • 여러 정책들이 각자 어떤 순서로 실행될지에 대해서는 서비스 단에서 결정하는 편이 좋다고 생각했음.(변경이 일어나기 쉬운 부분이라 생각했음.)