[그리디] 서현진 Spring Core 배포 7, 8, 9 단계 미션 제출합니다.#211
[그리디] 서현진 Spring Core 배포 7, 8, 9 단계 미션 제출합니다.#211nonactress wants to merge 54 commits intonext-step:nonactressfrom
Conversation
supernovaMK
left a comment
There was a problem hiding this comment.
안녕하세요 현진님~ 반갑습니다. 이번 리뷰어를 맡게 된 김민기입니다.
미션 요구사항 모두 잘 구현해주셨네요~
코멘트 몇 개 남겼는데 확인해주세요! 추가적으로 이야기 나누고 싶으신 것 있으시다면 편하게 남겨주세요!
| security.jwt.token.secret-key=thisismysecretkeyandgreedyzzangandsoftwarefighting | ||
| security.jwt.token.expire-length=3600000 |
There was a problem hiding this comment.
public repo를 사용하는 상황에서 secret key를 어떻게 공유하고 관리할 수 있을까요?
There was a problem hiding this comment.
찾아 보니 크게 2가지 방법이 있는 것 같습니다!!
1. 환경 변수 사용
코드에는 변수명만 적고, 실제 값은 서버 시스템 환경 변수로 등록하는 것입니다.
예시 (파일 : application.yml)
api: key: ${API_KEY_VALUE}
2. 실행 시 인자 전달
애플리케이션을 실행할 때 직접 값을 주입합니다.
java -jar app.jar --api.key=your-secret-key-123
There was a problem hiding this comment.
앗 제가 의도를 잘못 전달드렸나봐요.
만약 applicaiton-key.properties를 .gitignore하게 된다면 팀원들도 github에서 확인할 수 없을 것 같아요.
비밀로 유지해야하는 secret key값을 팀원들과 어떻게 공유하고 관리할 수 있을지에 대한 질문이었어요!
메모장에서 써서 공유하기,.env 파일 활용하기, git submodule 활용하기 와 같은 방식들을 한번 살펴보시면 좋을 것 같아요!
사용해보시면서 각각의 장단점을 생각해보다보면 더 좋을 것 같아요~
| Jws<Claims> claims = Jwts.parser() | ||
| .setSigningKey(secretKey) | ||
| .parseClaimsJws(token); | ||
| return !claims.getBody().getExpiration().before(new Date()); |
There was a problem hiding this comment.
토큰이 만료가 되었는지 따로 검사하시는 이유가 있나요?
parseClaimsJws과정에서 만료 검사를 하는 것으로 알고 있어서요!
There was a problem hiding this comment.
아 parseClaimsJws 가 토큰의 만료 시간을 검사 해주는 기능도 있는 지 몰랐습니다!! 이와 관련해서 만약 토큰이 만료 기한이 지났다면 ExpiredJwtException 를 던지는 것을 찾아보았습니다! 이를 반영하여 만료시간 예외 처리하도록 반영해보겠습니다!
반영 커밋 : c5bdf60
| throw new AuthenticationException("인증되지 않은 사용자입니다."); | ||
| } | ||
|
|
||
| return memberService.findByToken(token); |
There was a problem hiding this comment.
MemberService 를 참조하는 이유가 무엇인가요?
현진님이 정의하신 AuthService의 책임이 궁금해요!
AuthService에서 repository에 접근하여 member객체를 찾아왔을 수 도 있었어서요!
There was a problem hiding this comment.
AuthService의 책임
AuthService는 요청에서 토큰을 추출하고 그 토큰이 유효한지 검증하는 인증 프로세스라고 생각합니다!
반면, 특정 토큰을 가진 회원을 찾는 구체적인 로직(조회 방식, 예외 처리 등)은 Member 도메인을 관리하는 MemberService의 책임이라고 생각했습니다!!
AuthService에서 repository에 접근하여 member객체를 찾아왔을 수 도 있었어서요!
말씀하신 대로 AuthService 에서 직접 레포지토리에 접근할 수도 있지만, 그렇게 되면 Member 엔티티를 다루는 로직이 여러 서비스로 흩어지게 되어 나중에 Member 조회 로직이 변경될 때 유지 보수가 어려울 것 같아서 서비스를 사용해 보았습니다!
There was a problem hiding this comment.
오호 그렇군요~
memeber 엔티티를 다루는 로직이 여러 곳에 흩어지는 것을 주의해주셨군요~
- Token에 관련된 로직이 memberService에서의 책임일까?
- AuthService에서 꼭 Member 객체를 반환해야할까?
- Service가 Service를 참조할 때 나중에 문제가 생기지는 않을까?
등등을 같이 고민해보면 좋을 것 같습니다!
현재 미션에서는 이미 충분히 잘 구현해주셨지만, 더 고민해보면 좋을 지점인 것 같아서요!
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| public class JwtConfig { |
There was a problem hiding this comment.
제가 만든 클래스를 빈으로 등록시켜 간편하게 매번 사용할 때 마다 생성하지 않아도 되서 편했던 것 같습니다!
There was a problem hiding this comment.
그렇군요!
빈으로 관리하였을 때의 장점을 느끼셨군요!
@component 어노테이션을 통해서도 빈으로 등록하여도 빈으로 등록되는 것은 같을 것 같아요.
@configuration을 사용하면 저는 어떤 구현체로 bean을 등록하여 사용할지 configuration 클래스에서 설정할 수 있어서 편하다고 느꼈어요! 현재 서비스에서는 jwtProvider 구현체가 1개이지만 실제에서는 더 많이 변경되고 생겨날 수 있을 것 같네요.
더 자주 사용해보시고 편리한점이 더 있으셨다면 공유해주세요!
| import roomescape.time.TimeRepository; | ||
|
|
||
| @Component | ||
| public class ProductionDataLoader implements CommandLineRunner { |
There was a problem hiding this comment.
.sql 파일을 활용하는 대신 CommandLineRunner를 활용했을 때 어떤 차이가 있었나요?
장단점은 있을까요?
There was a problem hiding this comment.
sql
장점 - 퀴리 문으로 작동해서 매우 빠르다
단점 - 퀴리 문법을 알아야 한다....
CommandLineRunner
장점 - 자바 로직으로 데이터를 만들 수 있다, 복잡한 엔티티 연관관계 보다 쉽게 만들 수 있다!
단점 - 속도가 느리다....
차이는 속도적인 차이가 존재하고 sql 엔티티 연관관계 구조를 매번 적용 시켜줘야 한다는 단점이 있는 거 같습니다!
There was a problem hiding this comment.
오호 그렇군요~ 차이를 공유해주셔서 감사합니다.
제가 경험했을 때는 sql문을 사용하다보면 테스트가 변경될 때마다 매번 복잡한 쿼리문을 작성하느라 까다로웠던 것 같아요.
서비스 크기가 크지 않다면 문제 없지만 점점 커질 수록 다루기 번거롭더라구요. 다만 데이터 대량 적재를 해야했던 상황에서 활용하기 편했던 적도 있었던 것 같아요.
어떤 상황에 테스트 더미 데이터를 적재하냐에 따라서 방법이 달라지는 것 같네요 👍
| @@ -0,0 +1,42 @@ | |||
| #!/bin/bash | |||
|
|
|||
There was a problem hiding this comment.
현진님이 작성해주신 배포스크립트를 활용하면 어떻게 배포할 수 있나요?
즉, 이 배포 스크립트 실행 전에 해야할 사전 작업이 있나요?
There was a problem hiding this comment.
일단 EC2 기준으로 기본 사전 작업에 대해 생각해보면
- 서버에 jdk 설치 ( 버전이 맞도록 설치)
2.git pull origin main을 사용하므로 git 설치도 해야합니다!
There was a problem hiding this comment.
그렇군요! 👍👍👍
몇 가지 더 사전작업이 필요할 것 같아요.
- Git 저장소 존재 여부 확인 및 클론
- PID 파일 경로 설정 확인
등이 더 필요할 것 같아보여요.
배포스크립트 목적에 따라 사전 작업 부분들까지 스크립트에 추가해도 좋을 것 같네요!
| @SQLDelete(sql = "UPDATE theme SET deleted = true WHERE id = ?") | ||
| @Where(clause = "deleted = false") |
There was a problem hiding this comment.
위 어노테이션은 soft delete에서 파생된 로직입니다!
만약 위 어노테이션이 없다면 항상 데이터의 값을 사용할 때 deleted의 필드값을 확인하여 검증하는 로직이 필요하지만 위 어노테이션으로 애초에 불러 올 때 false인 것 만 가져오게 해보았습니다!
| public MemberService(MemberDao memberDao) { | ||
| this.memberDao = memberDao; | ||
| public MemberService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) { | ||
| this.memberRepository = memberRepository; |
There was a problem hiding this comment.
Dao를 사용하시다 jpa를 사용해주셨군요! 어떤 차이점이 있으셨나요?
각각의 장단점을 느끼셨나요? 저도 궁금해요!
There was a problem hiding this comment.
일단 dao를 사용하면 순수 쿼리문(?)을 사용해야하므로 문법적이 부분이 계속 찾아보게 되서 조금 힘들었지만 spring data jpa는 명시적인 이름만으로 퀴리문을 자동으로 날려 준다는 것이 편했던 것 같습니다!
dao의 장점으로는 뭔가 떠오르는게 없는데 성능적인 측면에서 아마도 spring data jpa 보단 빠르게 작동하지 않을까 합니다!
솔직하게는 spring data jpa가 너무 편하다고 느껴지는 미션이였습니다!
There was a problem hiding this comment.
저도 JPA 처음 사용할 때 너무 편하게 잘 사용했던 것 같아요.
이미 고려해주신 N+1 문제 등 처럼 JPA를 사용하면서 주의해야할 부분들을 점차 공부해가시다 보면 더 잘 활용하실 수 있을 것 같아요!
|
|
||
| List<Reservation> findByDateAndThemeId(String date, Long themeId); | ||
|
|
||
| @Query("SELECT r FROM Reservation r JOIN FETCH r.time JOIN FETCH r.theme WHERE r.member.id = :memberId") |
There was a problem hiding this comment.
해당 메서드에서 JOIN FETCH를 사용한 이유는 연관된 엔티티 조회 시 발생하는 N+1 문제를 방지하기 위함입니다!!
-> Reservation 조회 시 Time과 Theme 엔티티를 별도의 쿼리로 조회하지 않고, 단일 쿼리 내에서 조인하여 한 번에 가져옴으로써 오버헤드를 줄였습니다.
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) |
There was a problem hiding this comment.
DEFINED_PORT를 사용하면 어떤 문제가 생길 수 있을까요?
There was a problem hiding this comment.
-
포트 충돌 및 빌드 실패 (Port Collision)
로컬 개발 환경에서 이미 실제 애플리케이션이 8080 포트를 사용 중일 경우 테스트가 실행되지 않습니다. -
병렬 테스트 실행의 제약 (Parallel Execution)
포트가 고정되어 있으면 한 번에 단 하나의 테스트 세트만 실행 가능합니다. -
테스트의 격리성 저해입니다.
모든 테스트가 고정된 포트를 공유하므로, 이전 테스트의 네트워크 자원 정리가 미흡할 경우 다음 테스트에 영향을 줄 수 있습니다.
위 3가지 문제가 생길 수 있을 것 같습니다!!
해결법 : RANDOM_PORT를 사용하자!!
@LocalServerPort int port; 을 사용하면 어디 포트를 사용하는지 파악이 된다고 합니다!!
supernovaMK
left a comment
There was a problem hiding this comment.
리뷰 남긴 부분에 대해서 의견 주신 것 확인하였습니다.
이번 미션에서 중요해보이는 부분에 대해서 더 크게 리뷰할 부분은 없는 것 같아요.
JPA,JWT를 점점 사용하시면서 주의할 점들을 공부하시면 더 좋을 것 같아요.
더 궁금한 부분이 있거나 같이 이야기해보고 싶은 부분이 있다면 언제나 편하게 연락주세요!
| @@ -0,0 +1,42 @@ | |||
| #!/bin/bash | |||
|
|
|||
There was a problem hiding this comment.
그렇군요! 👍👍👍
몇 가지 더 사전작업이 필요할 것 같아요.
- Git 저장소 존재 여부 확인 및 클론
- PID 파일 경로 설정 확인
등이 더 필요할 것 같아보여요.
배포스크립트 목적에 따라 사전 작업 부분들까지 스크립트에 추가해도 좋을 것 같네요!
| import roomescape.time.TimeRepository; | ||
|
|
||
| @Component | ||
| public class ProductionDataLoader implements CommandLineRunner { |
There was a problem hiding this comment.
오호 그렇군요~ 차이를 공유해주셔서 감사합니다.
제가 경험했을 때는 sql문을 사용하다보면 테스트가 변경될 때마다 매번 복잡한 쿼리문을 작성하느라 까다로웠던 것 같아요.
서비스 크기가 크지 않다면 문제 없지만 점점 커질 수록 다루기 번거롭더라구요. 다만 데이터 대량 적재를 해야했던 상황에서 활용하기 편했던 적도 있었던 것 같아요.
어떤 상황에 테스트 더미 데이터를 적재하냐에 따라서 방법이 달라지는 것 같네요 👍
| throw new AuthenticationException("인증되지 않은 사용자입니다."); | ||
| } | ||
|
|
||
| return memberService.findByToken(token); |
There was a problem hiding this comment.
오호 그렇군요~
memeber 엔티티를 다루는 로직이 여러 곳에 흩어지는 것을 주의해주셨군요~
- Token에 관련된 로직이 memberService에서의 책임일까?
- AuthService에서 꼭 Member 객체를 반환해야할까?
- Service가 Service를 참조할 때 나중에 문제가 생기지는 않을까?
등등을 같이 고민해보면 좋을 것 같습니다!
현재 미션에서는 이미 충분히 잘 구현해주셨지만, 더 고민해보면 좋을 지점인 것 같아서요!
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| public class JwtConfig { |
There was a problem hiding this comment.
그렇군요!
빈으로 관리하였을 때의 장점을 느끼셨군요!
@component 어노테이션을 통해서도 빈으로 등록하여도 빈으로 등록되는 것은 같을 것 같아요.
@configuration을 사용하면 저는 어떤 구현체로 bean을 등록하여 사용할지 configuration 클래스에서 설정할 수 있어서 편하다고 느꼈어요! 현재 서비스에서는 jwtProvider 구현체가 1개이지만 실제에서는 더 많이 변경되고 생겨날 수 있을 것 같네요.
더 자주 사용해보시고 편리한점이 더 있으셨다면 공유해주세요!
| public MemberService(MemberDao memberDao) { | ||
| this.memberDao = memberDao; | ||
| public MemberService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) { | ||
| this.memberRepository = memberRepository; |
There was a problem hiding this comment.
저도 JPA 처음 사용할 때 너무 편하게 잘 사용했던 것 같아요.
이미 고려해주신 N+1 문제 등 처럼 JPA를 사용하면서 주의해야할 부분들을 점차 공부해가시다 보면 더 잘 활용하실 수 있을 것 같아요!
| security.jwt.token.secret-key=thisismysecretkeyandgreedyzzangandsoftwarefighting | ||
| security.jwt.token.expire-length=3600000 |
There was a problem hiding this comment.
앗 제가 의도를 잘못 전달드렸나봐요.
만약 applicaiton-key.properties를 .gitignore하게 된다면 팀원들도 github에서 확인할 수 없을 것 같아요.
비밀로 유지해야하는 secret key값을 팀원들과 어떻게 공유하고 관리할 수 있을지에 대한 질문이었어요!
메모장에서 써서 공유하기,.env 파일 활용하기, git submodule 활용하기 와 같은 방식들을 한번 살펴보시면 좋을 것 같아요!
사용해보시면서 각각의 장단점을 생각해보다보면 더 좋을 것 같아요~
안녕하세요 승현님!!! 로또 미션 이후로 리뷰어님으로 오랜만에 뵙는 것 같은데 벌써 마지막 미션이라는게 정말 시간이 빠른 것 같습니다 ^^ 잘 부탁드립니다!!!
미션 요구사항 및 해결방법
auth패키지를 만들어서JwtTokenProvider를 위치 시켰습니다! 또한 빈 등록을 위해jwtConfig를 이용하여 빈으로 등록하였습니다!CommandLineRunner을 이용하여 dataLoader를 만들어 보았습니다!DataLoader에서는 사용자 정보만 초기화TestDataLoader에서는 테스트에 필요한 사전 값 초기화 ->@ActiveProfile을 이용해서 테스트 코드시 사용해보았습니다!application-secret을 통해 키 값과 만료 시간을 분리해보았습니다!deploy.sh에