Skip to content
Open
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
70 changes: 32 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,48 @@
<br>

## 🚉 지하철 노선도 애플리케이션
<br>

<p align="center">
<img width="200px;" src="https://raw.githubusercontent.com/woowacourse/atdd-subway-admin-frontend/master/images/main_logo.png"/>
</p>
<p align="center">
<img alt="npm" src="https://img.shields.io/badge/npm-6.14.15-blue">
<img alt="node" src="https://img.shields.io/badge/node-14.18.2-blue">
<a href="https://edu.nextstep.camp/c/R89PYi5H" alt="nextstep atdd">
<img alt="Website" src="https://img.shields.io/website?url=https%3A%2F%2Fedu.nextstep.camp%2Fc%2FR89PYi5H">
</a>
<img alt="GitHub" src="https://img.shields.io/github/license/next-step/atdd-subway-admin">
</p>

<br>
### 💻 나의 프로젝트의 목표와 성과
#### 목표
- **도메인 주도 개발**을 위하여 Service의 비즈니스 로직을 Domain으로 옮기는 리팩터링을 진행
- 인수 테스트 기반으로 API를 검증하기 보다는 **시나리오, 흐름을 검증**하는 테스트로 리팩터링
- 외부 라이브러리를 사용하는 직접 구현하는 로직을 검증해야 하므로 **실제 객체를 활용하여 테스트 코드** 작성
- **토큰 발급(로그인)을 검증**하는 인수 테스트 구현

# 지하철 노선도 미션
[ATDD 강의](https://edu.nextstep.camp/c/R89PYi5H) 실습을 위한 지하철 노선도 애플리케이션
#### 성과
- 역 추가,제거 로직을 Service Layer에서 Line 도메인으로 이동하며 도메인 주도 개발의 중요성에 대해 깨달았다. <br>
[↳ 도메인 주도 설계에 관한 블로그 포스팅 (@yyy96)](https://velog.io/@yyy96/비즈니스로직)

- mock 서버와 dto를 정의하여 인수 테스트 외부 라이브러리(`jgrapht 라이브러리`)에 대한 테스트 코드 작성 <br>
[↳ 테스트 더블에 관한 블로그 포스팅 (@yyy96)](https://velog.io/@yyy96/Mock)

<br>
- 토큰 발급 인수 테스트를 진행하며 인증에 관하여 다시 한번 학습 <br>
[↳ 인증과 테스트에 관한 블로그 포스팅 (@yyy96)](https://velog.io/@yyy96/인증)

## 🚀 Getting Started

### Install
#### npm 설치
```
cd frontend
npm install
```
> `frontend` 디렉토리에서 수행해야 합니다.

### Usage
#### webpack server 구동
```
npm run dev
```
#### application 구동
```
./gradlew bootRun
```
<br>
- `디미터의 법칙(Law of Demeter)`을 준수하며 객체 지향적 설계 리팩터링 <br>
↳ Sections에서 매번 section의 정보를 가져올 것이 아니라 section 에서 따로 stationId 가 일치하는지 메서드를 생성

## ✏️ Code Review Process
[텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)

<br>
<br>

## 🐞 Bug Report

버그를 발견한다면, [Issues](https://github.com/next-step/atdd-subway-service/issues) 에 등록해주세요 :)
### [🚆 도메인 주도 개발 및 인수 테스트 통합 (branch)](https://github.com/yyy96/ddd-subway-service/tree/atdd1)
### [🚆 경로 조회 기능 추가 (branch)](https://github.com/yyy96/ddd-subway-service/tree/atdd2)
![image](https://user-images.githubusercontent.com/65826145/196177442-bec42307-05a6-4688-91d2-08fd33dafc82.png)
### [🚆 나의 즐겨찾기 기능과 토큰 발급 인수 테스트 (branch)](https://github.com/yyy96/ddd-subway-service/tree/atdd3)
![image](https://user-images.githubusercontent.com/65826145/196177583-22f13ff6-dfc5-49ed-963a-78cbb86f10d0.png)

<br>
<br>

### [📝 코드 리뷰 및 리팩터링 과정](https://github.com/next-step/atdd-subway-service/pulls?q=is%3Apr+is%3Aclosed+author%3Ayyy96)
![image](https://user-images.githubusercontent.com/65826145/196175541-7892eefb-98d5-416e-8394-e2a3ad4122e3.png)
![image](https://user-images.githubusercontent.com/65826145/196175403-9826b4bc-1285-4603-8246-211c226b2c21.png)

## 📝 License

This project is [MIT](https://github.com/next-step/atdd-subway-service/blob/master/LICENSE.md) licensed.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public TokenResponse login(TokenRequest request) {

public LoginMember findMemberByToken(String credentials) {
if (!jwtTokenProvider.validateToken(credentials)) {
return new LoginMember();
throw new AuthorizationException();
}

String email = jwtTokenProvider.getPayload(credentials);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package nextstep.subway.favorite.application;

import nextstep.subway.auth.domain.LoginMember;
import nextstep.subway.favorite.domain.Favorite;
import nextstep.subway.favorite.domain.FavoriteRepository;
import nextstep.subway.favorite.dto.FavoriteRequest;
import nextstep.subway.favorite.dto.FavoriteResponse;
import nextstep.subway.member.application.MemberService;
import nextstep.subway.member.domain.Member;
import nextstep.subway.station.application.StationService;
import nextstep.subway.station.domain.Station;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class FavoriteService {
private FavoriteRepository favoriteRepository;
private StationService stationService;
private MemberService memberService;

public FavoriteService(FavoriteRepository favoriteRepository, StationService stationService, MemberService memberService) {
this.favoriteRepository = favoriteRepository;
this.stationService = stationService;
this.memberService = memberService;
}

public FavoriteResponse saveFavorite(LoginMember loginMember, FavoriteRequest favoriteRequest) {
Member member = memberService.getAuthMember(loginMember);
Station source = stationService.findStationById(favoriteRequest.getSourceId());
Station target = stationService.findStationById(favoriteRequest.getTargetId());
Favorite favorite = favoriteRepository.save(Favorite.of(member, source, target));
return FavoriteResponse.of(favorite);
}

public List<FavoriteResponse> findFavorites(Long memberId) {
List<Favorite> favorites = favoriteRepository.findAllByMemberId(memberId);
return FavoriteResponse.of(favorites);
}

public void deleteFavorite(Long id) {
favoriteRepository.deleteById(id);
}
}
52 changes: 52 additions & 0 deletions src/main/java/nextstep/subway/favorite/domain/Favorite.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package nextstep.subway.favorite.domain;

import nextstep.subway.BaseEntity;
import nextstep.subway.member.domain.Member;
import nextstep.subway.station.domain.Station;

import javax.persistence.*;

@Entity
public class Favorite extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@ManyToOne
@JoinColumn(name = "source_id")
private Station source;
@ManyToOne
@JoinColumn(name = "target_id")
private Station target;

protected Favorite() {
}

public Favorite(Member member, Station source, Station target) {
this.member = member;
this.source = source;
this.target = target;
}

public static Favorite of(Member member, Station source, Station target) {
return new Favorite(member, source, target);
}

public Long getId() {
return id;
}

public Member getMember() {
return member;
}

public Station getSource() {
return source;
}

public Station getTarget() {
return target;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nextstep.subway.favorite.domain;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface FavoriteRepository extends JpaRepository<Favorite, Long> {
List<Favorite> findAllByMemberId(Long id);
}
22 changes: 22 additions & 0 deletions src/main/java/nextstep/subway/favorite/dto/FavoriteRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.subway.favorite.dto;

public class FavoriteRequest {
private Long sourceId;
private Long targetId;

public FavoriteRequest() {
}

public FavoriteRequest(Long sourceId, Long targetId) {
this.sourceId = sourceId;
this.targetId = targetId;
}

public Long getSourceId() {
return sourceId;
}

public Long getTargetId() {
return targetId;
}
}
44 changes: 44 additions & 0 deletions src/main/java/nextstep/subway/favorite/dto/FavoriteResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package nextstep.subway.favorite.dto;

import nextstep.subway.favorite.domain.Favorite;
import nextstep.subway.station.dto.StationResponse;

import java.util.List;
import java.util.stream.Collectors;

public class FavoriteResponse {
private Long id;
private StationResponse source;
private StationResponse target;

public FavoriteResponse() {
}

public FavoriteResponse(Long id, StationResponse source, StationResponse target) {
this.id = id;
this.source = source;
this.target = target;
}

public static FavoriteResponse of(Favorite favorite) {
return new FavoriteResponse(favorite.getId(), StationResponse.of(favorite.getSource()), StationResponse.of(favorite.getTarget()));
}

public static List<FavoriteResponse> of(List<Favorite> favorites) {
return favorites.stream()
.map(FavoriteResponse::of)
.collect(Collectors.toList());
}

public Long getId() {
return id;
}

public StationResponse getSource() {
return source;
}

public StationResponse getTarget() {
return target;
}
}
40 changes: 40 additions & 0 deletions src/main/java/nextstep/subway/favorite/ui/FavoriteController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package nextstep.subway.favorite.ui;

import nextstep.subway.auth.domain.AuthenticationPrincipal;
import nextstep.subway.auth.domain.LoginMember;
import nextstep.subway.favorite.application.FavoriteService;
import nextstep.subway.favorite.dto.FavoriteRequest;
import nextstep.subway.favorite.dto.FavoriteResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.net.URI;
import java.util.List;

@RestController
@RequestMapping("/favorites")
public class FavoriteController {

private FavoriteService favoriteService;

public FavoriteController(FavoriteService favoriteService) {
this.favoriteService = favoriteService;
}

@PostMapping
public ResponseEntity createFavorite(@AuthenticationPrincipal LoginMember loginMember, @RequestBody FavoriteRequest request) {
FavoriteResponse favorite = favoriteService.saveFavorite(loginMember, request);
return ResponseEntity.created(URI.create("/favorites/" + favorite.getId())).body(favorite);
}

@GetMapping
public ResponseEntity<List<FavoriteResponse>> findFavorites(@AuthenticationPrincipal LoginMember loginMember) {
return ResponseEntity.ok().body(favoriteService.findFavorites(loginMember.getId()));
}

@DeleteMapping("/{id}")
public ResponseEntity deleteFavorite(@PathVariable Long id) {
favoriteService.deleteFavorite(id);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package nextstep.subway.member.application;

import nextstep.subway.auth.application.AuthorizationException;
import nextstep.subway.auth.domain.LoginMember;
import nextstep.subway.member.domain.Member;
import nextstep.subway.member.domain.MemberRepository;
import nextstep.subway.member.dto.MemberRequest;
import nextstep.subway.member.dto.MemberResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MemberService {
Expand All @@ -25,6 +26,10 @@ public MemberResponse findMember(Long id) {
return MemberResponse.of(member);
}

public Member getAuthMember(LoginMember loginMember){
return memberRepository.findById(loginMember.getId()).orElseThrow(AuthorizationException::new);
}

public void updateMember(Long id, MemberRequest param) {
Member member = memberRepository.findById(id).orElseThrow(RuntimeException::new);
member.update(param.toMember());
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/nextstep/subway/member/ui/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ public ResponseEntity<MemberResponse> deleteMember(@PathVariable Long id) {
}

@GetMapping("/members/me")
public ResponseEntity<MemberResponse> findMemberOfMine(LoginMember loginMember) {
public ResponseEntity<MemberResponse> findMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
MemberResponse member = memberService.findMember(loginMember.getId());
return ResponseEntity.ok().body(member);
}

@PutMapping("/members/me")
public ResponseEntity<MemberResponse> updateMemberOfMine(LoginMember loginMember, @RequestBody MemberRequest param) {
public ResponseEntity<MemberResponse> updateMemberOfMine(@AuthenticationPrincipal LoginMember loginMember, @RequestBody MemberRequest param) {
memberService.updateMember(loginMember.getId(), param);
return ResponseEntity.ok().build();
}

@DeleteMapping("/members/me")
public ResponseEntity<MemberResponse> deleteMemberOfMine(LoginMember loginMember) {
public ResponseEntity<MemberResponse> deleteMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
memberService.deleteMember(loginMember.getId());
return ResponseEntity.noContent().build();
}
Expand Down
Loading