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

[1단계 - 지하철 정보 관리 기능] 제이미(임정수) 미션 제출합니다. #80

Merged
merged 16 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 97 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,97 @@
# jwp-subway-path
# jwp-subway-path

## API 기능 요구사항 구현하기

1. 노선에 역 등록 API 신규 구현
1. 노선 신규 등록
:ux를 고려했을 때, 신규 노선을 등록할 때, 초기 역을 함께 설정하는 것이 적합하다고 판단함
```
POST /lines
{
"name": "2호선"
"color": "Green"
}
```

2. 기존 노선에 역 등록
```
POST /lines/{lineId}/stations
{
"upStation": "낙성대id"
"downStation": "사당id"
"distance": 1
}
```
- 빈 노선인 경우,두 역을 동시에 등록해야 한다
- 빈노선이 아닌 경우, 한 역은 해당 노선에 존재하는 역이어야 하고, 다른 역은 노선에 등록되지 않은 역이어야 한다
- 거리 정보는 양의 정수로 제한한다

2. 노선에 역 제거 API 신규 구현
1. 노선 삭제
:ux를 고려했을 때, 신규 노선을 등록할 때, 초기 역을 함께 설정하는 것이 적합하다고 판단함
```
DELETE /lines/{lineId}
```

2. 노선에 역 삭제
```
DELETE /lines/{lineId}/stations/{stationId}
```
-
3. 노선 조회 API 수정
1. 노선 조회
```
GET /lines/{lineId}
```

2. 노선 목록 조회
```
GET /lines
```

4. 테스트 하기
- 노선에 역이 하나도 등록되지 않은 상황에서 최초 등록 시 두 역을 동시에 등록해야 합니다.
- 하나의 역은 여러 노선에 등록이 될 수 있습니다.
(1호선: A-B-C, 2호선: Z-B-D 처럼 B역은 두개 이상의 노선에 포함될 수 있습니다.)
- 노선 가운데 역이 등록 될 경우 거리 정보를 고려해야 합니다.
A-B-C 노선에서 B 다음에 D 역을 등록하려고 하는데
B-C가 3km, B-D거리가 2km라면 B-D거리는 2km로 등록되어야 하고 D-C 거리는 1km로 등록되어야 합니다.
- 노선 가운데 역이 등록 될 경우 거리는 양의 정수라는 비즈니스 규칙을 지켜야 합니다.
A-B-C 노선에서 B 다음에 D 역을 등록하려고 하는데
B-C역의 거리가 3km인 경우 B-D 거리는 3km보다 적어야 합니다.
- 노선에 역 제거 테스트
- 노선에서 역을 제거할 경우 정상 동작을 위해 재배치 되어야 합니다.
A-B-C-D 역이 있는 노선에서 C역이 제거되는 경우 A-B-D 순으로 재배치됩니다.
- 노선에서 역이 제거될 경우 역과 역 사이의 거리도 재배정되어야 합니다.
A-B가 2km, B-C가 3km, C-D가 4km인 경우 C역이 제거되면 B-D 거리가 7km가 되어야 합니다.
- 노선에 등록된 역이 2개 인 경우 하나의 역을 제거할 때 두 역이 모두 제거되어야 합니다.
A-B 노선에서 B를 제거할 때 거리 정보를 포함할 수 없기 때문에 두 역 모두 제거되어야 합니다.


## 도메인 설계하기
### Line
- 필드
- Long id
- String name => LineName name (-선으로 끝나야함) endwith
- String color => Color color
- Sections
- 기능
- 역 목록 조회하기

### Sections
- 필드
- List<Section>
- 기능
- 역 상행-하행 순으로 정렬하기
- 역 목록 조회하기

### Section
- 필드
- Station upStation
- Station downStation
- Integer distance => Distance distance

### Station
- 필드
- Long id
- String name => StaionName name (-역으로 끝나야함) endwith
73 changes: 56 additions & 17 deletions src/main/java/subway/application/LineService.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,92 @@
package subway.application;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import subway.dao.LineDao;
import subway.dao.LineEntity;
import subway.dao.SectionDao;
import subway.domain.Line;
import subway.domain.Section;
import subway.domain.Sections;
import subway.dto.LineRequest;
import subway.dto.LineResponse;

import java.util.List;
import java.util.stream.Collectors;
import subway.exception.LineDuplicatedException;

@Service
public class LineService {

private final LineDao lineDao;
private final SectionDao sectionDao;

public LineService(LineDao lineDao) {

public LineService(LineDao lineDao, SectionDao sectionDao) {
this.lineDao = lineDao;
this.sectionDao = sectionDao;
}

public LineResponse saveLine(LineRequest request) {
Line persistLine = lineDao.insert(new Line(request.getName(), request.getColor()));
validateDuplicateLine(request);
LineEntity lineEntity = new LineEntity(request.getName(), request.getColor());
LineEntity savedLine = lineDao.insert(lineEntity);
Line line = new Line(savedLine.getId(), savedLine.getName(), savedLine.getColor(), Sections.EMPTY_SECTION);
return LineResponse.of(line);
}

public LineResponse findLineResponseById(Long id) {
Line persistLine = findLineById(id);
return LineResponse.of(persistLine);
}

private Line findLineById(Long lineId) {
LineEntity lineEntity = lineDao.findById(lineId).orElseThrow(() -> new NotFoundException("해당 노선이 존재하지 않습니다"));
List<Section> sections = sectionDao.findSectionsByLineId(lineId);
return new Line(lineId, lineEntity.getName(), lineEntity.getColor(), new Sections(sections));
}

public List<LineResponse> findLineResponses() {
List<Line> persistLines = findLines();
return persistLines.stream()
List<Line> persistLine = findLines();
return persistLine.stream()
.map(LineResponse::of)
.collect(Collectors.toList());
}

public List<Line> findLines() {
return lineDao.findAll();
List<LineEntity> lineEntities = lineDao.findAll();
return lineEntities.stream()
.map(lineEntity ->
new Line(lineEntity.getId(), lineEntity.getName(), lineEntity.getColor(),
new Sections(sectionDao.findSectionsByLineId(lineEntity.getId()))
)
)
.collect(Collectors.toList());
}

public LineResponse findLineResponseById(Long id) {
Line persistLine = findLineById(id);
return LineResponse.of(persistLine);
public void updateLine(Long id, LineRequest lineUpdateRequest) {
checkIfExistLine(id);
LineEntity lineEntity = new LineEntity(id, lineUpdateRequest.getName(), lineUpdateRequest.getColor());
lineDao.update(lineEntity);
}

public Line findLineById(Long id) {
return lineDao.findById(id);
public void deleteLineById(Long id) {
checkIfExistLine(id);
lineDao.deleteById(id);
}

public void updateLine(Long id, LineRequest lineUpdateRequest) {
lineDao.update(new Line(id, lineUpdateRequest.getName(), lineUpdateRequest.getColor()));
private void checkIfExistLine(Long lineId) {
lineDao.findById(lineId).orElseThrow(() -> new NotFoundException("해당 노선이 존재하지 않습니다."));
}

public void deleteLineById(Long id) {
lineDao.deleteById(id);
private void validateDuplicateLine(LineRequest request) {
Optional<LineEntity> optionalLineByName = lineDao.findByName(request.getName());
optionalLineByName.ifPresent(findUser -> {
throw new LineDuplicatedException("이미 존재하는 노선 이름입니다.");
});
Optional<LineEntity> optionalLineByColor = lineDao.findByColor(request.getColor());
optionalLineByColor.ifPresent(findUser -> {
throw new LineDuplicatedException("이미 존재하는 노선 색입니다.");
});
}

}
10 changes: 10 additions & 0 deletions src/main/java/subway/application/NotFoundException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package subway.application;

public class NotFoundException extends RuntimeException {

public NotFoundException(){};

public NotFoundException(String message) {
super(message);
}
}
170 changes: 170 additions & 0 deletions src/main/java/subway/application/SectionService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package subway.application;

import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import subway.dao.LineDao;
import subway.dao.SectionDao;
import subway.dao.SectionEntity;
import subway.dao.StationDao;
import subway.dto.SectionRequest;
import subway.exception.InvalidSectionException;

@Service
public class SectionService {

private final LineDao lineDao;
private final StationDao stationDao;
private final SectionDao sectionDao;

public SectionService(LineDao lineDao, StationDao stationDao, SectionDao sectionDao) {
this.lineDao = lineDao;
this.stationDao = stationDao;
this.sectionDao = sectionDao;
}

public void saveSection(Long lineId, SectionRequest sectionRequest) {
checkIfExistLine(lineId);
checkIfExistStation(sectionRequest.getUpStationId());
checkIfExistStation(sectionRequest.getDownStationId());
SectionEntity sectionEntity = new SectionEntity(lineId, sectionRequest.getUpStationId(),
sectionRequest.getDownStationId(), sectionRequest.getDistance());

if (isEmptyLine(lineId)) {
sectionDao.insert(sectionEntity);
return;
}
saveSectionWhenLineIsNotEmpty(sectionEntity);
}

private boolean isEmptyLine(Long lineId) {
Optional<List<SectionEntity>> byLineId = sectionDao.findByLineId(lineId);
return byLineId.get().isEmpty();
}

private void saveSectionWhenLineIsNotEmpty(SectionEntity sectionEntity) {
checkIfImpossibleSection(sectionEntity);
addStationBetweenStations(sectionEntity);
}

private void checkIfImpossibleSection(SectionEntity sectionEntity) {
Long lineId = sectionEntity.getLineId();
Long upStationId = sectionEntity.getUpStationId();
Long downStationId = sectionEntity.getDownStationId();
boolean hasUpStationInLine = hasStationInLine(upStationId, lineId);
boolean hasDownStationInLine = hasStationInLine(downStationId, lineId);

if (hasUpStationInLine && hasDownStationInLine) {
throw new InvalidSectionException("이미 존재하는 구간입니다.");
}
}

private boolean hasStationInLine(Long stationId, Long lineId) {
return sectionDao.findByUpStationId(stationId, lineId).isPresent()
|| sectionDao.findByDownStationId(stationId, lineId).isPresent();
}

private void addStationBetweenStations(SectionEntity sectionEntity) {
if (hasStationInLine(sectionEntity.getUpStationId(), sectionEntity.getLineId())) {
addSectionBasedOnUpStation(sectionEntity.getUpStationId(), sectionEntity);
return;
}
if (hasStationInLine(sectionEntity.getDownStationId(), sectionEntity.getLineId())) {
addSectionBasedOnDownStation(sectionEntity.getDownStationId(), sectionEntity);
return;
}
throw new InvalidSectionException("한 역은 기존의 노선에 존재해야 합니다.");
}

public void addSectionBasedOnUpStation(Long upStationId, SectionEntity sectionToAdd) {
Long lineId = sectionToAdd.getLineId();
Optional<SectionEntity> originalSectionEntity = sectionDao.findByUpStationId(upStationId, lineId);
if (originalSectionEntity.isPresent()) {
SectionEntity originalSection = originalSectionEntity.get();
Long downStationIdOfOrigin = originalSection.getDownStationId();
Long downStationIdOfToAdd = sectionToAdd.getDownStationId();
int revisedDistance = findRevisedDistance(sectionToAdd, originalSection);
SectionEntity revisedSection = new SectionEntity(lineId, downStationIdOfToAdd, downStationIdOfOrigin,
revisedDistance);
sectionDao.updateByDownStationId(revisedSection);
sectionDao.insert(sectionToAdd);
return;
}

if (sectionDao.findByDownStationId(upStationId, lineId).isPresent()) {
sectionDao.insert(sectionToAdd);
}
}

public void addSectionBasedOnDownStation(Long downStationId, SectionEntity sectionToAdd) {
Long lineId = sectionToAdd.getLineId();
if (sectionDao.findByUpStationId(downStationId, lineId).isPresent()) {
sectionDao.insert(sectionToAdd);
return;
}
Optional<SectionEntity> originalSectionEntity = sectionDao.findByDownStationId(downStationId, lineId);
if (originalSectionEntity.isPresent()) {
SectionEntity originalSection = originalSectionEntity.get();
Long upStationIdOfOrigin = originalSection.getUpStationId();
Long upStationIdOfToAdd = sectionToAdd.getUpStationId();
int revisedDistance = findRevisedDistance(sectionToAdd, originalSection);

SectionEntity revisedSection = new SectionEntity(lineId, upStationIdOfOrigin, upStationIdOfToAdd,
revisedDistance);
sectionDao.updateByUpStationId(revisedSection);
sectionDao.insert(sectionToAdd);
}
}

private static int findRevisedDistance(SectionEntity sectionToAdd, SectionEntity originalSection) {
int revisedDistance = originalSection.getDistance() - sectionToAdd.getDistance();
if (revisedDistance <= 0) {
throw new InvalidSectionException("현재 구간보다 큰 구간은 입력할 수 없습니다.");
}
return revisedDistance;
}

public void removeStationFromLine(Long lineId, Long stationIdToRemove) {
checkIfExistLine(lineId);
checkIfExistStation(stationIdToRemove);
if (hasStationInLine(stationIdToRemove, lineId)) {
deleteStationBetweenStations(lineId, stationIdToRemove);
return;
}
Optional<SectionEntity> upStationEntity = sectionDao.findByUpStationId(stationIdToRemove, lineId);
if (upStationEntity.isPresent()) {
SectionEntity upSectionOfOrigin = upStationEntity.get();
sectionDao.delete(upSectionOfOrigin);
return;
}
Optional<SectionEntity> downStationEntity = sectionDao.findByDownStationId(stationIdToRemove, lineId);
if (downStationEntity.isPresent()) {
SectionEntity downSectionOfOrigin = downStationEntity.get();
sectionDao.delete(downSectionOfOrigin);
return;
}
throw new NotFoundException("노선에 역이 존재하지 않습니다.");
}

private void deleteStationBetweenStations(Long lineId, Long stationIdToRemove) {
SectionEntity upSectionOfOrigin = sectionDao.findByUpStationId(stationIdToRemove, lineId).get();
SectionEntity downSectionOfOrigin = sectionDao.findByDownStationId(stationIdToRemove, lineId).get();
int revisedDistance = upSectionOfOrigin.getDistance() + downSectionOfOrigin.getDistance();

SectionEntity revisedSection = new SectionEntity(lineId, upSectionOfOrigin.getUpStationId(),
downSectionOfOrigin.getDownStationId(), revisedDistance);

sectionDao.updateByUpStationId(revisedSection);
sectionDao.delete(downSectionOfOrigin);
}


private void checkIfExistLine(Long lineId) {
lineDao.findById(lineId).orElseThrow(() -> new NotFoundException("해당 노선이 존재하지 않습니다."));
}

private void checkIfExistStation(Long stationId) {
stationDao.findById(stationId).orElseThrow(() -> new NotFoundException("해당 역이 존재하지 않습니다."));
}

}
Loading