[강의]Spring Boot를 이용한 RESTful API 개발
Spring Boot를 이용한 RESTful API 개발 강의 github
이도원님의 인프런 강의(Spring Boot를 이용한 RESTful API 개발) 을 수강하면서 강의 내용을 일부 발췌해 요약한 글.
자바가 겨우 익숙해지고 스프링 그게 뭐지..? 싶을 때 준범 오빠가 추천해 준 인프런 강의이고 강의 목차는 크게 다음과 같이 구성되어 있다.
강의목차
- Spring Boot로 개발하는 RESTful Service
- 스프링 부트 프로젝트 생성 및 HelloWorld Bean과 Controller 추가
- User Service API 구현
- User 도메인 클래스 생성 및 사용자 조회, 등록, 삭제 API를 구현(HTTP의 GET, POST, DELETE method). Http Status 코드 제어
- RESTful Service 기능 확장
- Validation API, 다국어 처리, 데이터 제어를 위한 Filtering, API 버전 관리
- Spring Boot API 사용
- HATEOAS, Swagger, Actuator, Spring Security, Configuration
- Java Persistence API 사용
- JPA를 위한 Entity 설정, JPA를 이용한 사용자 조회, 등록, 삭제 기능 생성, 게시물 관리와 조회를 위한 Post Entity 생성
- RESTful API 설계 가이드
- Richardson Maturity Model 소개, REST API 설계 시 고려해야 할 사항 소개
강의 내용을 전부 정리하기엔 시간이 너무 많이 소모될 것 같고 충분히 잘 정리해둔 좋은 글이 있어서, 강의 자료가 따로 업로드되어 있는 줄 모르고 따로 적어둔 것이나 오류 해결 부분만 간략하게 정리하려고 한다.
applicaition.properties -> application.yml
yml 파일로 사용시
- 데이터를 리스트 해쉬 스칼라 조합으로 적절히 표현 가능
- 문법이 상대적으로 이해하기 쉽고 가독성이 좋음
domain
특정 전문 분야에서 사용되는 업무 지식
의존성 주입(Dependency Injection)
개발자가 직접 서비스에 인스턴스를 생성해 사용하는 것이 아니라 스프링 프레임워크에 의해서 관리되도록 인스턴스를 선언하고 사용해야 한다. (의존성 주입 방식 사용) 스프링에서 선언되어 관리되고 있는 인스턴스를 bean(객체)이라고 부르고 용도에 따라 ControllerBean, ServiceBean, RepositoryBean 등이 있다. 스프링에서 선언된 Bean을 의존성 주입이라는 방법을 통해 관리하는데, 의존성 주입은 XML 설정 파일을 통해서도 가능하고 클래스에서 Setter 함수나 Method나 생성자를 통해서도 가능하다. 스프링 컨테이너에 등록된 빈을 사용하기 위해서는 컨테이너에 등록된 빈의 참조값을 받아와 사용하며, 개발자가 실행 도중에 변경할 수 없으므로 일관성을 유지할 수 있게 된다.
URI
Uniform Resource Identifier의 약자인 URI 뜻은 우리말로 ‘통합 자원 식별자’이다.
- Uniform: 리소스를 식별하는 통일된 방식
- Resource: URI로 식별이 가능한 모든 종류의 자원(웹 브라우저 파일 및 그 이외의 리소스 포함)을 지칭
- Identifier: 다른 항목과 구분하기 위해 필요한 정보
즉, URI는 인터넷상의 리소스 “자원 자체”를 식별하는 고유한 문자열 시퀀스이다.
상태 코드(Status Code)
보통, HTTP Status Code 상태코드는 다음과 같다.
- 2XX: OK
- 4XX: Client 측의 오류
- 5XX: Server 측의 오류
하나의 코드값을 사용하는 것보다는 용도에 맞춰 예외 핸들링을 적절히 조합해 사용하는 것이 좋다!
ex) get - 전체 사용자 조회, 개별 사용자 조회 시 상태코드 200 ok post - 신규 사용자 추가 시 상태코드 200 ok -> 201 created 로 분리
ex) 데이터베이스에 존재하지 않는 데이터를 요청해도 서버 측에서는 프로그램 실행에 이상이 없으므로 상태코드 200으로 전달됨. -> 예외를 발생시켜주어야 한다.
스프링 프레임워크에서 로깅, 로그인, 메세지 추가 등의 모든 비즈니스 로직, 컨트롤러에서 항상 실행시켜야 하는 공통항목은 AOP로 별도의 공통된 로직을 사용한다.
유효성 검증 부분 오류 참고
스프링부트 2.3 부터 Validation Starter 가 Spring Boot Web과 분리가 되면서 동작을 하지 않아서 따로 넣어줘야 한다.
pom.xml의 dependency에 다음 내용추가 후
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
오른쪽 Maven 탭에서 새로고침 버튼을 클릭하면 spring-boot-starter-validation:2.6.0 이 추가 된다. 그리고 @Vaild 에서 import 하면 javax.validation.Valid 가 import 되면서 해결된다.
데이터 제어를 위한 Filtering
클라이언트에게 전달해주고자 하는 정보를 제어하고자 할 때, 스프링 부트 프로젝트에서는 filtering 기능으로 작업을 처리할 수 있다. 예를 들어, 주민번호나 비밀번호 등의 중요한 값을 처리한다고 할 때, 이를 클라이언트가 바로 얻을 수 있게 노출하는 것은 매우 바람직하지 않다. 중요한 부분은 특수문자로 대치해 반환하거나 null로 반환하는 방법도 있지만, 이는 모두 조회 시에 필드를 모두 보여주어야 한다는 점은 동일하다.
-> @JsonIgnore 를 통해 전달하고자 하는 데이터를 제어하거나 @JsonIgnoreProperties(value={“password”, “ssn”}) 과 같이 클래스를 무시하도록 제어할 수 있다.
API 버전 관리
우리가 제공하려는 REST API 자원에 대한 정보뿐만 아니라 현재 API 버전까지 같이 표시해주는 것이 좋다. 또한, API를 사용하는 사용자나 개발자에게 올바른 사용 가이드를 알려주기 위해서 반드시 필요한 작업이다.
버전 관리 방법
URI 이용
- Request Parameter 이용 파라미터로 넘겨주는 방식이며 value는 /로 끝나야 한다. 조회 시에도 파라미터를 넣어주는 것이므로 /?version=1 처럼 넣어주어야 한다.
Header 값 이용 postman에서 header를 넣어주면 버전 별로 조회가 가능하다.
MIME(Multipurpose Internet Mail Extensions) 이메일과 함께 전송되는 메일을 텍스트 문자로 전환해 이메일 서버에 전달하기 위한 방법으로 일정의 파일 형식 지정이다. 마찬가지로 postman에서 header를 통해 버전별 조회가 가능하다. 참고로 문서 타입을 원할 때는 Key 에서 Accept 를 입력한다.
버전 관리는 단순히 사용자에게 보여주는 항목을 제어하는 용도가 아니라 REST API 의 설계가 변경되거나 Application 의 구조가 바뀔 때도 버전을 변경해 사용해야 하며, 사용자에게는 어떤 API 버전을 사용해야 하는지에 대한 적절한 가이드를 명시해주어야 한다. Versioning 에서 고려해야 할 요소는 다음과 같다.
- URI Pollution : URI 가 과도하게 정보를 포함하거나 지저분하는 것을 지양
- Misuse of HTTP Headers : 잘못된 헤더값 사용 주의
- Caching : 인터넷 웹브라우저의 캐시 기능으로 지정한 값이 제대로 반영되지 않는 경우가 있으므로 적절하게 캐시를 삭제하는 것이 좋음
- Can we execute the request on the browser? : API는 적절한 용도에 따라 웹브라우저에서 실행될 수 있어야 함
- API Documentation : get 메소드와 같이 별도의 프로그래밍 없이 웹브라우저에서 바로 사용할 수 있는 적절한 API 개발 문서를 제공해주어야 함 (API에 대한 사전 정보)
Swagger 사용 시 버전 문제
스프링부트 3.XX 버전과 SpringFox의 호환성 문제로 SpringDoc 을 사용해야 했다. 아래는 참고한 링크들이다.
- pom.xml
1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
- application.yml
1
2
3
4
5
6
7
8
9
application.ymlspringdoc:
packages-to-scan: com.example.restfulwebservice
default-consumes-media-type: application/json;charset=UTF-8
default-produces-media-type: application/json;charset=UTF-8
swagger-ui:
path: /
disable-swagger-default-url: true
display-request-duration: true
operations-sorter: alpha
- Config 패키지 생성 후 SwaggerConfig 클래스 생성
HAL Browser를 이용한 HATEOAS 기능 구현
하이퍼 텍스트로 어플리케이션에 부가적인 정보, 기능을 부여하는 것을 의미한다. 즉, 우리가 제공하려는 리소스의 정보를 링크로 추가해 제공하는 것으로, 리소스는 컴퓨터의 자원 ex) 유저 정보, 를 변경, 수정, 추가 등의 작업을 포함하는데 이러한 리소스를 외부에 공개하기 위해 RESTful 서비스를 사용한다. 해당하는 요청 작업에 부가적인 리소스를 연결하기 위해 html의 링크 즉, HyperText 기능을 사용한다.
- pom.xml 강의 자료에 나와있는 artifactId는 hal-browser이나 버전 문제로 hal-explorer를 사용해주어야 한다.
1
2
3
4
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-explorer</artifactId>
</dependency>
Spring Security를 이용한 인증 처리
보안이 중요한 리소스를 일부 인증된 사용자들만 접근 가능하게 하도록 해야 한다. 크게 두가지 방식이 있다.
- JWT 토큰 이용 방식 콘솔의 로그에서 password를 찾아, 조회 시에 authorization에서 Password를 넘겨주어야 오류가 발생하지 않는다.
ID/PW application.yml 파일에서 지정 가능하나 사용자의 id/pw를 고정값으로 yml 파일에 지정하게 되면 이를 변경할 때마다 서버를 재기동해주어야 하므로, 이 방식보다는 LDAP(Lightweight Directory Access Protocol,사용자가 조직, 구성원 등에 대한 데이터를 찾는 데 도움이 되는 프로토콜)이나 데이터베이스 등에서 사용자 정보를 가져오는 방식 등으로 프로그램을 수정하는 것이 필요하다.
- WebSecurityConfigurerAdapter deprecated 되어 참고했다.
JPA 관련 에러
버전 업그레이드에 따른 에러가 발생했고, 참고1 참고2 참고3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.requestMatchers(new AntPathRequestMatcher("/h2-console/**")).permitAll() //h2-console 뒤에 무슨 데이터 파일이 오더라도 통과
.anyRequest().authenticated()
)
.csrf(AbstractHttpConfigurer::disable) //csrf를 사용하지 않음
.headers((headers) -> //header의 프레임에 관련된 값을 사용하지 않음
headers
.defaultsDisabled()
.cacheControl(withDefaults())
.frameOptions(withDefaults()));
return http.build();
}
h2-console 커넥트 이후 접근이 제한되는 문제가 있었다. 다음의 두 사이트를 참고해 해결된 최종 코드이다. 참고1 참고2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.requestMatchers(new AntPathRequestMatcher("/h2-console/**")).permitAll() //h2-console 뒤에 무슨 데이터 파일이 오더라도 통과
.anyRequest().authenticated()
)
.headers(headers -> headers.frameOptions(withDefaults()).disable()) //header의 프레임에 관련된 값을 사용하지 않음
.csrf(csrf -> csrf
.ignoringRequestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**"))); //csrf를 사용하지 않음
return http.build();
}
그러나 이후 data.sql 관련 오류와 h2 관련 오류가 해결되지 않았고, 시간도 너무 오래 걸려서 포기했다.. 우선 강의를 수강하는 목적이 전반적으로 흐름을 아는 것이었기 때문에 추후 수강할 스프링 강의에서 해결해보려고 한다.
Richardson Maturity Model
Leonard Richardson(Beautifulsoup, Rest 설계 개발자)의 REST API 성숙도 모델
Level 0: 기본 단계. 기존 리소스를 웹서비스 형태로 제공해 단순히 URI만 매핑한 형태
http://sever/getPosts
,http://server/deletePosts
등Level 1: 외부에 공개하고자 하는 리소스에 대해 의미있고 적절한 URI로 표현하기 시작
http://server/acounts
, `http://server/acounts/10’ 등 일정한 패턴을 가지고 작성하지만 http의 method 별로 서비스를 구분해 사용하고 있지는 않는다. 리소스의 형태와 작업의 종류에 맞춰 적절한 http 메소드를 지정하고 있지는 않다. 예를 들어 get, post 만으로 처리하거나 모든 반환값을 error 200으로만 반환한다.Level 2: Level1 + HTTP Methods 로 CRUD를 get, post, put, delete로 매핑해 서비스하는 것을 의미
Level 3: Level2 + HATEOAS 로 data와 next possible action 을 포함한 상태
REST API 설계 시 고려해야 할 사항
- 개발자보다 소비자 입장에서 간단명료하고 직관적인 API 형태로 설계해야 한다.
- HTTP의 장점을 최대한 살려야 한다.
- 각 리소스별 적절한 HTTP의 메소드를 제공한다.
- GET: 조회/읽기 전용
- POST: 데이터 추가
- PUT: 변경
- DELETE: 삭제
- 적절한 상태 코드를 반환한다.
- 비밀번호와 같은 크리티컬한 데이터를 URI에 포함하지 않아야 한다.
- URI는 복수 형태로 지칭하고, 특정 리소스를 지칭하고 싶으면 다음 Depth의 URI로 표현한다.
- /user 보다 /users
- /user1 보다 /users/1 가 바람직하다.
- 동사보다는 명사가 더 직관적이다.
- 여러 개의 검색 API를 생성하는 것보다 단순한 search 메서드를 생성 후 파라미터/헤더 값을 이용해 여러 종류의 요청 처리를 하는 것이 더 나을 수 있다.
- 일관된 접근 end point를 사용한다.
강의 내용 자체는 엄청 어렵지는 않았는데, 내 환경과 강의 환경이 많이 다르다보니 오류가 꽤 많이 발생했었고 이를 해결하는 데 꽤 많은 시간이 소요됐다. 그래도 다시 한번 보니 처음 강의를 따라 했을 때보다는 좀 더 와닿는 것 같다. 복습은 꼭 제때제때 하기… 메모…