REST API Level 3๋ฅผ ์ํ HATEOAS ์ค์
HATEOAS
Hypermedia As the Engine Of Application State
ํ์ฌ ๋ฆฌ์์ค์ ์ฐ๊ด๋(ํธ์ถ ๊ฐ๋ฅํ) ์์ ์ํ ์ ๋ณด๋ฅผ ์ ๊ณต
REST API ์ค๊ณ ๋จ๊ณ
- The Swamp of POX : ํน์ ํ ์น ํ๋กํ ์ฝ์ ํตํด์ ์ปดํจํฐ๊ฐ ๊ฐ์ง๊ณ ์๋ ์์์ ์๋ฏธ ์์ด ์ ๋ฌ
- Resources
- HTTP Verbs : http ์ํ ๋ฉ์๋๋ฅผ ์ด์ฉ
- Hypermedia controls : ๋ฆฌ์์ค์ ์ ๋ณด๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ๋ฆฌ์์ค์ ํจ๊ป ์ ๋ฌ
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
IntelliJ์์ ๊ฒ์
shift key 2๋ฒ ๋๋ฅด๊ธฐ → pom.xml ์ ํ
๋ฒ์ ๋ณ HATEOAS implementation
pom.xml ์ ์ผ ์๋จ์ ๋ณด๋ฉด ์คํ๋ง๋ถํธ ๋ฒ์ ํ์ธ ๊ฐ๋ฅ
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
- spring 2.1.8 RELEASE
- Resource : ๋ง๋ค๊ณ ์ ํ๋ ์ถ๊ฐ ์์์ Resouce ๊ฐ์ฒด๋ก ๋ฃ์
- ControllerLinkBuilder : ๋งํฌํด์ค
@GetMapping("/users/{id}")
public Resource<User> retrieveUser(@PathVariable int id){
// HATEOAS
// "all-users", SERVER_PATH + "/users"
// retrieveAllUsers
Resource<User> resource = new Resource<>(user);
ControllerLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
resource.add(linkTo.withRel("all-users");
return resource;
}
- spring 2.2
- Resource → EntityModel
- ControllerLinkBuilder → WebMvcLinkBuilder
@GetMapping("/users/{id}")
public Resource<User> retrieveUser(@PathVariable int id){
// HATEOAS
// "all-users", SERVER_PATH + "/users"
// retrieveAllUsers
EntityModel<User> model= new EntityModel<>(user);
WebMvcLinkBuilder linkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
model.add(linkTo.withRel("all-users"); // ๋ฆฌ์์ค ์ด๋ฆ : all-users
return model;
}
retrieveAllUsers()์ all-users ์ฐ๋ → /users์ all-users mapping
REST API Documentation์ ์ํ Swagger ์ค์
Swagger
API ์ค๊ณ, ๋น๋, ๋ฌธ์ํ, ์ฌ์ฉ์ ๋์์ ์ฃผ๋ ์คํ์์ค ํ๋ ์์ํฌ
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
spring boot 2.6 ๋ฒ์ ์ดํ์ spring.mvc.pathmatch.matching-strategy ๊ฐ์ด ant_path_matcher์์ path_pattern_parser๋ก ๋ณ๊ฒฝ๋๋ฉด์ ๋ช๋ช ๋ผ์ด๋ธ๋ฌ๋ฆฌ(swaggerํฌํจ)์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ์๋ ๊ฐ์ application.yml์ ์ถ๊ฐํด์ค์ผ ํจ.
Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.web.servlet.mvc.condition.PatternsRequestCondition.getPatterns()" because "this.condition" is null
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
⇒ ํ์ง๋ง ์๋ actuator๋ฅผ ์ง์ํ์ง ์๋๋ค(actuator๋ pathpattern-based parsing์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ)
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2);
}
// SWAGGER 2
// All the paths
// All the apis
}
@Data
@AllArgsConstructor
@NoArgsConstructor // ๋ํดํธ ์์ฑ์ ์์ฑ
@ApiModel(description = "์ฌ์ฉ์ ์์ธ ์ ๋ณด๋ฅผ ์ํ ๋๋ฉ์ธ ๊ฐ์ฒด")
public class User {
private Integer id;
@Size(min=2, message = "Name์ 2๊ธ์ ์ด์ ์
๋ ฅํด์ฃผ์ธ์.")
@ApiModelProperty(notes = "์ฌ์ฉ์ ์ด๋ฆ์ ์
๋ ฅํด์ฃผ์ธ์.")
private String name;
@Past // ๊ณผ๊ฑฐ ๋ ์ง๋ง ๊ฐ๋ฅํ ์ ์ฝ ์กฐ๊ฑด
@ApiModelProperty(notes = "์ฌ์ฉ์์ ๋ฑ๋ก์ผ์ ์
๋ ฅํด์ฃผ์ธ์.")
private Date joinDate;
@ApiModelProperty(notes = "์ฌ์ฉ์์ ํจ์ค์๋๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.")
private String password;
@ApiModelProperty(notes = "์ฌ์ฉ์์ ์ฃผ๋ฏผ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์.")
private String ssn; // ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ
}
๋ญ๊ฐ ๋์ค์ ๋์์ด ๋ ๊ฒ ๊ฐ์ ์ง๋ฌธ
Swagger์ Jackson Filter ์ฌ์ฉ ์ Swagger-ui์ example value - ์ธํ๋ฐ | ์ง๋ฌธ & ๋ต๋ณ
REST API Monitoring์ ์ํ Actuator ์ค์
ํ์ฌ ๊ธฐ๋ํ๊ณ ์๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ์ฝ๊ฒ monitoringํ ์ ์๋ค. ํ์ฌ ์๋ฒ๊ฐ ๊ตฌ๋ ์ค์ธ์ง๋ ํ์ธ ๊ฐ๋ฅ
์๋ฒ์ ๋ชจ๋ํฐ๋ง ๋๊ตฌ๋ก ์ฌ์ฉ → ๋ณ๋๋ก ์ปค์คํฐ๋ง์ด์งํด์ ์ฌ์ฉํ๋ฉด ๋ ์ข๋ค.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
์๋ฌ
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper';
https://github.com/springfox/springfox/issues/3462
SwaggerConfig.java ์์ bean ์ค์ ํด์ฃผ๊ธฐ
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
}
private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
http://localhost:8088/actuator
http://localhost:8088/actuator/health
→ ์๋ฒ๊ฐ ์๋ ์ค์ธ์ง ํ์ธํ ์ ์๋ค
application.yml ์ค์
์กฐ๊ธ ๋ ๋ค์ํ ํ์ผ์ ๋ณผ ์ ์๋ค.
# actuator ์ค์
management:
endpoints:
web:
exposure:
include: "*"
HAL Browser
- Hypertext Application Language
- “HAL is a simple format that gives a consistent and easy way to hyperlink between resources in your API”
- API ๋ฆฌ์์ค ์ฌ์ด์์ ์ผ๊ด์ ์ธ ํ์ดํผ๋งํฌ๋ฅผ ์ ๊ณต
- API ์ค๊ณ์์ HAL์ ๋์ ํ๊ฒ ๋๋ฉด API ๊ฐ ์ฌ์ด ๊ฒ์์ด ๊ฐ๋ฅํด์ง๋ค. → ๋ ๋์ ๊ฐ๋ฐํ๊ฒฝ ์ ๊ณต
- HAL์ response message์ ๋์ ํ๊ฒ ๋๋ฉด ๊ทธ ๋ฉ์์ง์ ํฌ๋งท๊ณผ ์๊ด์์ด API๋ฅผ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ์ ๋ณด(๋ฉํ์ ๋ณด)๋ฅผ ํ์ดํผ๋งํฌ ํ์์ผ๋ก ์ ๊ณต
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
HAL Browser๊ฐ deprecated ๋์ด์ ์ต์ spring boot ๋ฒ์ ์์๋ HAL explorer๋ฅผ ํตํด ์ฌ์ฉํ๋ฉด ๋๋ค. (๋ฒ์ ์ถ๊ฐ ์ ํ๋ฉด ์๋ฌ๋จ)
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-explorer</artifactId>
<version>3.5.1</version>
</dependency>
์ฅ์
response๊ฐ์ฒด์ ๋ถ๊ฐ์ ์ธ ์ ๋ณด๋ฅผ ์ ๊ณตํด์ค๋ค
๋งํฌ ์ ๋ณด๋ฅผ jsonํํ๋ก ํํํ ์ ์๋ ํ์ค
์ฅ์ : rest์์์ ํ์ํ๊ธฐ ์ํ ์๋ฃ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ง ์์๋ HATEOAS๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ค
Spring Security
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- dependency๋ฅผ ์ค์ ํ ํ, ์ดํ๋ฆฌ์ผ์ด์ ์ ์คํํ์ฌ ๋น๋ฐ๋ฒํธ๋ฅผ ํ์ธํด์ผ ํ๋ค. (์น์๋ฒ๋ฅผ ํ์ธํ๊ธฐ ์ํด ํ์)
- 5bea3312-3c01-4d40-8ab3-03e86f56627f
- ์ธ์ฆ์ด ๋์ด ์์ง ์๋ค๋ฉด 401 Unauthorized๊ฐ ๋ฌ๋ค.
- ์ฌ๊ธฐ์์ username ๊ฐ์ user๋ก ํด๋ ๋ถ๋ฌ์์ง๋ ์ด์ ๋ spring security์ ๊ธฐ๋ณธ ์ค์ ๋๋ฌธ์ด๋ค.
→ spring.security.user.name / spring.security.user.password๋ฅผ ์ง์ ํ์ง ์์ผ๋ฉด ๊ธฐ๋ณธ ์ ์ ๋ช ๊ณผ ๋๋ค ํจ์ค์๋๋ฅผ ์ฌ์ฉํ๋ค.
→ ๊ธฐ๋ณธ ์ ์ ๋ช ์ user์ด๋ฉฐ password์ ๊ฒฝ์ฐ ์คํ๋ง๋ถํธ๊ฐ ๊ธฐ๋๋ ๋ ํ์๋๋ค
- ์คํ๋ง๋ถํธ์์ ์ค์ ํ ์ ์๋ ํญ๋ชฉ
Configuration ํด๋์ค๋ฅผ ์ด์ฉํ ์ฌ์ฉ์ ์ธ์ฆ ์ฒ๋ฆฌ
[application.yml]
spring:
security:
user:
name: nitro
password: helloworld
์ถ๊ฐ : spring cloud bus → yml ํ์ผ ๋ณ๊ฒฝ ์ ์๋ฒ์ ์ฌ๋ถํ ์์ด ์ ๋ฐ์ดํธ ๊ฐ๋ฅ
@Configuration // Spring boot ๊ธฐ๋ ์ ๋ฉ๋ชจ๋ฆฌ์ ์ค์ ์ ๋ณด๋ฅผ ๊ฐ์ด ๋ก๋ฉํ๊ฒ ๋๋ค
'๐ฟ Spring > โ RESTful Web Service' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
6. RESTful API ์ค๊ณ ๊ฐ์ด๋ (0) | 2023.06.20 |
---|---|
5. Java Persistence API ์ฌ์ฉ (0) | 2023.06.20 |
3. RESTful Service ๊ธฐ๋ฅ ํ์ฅ (0) | 2023.06.20 |
2. User Service API ๊ตฌํ (0) | 2023.06.20 |
1. Spring Boot๋ก ๊ฐ๋ฐํ๋ RESTful Service (0) | 2023.06.20 |
๋๊ธ