이번에 졸업 작품을 하면서 2명이 백앤드 개발를 맡기로 하였고, 개발 속도를 빠르게 하기 위해 각각 회원 부분과 게시글 부분을 나누어서 작업을 하기로 하였다.
나는 게시글 부분을 맡고, 다른 분은 회원 부분을 맡았다.
게시글 작성 api 를 구현하던 도중 문제가 발생했다.
우리 프로젝트에서는 유저가 게시글을 작성을 해야하는데, 아직 유저의 로그인 부분과 회원 가입 부분에 대한 개발이 완료가 되지 않았다.
처음 들었던 생각은 유저의 로그인, 회원 가입
에 대한 개발을 기다렸다가 게시글 작성 api
를 구현하면 된다고 생각했다. 하지만 그러기에는 시간적인 여유
가 없었고, 다른 해결책이 있지 않을까 고민을 하였다.
인증/인가 로직
이 완성되면 코드 리팩토링인증/인가 로직
이 완성 되면 컨트롤러(@Login
및 각 메서드에 memberId 파라미터 추가
)의 변경이 일어난다. 명세된 api에 맞는 개발이 아니기 때문에 프런트 개발자와 혼동이 생길 수 있음.@Login
을 미리 구현을 해두고, 이를 처리하는 ArgumentResolver
를 구현, 이때 구현한 ArgumentResovler
는 특정 회원에 대한 id
값을 무조건 반환하게함. 후에 인증/인가 로직
완성 후 나중에 리팩토링 시, 컨트롤러에 대한 변경이 일어나지 않아도 됨
. 또한 개발단계에서 사전에 명세된 api
로 테스트를 바로 할 수 있음정리하자면
@Login
구현 및 ArgumentResolver
를 미리 구현해야하지만, 후에 리팩토링 시 구현해두었던 ArgumentResolver 에 대한 변경만 일어남.나는 2번 방법으로 해결하였다. 나중에 변경될 범주가 적이 때문에 합리적이였고, 인증/인가
에 대한 작업을 하려면 ArgumentResovler
에 대한 변경은 당연히 일어나야했다.
따라서 미리 만들어둔 @Login
과 ArgumentResovler
를 통해 무조건 인가 처리가 된 사용자가 들어온다는 가정 하에 개발을 진행 했다.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}
@Component
@RequiredArgsConstructor
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.hasParameterAnnotation(Login.class); //@Login 이 컨트롤러에 있다면 무조건 요청을 허용하도록 하였다.
}
@Override
public Object resolveArgument(final MethodParameter parameter, final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) {
return 1L; // 이 부분을 통해 인가를 거치지 않는다.
// 모든 요청에 대해 pk 가 1L인 유저에 대한 요청이라고 처리를 하도록 해두었다.
}
}
위와 같이 모든 @Login 을 통해 인자를 받는 모든 api 요청에서 1L 이라는 값을 내려주도록 하였다.
팀원들과 회원의 식별에 대한 처리는 회원의 pk 값을 기준으로 하기로 했기 때문에 1L 을 내려주도록 함.
이후 BoardController 를 다음과 같이 작성하였다.
@Slf4j
@RequestMapping("/api/boards")
@RestController
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
@PostMapping
public ResponseEntity<Void> write(@Login Long memberId, @RequestBody final BoardCreateRequest boardCreateRequest) {
log.info("memberId = {} 의 새글작성", memberId);
boardService.write(memberId, boardCreateRequest);
return ResponseEntity.status(HttpStatus.CREATED)
.build();
}
}
만약 인증/인가 부분이 개발이 완료 된다면 해당 부분만 리팩토링 해주면 될 것이다.
이상 끝.
이 아니다.
위 게시물에서 인증/인가에 대한 처리를 할 때, LoginMemberArgumentReslover
를 통해서 1L
을 디폴트로 반환을 해두었다.
따라서 해당 값을 사용하려면 (실제로 api 를 쏴서 테스트를 할 때) 데이터베이스에 Pk
가 1L
인 Member
를 생성해주어야만 했다.
그래서 프로젝트 빌드 시 1L
라는 pk
를 가진 Member
객체를 자동으로 등록해두기로 했고, 다음과 같이 해결 하였다.
데이터베이스에 Member 테이블에 pk 가 1L 인 테스트용 유저를 만들어 두기 위해 스크립트를 작성 하였다.
//data.sql
INSERT INTO member (EMAIL, ALIAS) VALUES ('test@email.com','테스트유저')
작성한 스크립트는 resources
패키지 하단에 data.sql
라는 이름으로 생성 해두었다.
sql 스크립트
를 작성만 해서는 프로젝트 빌드 시, 해당 sql 스크립트가 자동
으로 실행되지 않는다.
application.yml
파일에 해당 스크립트를 자동으로 실행 하도록 설정을 해주어야했디.
Spring Boot Reference Documentation
공식문서를 조금 확인해보았는데, 기본적으로 스프링 부트에서 sql 스크립트를 등록하며면 스키마 형식
(테이블 삭제 생성 등)은 schema.sql
, sql 문
은 data.sql
로 파일명을 지정해둬야 작동한다고 한다.
동작 순서는 schema.sql
→ data.sql
이다
spring:
jpa:
defer-datasource-initialization: true
sql:
init:
mode: always // 항상 해당 스크립트를 실행함. always 말고도 EMBEDDED, NEVER 설정이 있음
data-locations: // 실행할 스크립트의 경로를 설정, 한번에 여러 sql 문 스크립트 등록 가능.
- classpath:data.sql
❓ EMBEDDED : (내장 데이터 베이스 H2 실행 시 동작하는 것으로 추측)
✅ `다음과 같은 형태로 등록 가능`
data-locations :
- data.sql
- data2.sql
Hibernate 초기화 이전에 sql 스크립트를 실행할지를 설정한다. true 인 경우 하이버네이트 초기화 전에 쿼리가 실행되면서 데이터를 생성할 수 있다.
스크립트 기반 DataSource 초기화가 Hibernate에 의해 수행된 스키마 생성을 기반으로 구축될 수 있도록 하려면 true 로 설정해야한다.
나는 schema.sql
을 따로 설정하지 않았고, 자동으로 hibernate.ddl-auto: create
세팅을 유지 한 채 data.sql
을 실행하기 위해 설정하였다.