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

[Spring 지하철 경로 조회 - 1, 2단계] 검프(김태정) 미션 제출합니다. #100

Merged
merged 29 commits into from
May 19, 2021

Conversation

Livenow14
Copy link

안녕하세요 데이브..! 처음뵙겠습니다. 잘부탁드립니다.

vue.js는 처음이네요.. 프론트 코드를 이해하는데도 꽤나 시간이 걸린거 같아요 😂

백엔드 코드는 기본적인 스켈레톤 코드가 작성되어 있어서 수월했어요.

주로 작성한 부분은.

전역적 ExceptionHandler 처리.

레이어별 테스트 등입니다.

피드백을 통해 성장하겠습니다..! 많은 피드백 기다리겠습니다 감사합니다

@Livenow14
Copy link
Author

검프 학습로그

관계설정

클래스 사이의 관계는 코드에 다른 클래스 이름이 나타나기 때문에 만들어짐.

하지만 객체 사이의 관계는 그렇지 않음. 코드에서 A라는 클래스를 전혀 알지 못하더라도 A 클래스가 구현한 인터페이스를 사용했다면, A 클래스의 객체를 인스턴스 타입으로 받아서 사용할 수 있음.

이는 다형성이라는 특징 덕분임.

즉. 객체 서로가 구현에 의존하는 것이 아닌 역할에 의존한다면, 역할을 구현하는 방법이 바뀌더라도, 상대 객체에는 아무런 영향을 주지 않음.

또한, 객체를 사용하는 클라이언트를 변경하지 않아도, 새로운 기능을 제공할 수 있다는 장점을 얻을 수 잇음.

대체가능성과 확장성을 얻음.

제어의 역전

서비스를 2개의 영역으로 나눠보면 사용 영역, 구성 영역으로 나눌 수 있어요.

제어가 역전됐다는 것은 프로그램의 제어 흐름 구조가 뒤 바뀐 것이라 할 수 있어요.

일반적인 프로그램은 main() 메소드와 같이 프로그램이 시작되는 지점에서 다음에 사용할 객체를 결정하고, 결정할 객체를 생성하고, 만들어진 객체에 있는 메소드를 호출하고 등의 작업이 반복되요.

즉, 모든 종류의 작업을 사용 영역에서 제어하는 구조에요.

제어의 역전이란 이런 제어 흐름의 개념을 거꾸로 뒤집는 것이에요.

제어의 역전에서는 객체가 자신이 사용할 오브젝트를 스스로 선택하지 않고, 생성하지도 않아요. 또한 자신이 어떻게 만들어지고 어디서 사용되는지를 알 수 없어요. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이에요.

즉, 모든 종류의 작업을 구성 영역에서 제어하는 구조에요.

라이브러리와 프레임워크의 차이가 여기서 나요

라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어해요. 단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이에요.

반면에 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용돼요. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이에요

프레임워크에는 분명한 제어의 역전(IOC) 개념이 적용되어야해요

즉, 애플리케이션 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야해요.

금방 작성했던 코드도 IoC를 적용한 것이에요.

# 구성영역
		public class DaoFactory {

    public UserDao userDao() {
        return new UserDao(connections());
    }

    public AccountDao accountDao() {
        return new AccountDao(connections());
    }

    public MessageDao messageDao() {
        return new MessageDao(connections());
    }

    private Connections connections() {
        return new DConnections();
    }
}
# 사용영역
public class UserDaoTest {
    public static void main(String[] args) {
        UserDao userDao = new DaoFactory().userDao();
    }
}

@Livenow14 Livenow14 changed the base branch from master to livenow14 May 14, 2021 14:51
Copy link

@dave915 dave915 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 11 to 13
@ExceptionHandler({AuthException.class})
public ResponseEntity<String> authException(RuntimeException exception) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(exception.getMessage());
Copy link

Choose a reason for hiding this comment

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

👍

return ResponseEntity.ok().build();
}

// TODO: 구현 하기
@DeleteMapping("/members/me")
public ResponseEntity<MemberResponse> deleteMemberOfMine() {
public ResponseEntity<MemberResponse> deleteMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
Copy link

Choose a reason for hiding this comment

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

noContent 면 응답을 Void로 해놓아도 좋을 것 같아요 :)

Suggested change
public ResponseEntity<MemberResponse> deleteMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
public ResponseEntity<Void> deleteMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {

Copy link
Author

Choose a reason for hiding this comment

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

body를 포함하지 않는 메소드, 전체 Void를 반환하도록 변경했습니다 :)

f97da8a

// TODO: 유효한 로그인인 경우 LoginMember 만들어서 응답하기
return null;
String token = AuthorizationExtractor.extract(Objects.requireNonNull(webRequest.getNativeRequest(HttpServletRequest.class)));
return authService.findByToken(token);
Copy link

Choose a reason for hiding this comment

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

지금 같은 경우 @AuthenticationPrincipal 어노테이션이 있을때만 로그인 검증을 할 텐데 intercepter 를 사용해서 @AuthenticationPrincipal를 사용하지 않는 api 들도 인증한 사용자인지 체크하도록 해보면 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

@AuthenticationPrincipal을 사용하지 않는 api또한 인증된 사용자인지 체크하도록 변경했어요 😊

@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(authService))
                .addPathPatterns("/**")
                .excludePathPatterns("/login/token", "/members");
    }

여기서 질문이 있어요..!

저는 post의 /members만 inteceptor의 영향을 받고싶지 않은데,

지금 같은경우는 members의 path는 메소드에 상관없이 다 허용하고있어요.

이런경우 어떻게 해야하는걸까요...?

bcf2449

Copy link

Choose a reason for hiding this comment

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

인터셉터는 Path로만 제어 가능 한 것으로 알고 있어요 😭
아래 링크 처럼 인터셉터 동작 후 처리 하던가, 원하지 않는 api의 path를 변경 해야 할 것 같네요.

https://stackoverflow.com/questions/48306597/spring-mvc-interceptor-pattern

body: JSON.stringify(jsonData)
};

const response = await fetch("http://localhost:8080/login/token", option);
Copy link

Choose a reason for hiding this comment

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

http://localhost:8080 같은경우 환경에 따라 달라질 수 있기 때문에 보통 Path로만 요청하고 있어요 :)

Suggested change
const response = await fetch("http://localhost:8080/login/token", option);
const response = await fetch("/login/token", option);

Copy link
Author

Choose a reason for hiding this comment

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

view.config의 수정을 통해, path로 요청할 수 있게 번경했습니다.

const path = require('path');

module.exports = {
  outputDir: path.resolve(__dirname, '../src/main/resources/static/'),
  devServer: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080/',
        ws: true,
        changeOrigin: true,
      },
    },
    historyApiFallback: true,
  },

  transpileDependencies: [
    'vuetify'
  ]
}

또한 기본 path에 /api를 추가했어요.

72d7456

const response = await fetch("http://localhost:8080/login/token", option);
let resJson = await response.json();
const accessToken = resJson.accessToken;
this.$store.commit(SET_ACCESS_TOKEN, accessToken);
Copy link

Choose a reason for hiding this comment

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

오호.. store!
store에 토큰을 저장 할 경우 새로고침하면 store가 초기화 되지 않나요? 🤔

Copy link
Author

Choose a reason for hiding this comment

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

새로고침을 전혀 생각하지 않았네요..!

vuex-persistedstate를 사용하며,
실질적으로 사용하는 member, auth 모듈의 state만 저장되게 번경햇습니다.😊

좋은 피드백 감사합니다!

72d7456


@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({AuthException.class})
Copy link

Choose a reason for hiding this comment

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

지금 미션과 상관은 없지만 Exception.class 를 처리하는 handler도 만들어보면 좋을 것 같아요 :)

Copy link
Author

Choose a reason for hiding this comment

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

사용자 잘못이 아닌, 서버 자체의 예외 처리를 말씀하시는 거죠..?

우선 이번 서비스에서 중요 로직을 담당하는 데이터베이스의 예외를 잡기위해

DataAccesException으로 서버 내부 예외에 대한 처리를 완료했어요. 😊

@ExceptionHandler({DataAccessException.class})
    public ResponseEntity<String> databaseException(RuntimeException exception) {
        logger.info(exception.getMessage());
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(exception.getMessage());
    }

bf54781

Copy link

Choose a reason for hiding this comment

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

DataAccesException.class 이외의 에러가 발생했을때는 어떻게 해야할까요?
보통 @ExceptionHandler({Exception.class}) 을 만들어서 개발자가 예상하지 못한 에러가 발생시 처리해주고 있어요 :)

@PutMapping("/members/me")
public ResponseEntity<MemberResponse> updateMemberOfMine() {
public ResponseEntity<TokenResponse> updateMemberOfMine(@AuthenticationPrincipal LoginMember loginMember, @RequestBody MemberRequest request ) {
Copy link

Choose a reason for hiding this comment

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

@Valid 어노테이션을 이용해서 MemberRequest의 유효성 체크도 해보면 좋을 것 같아요 :)

Copy link
Author

Choose a reason for hiding this comment

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

spring starterr의 validatiion의존성을 추가해, 바인딩시 유효셩 검사를 진행했어요.

6c7f050

public void updateMember(Long id, MemberRequest memberRequest) {
memberDao.update(new Member(id, memberRequest.getEmail(), memberRequest.getPassword(), memberRequest.getAge()));
}

public void deleteMember(Long id) {
memberDao.deleteById(id);
}

public Member findMemberByEmail(TokenRequest request) {
Copy link

Choose a reason for hiding this comment

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

TokenRequest 채로 받는게 맞을지 고민되네요 🤔
email로만 Member를 조회하고 login 메소드에서 비밀번호 체크하는건 어떠신가요?

Copy link
Author

Choose a reason for hiding this comment

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

Service와 도메인의 책임을 더 생각해보게 됐어요..!
수정했습니다..! 좋은 피드백 감사합니다.

e03250f

@Livenow14
Copy link
Author

안녕하세요 데이브! 두번째 pr요청입니다..!
세세한 부분들을 깊게 생각해보지 못했던거 같아요!! 피드백을 통해 세세한 부분들을 많이 체크하게 됐어요 ㅎㅎ
좋은 피드백 감사합니다 😊

질문 사항은 👀 를 통해 남겼어요..
이번에도 많은 피드백 부탁드려요..!🙇🏻‍♂️🙇🏻‍♂️🙇🏻‍♂️

Copy link

@dave915 dave915 left a comment

Choose a reason for hiding this comment

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

검프 안녕하세요!
피드백 반영 한 부분 확인했어요 👍
질문 주신 부분에 대해서도 답변 남겼어요. 확인 부탁드릴게요!
이번 단계는 이만 머지할게요 :)

@dave915 dave915 merged commit 08a9293 into woowacourse:livenow14 May 19, 2021
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.

3 participants