Skip to content

[그리디] 서현진 Spring Core 배포 7, 8, 9 단계 미션 제출합니다.#211

Open
nonactress wants to merge 54 commits intonext-step:nonactressfrom
nonactress:hyeonjin5
Open

[그리디] 서현진 Spring Core 배포 7, 8, 9 단계 미션 제출합니다.#211
nonactress wants to merge 54 commits intonext-step:nonactressfrom
nonactress:hyeonjin5

Conversation

@nonactress
Copy link
Copy Markdown

@nonactress nonactress commented Jan 15, 2026

안녕하세요 승현님!!! 로또 미션 이후로 리뷰어님으로 오랜만에 뵙는 것 같은데 벌써 마지막 미션이라는게 정말 시간이 빠른 것 같습니다 ^^ 잘 부탁드립니다!!!

미션 요구사항 및 해결방법

  1. JWT 관련 로직을 roomescape와 같은 계층의 auth 패키지의 클래스로 분리하고 불필요한 DB 접근을 최소화 하세요.
  • auth 패키지를 만들어서 JwtTokenProvider 를 위치 시켰습니다! 또한 빈 등록을 위해 jwtConfig 를 이용하여 빈으로 등록하였습니다!
  1. schema.sql 대신 데이터베이스를 초기화 해주기 위해 실행하는 클래스를 만드세요. 스프링이 실행될 때 동작해야 합니다.token 생성에 필요한 비밀키값을 외부 파일로 분리하세요.
  • CommandLineRunner 을 이용하여 dataLoader를 만들어 보았습니다!
  • DataLoader 에서는 사용자 정보만 초기화
  • TestDataLoader 에서는 테스트에 필요한 사전 값 초기화 -> @ActiveProfile 을 이용해서 테스트 코드시 사용해보았습니다!
  • application-secret 을 통해 키 값과 만료 시간을 분리해보았습니다!
  1. ec2나 서버에서 배포를 할 수 있게 배포 스크립트를 작성하세요.
  • deploy.sh
    1. 저장소에서 최신 코드를 pull 받음
    1. 프로젝트 빌드
    1. 기존에 실행 중인 애플리케이션의 PID를 찾아 종료
    1. 빌드된 애플리케이션 실행
  • 순서대로 적어보았습니다!

1) 모든 도메인 엔티티로 변경
2) TimeRepository 퀴리문 변경
3) DataInitializer로 초기값 설정
@nonactress nonactress changed the title [그리디] 서현진 Spring 배포 7, 8, 9 단계 미션 제출합니다. [그리디] 서현진 Spring Core 배포 7, 8, 9 단계 미션 제출합니다. Jan 15, 2026
Copy link
Copy Markdown

@supernovaMK supernovaMK 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 +1 to +2
security.jwt.token.secret-key=thisismysecretkeyandgreedyzzangandsoftwarefighting
security.jwt.token.expire-length=3600000
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

public repo를 사용하는 상황에서 secret key를 어떻게 공유하고 관리할 수 있을까요?

Copy link
Copy Markdown
Author

@nonactress nonactress Jan 22, 2026

Choose a reason for hiding this comment

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

찾아 보니 크게 2가지 방법이 있는 것 같습니다!!


1. 환경 변수 사용

코드에는 변수명만 적고, 실제 값은 서버 시스템 환경 변수로 등록하는 것입니다.

예시 (파일 : application.yml)
api: key: ${API_KEY_VALUE}

2. 실행 시 인자 전달

애플리케이션을 실행할 때 직접 값을 주입합니다.

java -jar app.jar --api.key=your-secret-key-123

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

앗 제가 의도를 잘못 전달드렸나봐요.

만약 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());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

토큰이 만료가 되었는지 따로 검사하시는 이유가 있나요?

parseClaimsJws과정에서 만료 검사를 하는 것으로 알고 있어서요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

parseClaimsJws 가 토큰의 만료 시간을 검사 해주는 기능도 있는 지 몰랐습니다!! 이와 관련해서 만약 토큰이 만료 기한이 지났다면 ExpiredJwtException 를 던지는 것을 찾아보았습니다! 이를 반영하여 만료시간 예외 처리하도록 반영해보겠습니다!

반영 커밋 : c5bdf60

throw new AuthenticationException("인증되지 않은 사용자입니다.");
}

return memberService.findByToken(token);
Copy link
Copy Markdown

@supernovaMK supernovaMK Jan 21, 2026

Choose a reason for hiding this comment

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

MemberService 를 참조하는 이유가 무엇인가요?

현진님이 정의하신 AuthService의 책임이 궁금해요!
AuthService에서 repository에 접근하여 member객체를 찾아왔을 수 도 있었어서요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

AuthService의 책임


AuthService는 요청에서 토큰을 추출하고 그 토큰이 유효한지 검증하는 인증 프로세스라고 생각합니다!
반면, 특정 토큰을 가진 회원을 찾는 구체적인 로직(조회 방식, 예외 처리 등)은 Member 도메인을 관리하는 MemberService의 책임이라고 생각했습니다!!

AuthService에서 repository에 접근하여 member객체를 찾아왔을 수 도 있었어서요!


말씀하신 대로 AuthService 에서 직접 레포지토리에 접근할 수도 있지만, 그렇게 되면 Member 엔티티를 다루는 로직이 여러 서비스로 흩어지게 되어 나중에 Member 조회 로직이 변경될 때 유지 보수가 어려울 것 같아서 서비스를 사용해 보았습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

오호 그렇군요~

memeber 엔티티를 다루는 로직이 여러 곳에 흩어지는 것을 주의해주셨군요~

  • Token에 관련된 로직이 memberService에서의 책임일까?
  • AuthService에서 꼭 Member 객체를 반환해야할까?
  • Service가 Service를 참조할 때 나중에 문제가 생기지는 않을까?

등등을 같이 고민해보면 좋을 것 같습니다!

현재 미션에서는 이미 충분히 잘 구현해주셨지만, 더 고민해보면 좋을 지점인 것 같아서요!

import org.springframework.context.annotation.Configuration;

@Configuration
public class JwtConfig {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Config 방식을 사용해보니 어떤 점이 좋았나요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

제가 만든 클래스를 빈으로 등록시켜 간편하게 매번 사용할 때 마다 생성하지 않아도 되서 편했던 것 같습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

그렇군요!

빈으로 관리하였을 때의 장점을 느끼셨군요!
@component 어노테이션을 통해서도 빈으로 등록하여도 빈으로 등록되는 것은 같을 것 같아요.

@configuration을 사용하면 저는 어떤 구현체로 bean을 등록하여 사용할지 configuration 클래스에서 설정할 수 있어서 편하다고 느꼈어요! 현재 서비스에서는 jwtProvider 구현체가 1개이지만 실제에서는 더 많이 변경되고 생겨날 수 있을 것 같네요.

더 자주 사용해보시고 편리한점이 더 있으셨다면 공유해주세요!

import roomescape.time.TimeRepository;

@Component
public class ProductionDataLoader implements CommandLineRunner {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

.sql 파일을 활용하는 대신 CommandLineRunner를 활용했을 때 어떤 차이가 있었나요?

장단점은 있을까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

sql


장점 - 퀴리 문으로 작동해서 매우 빠르다
단점 - 퀴리 문법을 알아야 한다....

CommandLineRunner


장점 - 자바 로직으로 데이터를 만들 수 있다, 복잡한 엔티티 연관관계 보다 쉽게 만들 수 있다!
단점 - 속도가 느리다....

차이는 속도적인 차이가 존재하고 sql 엔티티 연관관계 구조를 매번 적용 시켜줘야 한다는 단점이 있는 거 같습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

오호 그렇군요~ 차이를 공유해주셔서 감사합니다.

제가 경험했을 때는 sql문을 사용하다보면 테스트가 변경될 때마다 매번 복잡한 쿼리문을 작성하느라 까다로웠던 것 같아요.
서비스 크기가 크지 않다면 문제 없지만 점점 커질 수록 다루기 번거롭더라구요. 다만 데이터 대량 적재를 해야했던 상황에서 활용하기 편했던 적도 있었던 것 같아요.

어떤 상황에 테스트 더미 데이터를 적재하냐에 따라서 방법이 달라지는 것 같네요 👍

@@ -0,0 +1,42 @@
#!/bin/bash

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

현진님이 작성해주신 배포스크립트를 활용하면 어떻게 배포할 수 있나요?

즉, 이 배포 스크립트 실행 전에 해야할 사전 작업이 있나요?

Copy link
Copy Markdown
Author

@nonactress nonactress Jan 23, 2026

Choose a reason for hiding this comment

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

일단 EC2 기준으로 기본 사전 작업에 대해 생각해보면

  1. 서버에 jdk 설치 ( 버전이 맞도록 설치)
    2.git pull origin main 을 사용하므로 git 설치도 해야합니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

그렇군요! 👍👍👍

몇 가지 더 사전작업이 필요할 것 같아요.

  • Git 저장소 존재 여부 확인 및 클론
  • PID 파일 경로 설정 확인

등이 더 필요할 것 같아보여요.

배포스크립트 목적에 따라 사전 작업 부분들까지 스크립트에 추가해도 좋을 것 같네요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

아아 넵 위 두가지 사전작업도 기억해놓겠습니다!!

Comment on lines +11 to +12
@SQLDelete(sql = "UPDATE theme SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

이 두 어노테이션은 왜 사용하나요?😃

Copy link
Copy Markdown
Author

@nonactress nonactress Jan 23, 2026

Choose a reason for hiding this comment

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

위 어노테이션은 soft delete에서 파생된 로직입니다!
만약 위 어노테이션이 없다면 항상 데이터의 값을 사용할 때 deleted의 필드값을 확인하여 검증하는 로직이 필요하지만 위 어노테이션으로 애초에 불러 올 때 false인 것 만 가져오게 해보았습니다!

public MemberService(MemberDao memberDao) {
this.memberDao = memberDao;
public MemberService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) {
this.memberRepository = memberRepository;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Dao를 사용하시다 jpa를 사용해주셨군요! 어떤 차이점이 있으셨나요?

각각의 장단점을 느끼셨나요? 저도 궁금해요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

일단 dao를 사용하면 순수 쿼리문(?)을 사용해야하므로 문법적이 부분이 계속 찾아보게 되서 조금 힘들었지만 spring data jpa는 명시적인 이름만으로 퀴리문을 자동으로 날려 준다는 것이 편했던 것 같습니다!
dao의 장점으로는 뭔가 떠오르는게 없는데 성능적인 측면에서 아마도 spring data jpa 보단 빠르게 작동하지 않을까 합니다!

솔직하게는 spring data jpa가 너무 편하다고 느껴지는 미션이였습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

저도 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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JOIN FETCH를 써주신 이유가 궁금해요!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

해당 메서드에서 JOIN FETCH를 사용한 이유는 연관된 엔티티 조회 시 발생하는 N+1 문제를 방지하기 위함입니다!!

-> Reservation 조회 시 TimeTheme 엔티티를 별도의 쿼리로 조회하지 않고, 단일 쿼리 내에서 조인하여 한 번에 가져옴으로써 오버헤드를 줄였습니다.


import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

DEFINED_PORT를 사용하면 어떤 문제가 생길 수 있을까요?

Copy link
Copy Markdown
Author

@nonactress nonactress Jan 23, 2026

Choose a reason for hiding this comment

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

  1. 포트 충돌 및 빌드 실패 (Port Collision)
    로컬 개발 환경에서 이미 실제 애플리케이션이 8080 포트를 사용 중일 경우 테스트가 실행되지 않습니다.

  2. 병렬 테스트 실행의 제약 (Parallel Execution)
    포트가 고정되어 있으면 한 번에 단 하나의 테스트 세트만 실행 가능합니다.

  3. 테스트의 격리성 저해입니다.
    모든 테스트가 고정된 포트를 공유하므로, 이전 테스트의 네트워크 자원 정리가 미흡할 경우 다음 테스트에 영향을 줄 수 있습니다.

위 3가지 문제가 생길 수 있을 것 같습니다!!

해결법 : RANDOM_PORT를 사용하자!!
@LocalServerPort int port; 을 사용하면 어디 포트를 사용하는지 파악이 된다고 합니다!!

Copy link
Copy Markdown

@supernovaMK supernovaMK left a comment

Choose a reason for hiding this comment

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

리뷰 남긴 부분에 대해서 의견 주신 것 확인하였습니다.

이번 미션에서 중요해보이는 부분에 대해서 더 크게 리뷰할 부분은 없는 것 같아요.
JPA,JWT를 점점 사용하시면서 주의할 점들을 공부하시면 더 좋을 것 같아요.

더 궁금한 부분이 있거나 같이 이야기해보고 싶은 부분이 있다면 언제나 편하게 연락주세요!

@@ -0,0 +1,42 @@
#!/bin/bash

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

그렇군요! 👍👍👍

몇 가지 더 사전작업이 필요할 것 같아요.

  • Git 저장소 존재 여부 확인 및 클론
  • PID 파일 경로 설정 확인

등이 더 필요할 것 같아보여요.

배포스크립트 목적에 따라 사전 작업 부분들까지 스크립트에 추가해도 좋을 것 같네요!

import roomescape.time.TimeRepository;

@Component
public class ProductionDataLoader implements CommandLineRunner {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

오호 그렇군요~ 차이를 공유해주셔서 감사합니다.

제가 경험했을 때는 sql문을 사용하다보면 테스트가 변경될 때마다 매번 복잡한 쿼리문을 작성하느라 까다로웠던 것 같아요.
서비스 크기가 크지 않다면 문제 없지만 점점 커질 수록 다루기 번거롭더라구요. 다만 데이터 대량 적재를 해야했던 상황에서 활용하기 편했던 적도 있었던 것 같아요.

어떤 상황에 테스트 더미 데이터를 적재하냐에 따라서 방법이 달라지는 것 같네요 👍

throw new AuthenticationException("인증되지 않은 사용자입니다.");
}

return memberService.findByToken(token);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

오호 그렇군요~

memeber 엔티티를 다루는 로직이 여러 곳에 흩어지는 것을 주의해주셨군요~

  • Token에 관련된 로직이 memberService에서의 책임일까?
  • AuthService에서 꼭 Member 객체를 반환해야할까?
  • Service가 Service를 참조할 때 나중에 문제가 생기지는 않을까?

등등을 같이 고민해보면 좋을 것 같습니다!

현재 미션에서는 이미 충분히 잘 구현해주셨지만, 더 고민해보면 좋을 지점인 것 같아서요!

import org.springframework.context.annotation.Configuration;

@Configuration
public class JwtConfig {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

그렇군요!

빈으로 관리하였을 때의 장점을 느끼셨군요!
@component 어노테이션을 통해서도 빈으로 등록하여도 빈으로 등록되는 것은 같을 것 같아요.

@configuration을 사용하면 저는 어떤 구현체로 bean을 등록하여 사용할지 configuration 클래스에서 설정할 수 있어서 편하다고 느꼈어요! 현재 서비스에서는 jwtProvider 구현체가 1개이지만 실제에서는 더 많이 변경되고 생겨날 수 있을 것 같네요.

더 자주 사용해보시고 편리한점이 더 있으셨다면 공유해주세요!

public MemberService(MemberDao memberDao) {
this.memberDao = memberDao;
public MemberService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) {
this.memberRepository = memberRepository;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

저도 JPA 처음 사용할 때 너무 편하게 잘 사용했던 것 같아요.

이미 고려해주신 N+1 문제 등 처럼 JPA를 사용하면서 주의해야할 부분들을 점차 공부해가시다 보면 더 잘 활용하실 수 있을 것 같아요!

Comment on lines +1 to +2
security.jwt.token.secret-key=thisismysecretkeyandgreedyzzangandsoftwarefighting
security.jwt.token.expire-length=3600000
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

앗 제가 의도를 잘못 전달드렸나봐요.

만약 applicaiton-key.properties를 .gitignore하게 된다면 팀원들도 github에서 확인할 수 없을 것 같아요.

비밀로 유지해야하는 secret key값을 팀원들과 어떻게 공유하고 관리할 수 있을지에 대한 질문이었어요!

메모장에서 써서 공유하기,.env 파일 활용하기, git submodule 활용하기 와 같은 방식들을 한번 살펴보시면 좋을 것 같아요!

사용해보시면서 각각의 장단점을 생각해보다보면 더 좋을 것 같아요~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants