๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŒฟ Spring/โœ… RESTful Web Service

3. RESTful Service ๊ธฐ๋Šฅ ํ™•์žฅ

by nitronium102 2023. 6. 20.

Validation API

@Valid annotation ์‚ฌ์šฉ

  1. **pom.xml์— dependency ์ถ”๊ฐ€**
<dependency>
           <groupId>org.hibernate.validator</groupId>
           <artifactId>hibernate-validator</artifactId>
           <version>6.0.7.Final</version>
        </dependency>
  1. dto์— ์ œ์•ฝ์กฐ๊ฑด ์ถ”๊ฐ€
@Data
@AllArgsConstructor
public class User {
	private Integer id;

	@Size(min=2, message = "Name์€ 2๊ธ€์ž ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.")
	private String name;
	private String password;
	@Past // ๊ณผ๊ฑฐ ๋‚ ์งœ๋งŒ ๊ฐ€๋Šฅํ•œ ์ œ์•ฝ ์กฐ๊ฑด
	private Date joinDate;
}
  1. controller์— ์ถ”๊ฐ€
  • JDK์— ํฌํ•จ๋œ API์™€ hibernate library์— ํฌํ•จ๋œ validation ๊ธฐ๋Šฅ
  • @Valid : ์‚ฌ์šฉ์ž ์ถ”๊ฐ€ํ•  ๋•Œ validation ๊ฒ€์ฆ
  • @RequestBody : ํ˜„์žฌ variable์ด requestBody ํ˜•์‹์œผ๋กœ ๋“ค์–ด์˜ด์„ ์•Œ๋ฆผ
  1. exception handler ์ถ”๊ฐ€
@ResponseStatus(HttpStatus.NOT_FOUND) // ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ ๊ฐ’ ์ง€์ •
// CustomizedResponseEntityExceptionHandler.class์—์„œ UserNotFoundException์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, Exception Response๋ฅผ ๋ฐ˜ํ™˜(Http status code๋„ ๋ฐ˜ํ™˜)
// => ResponseStatus ์ƒ๋žต ๊ฐ€๋Šฅ
public class UserNotFoundException extends RuntimeException {
	public UserNotFoundException(String message) {
		super(message); // ex.getMessage()
	}
}

๋‹ค๊ตญ์–ด ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ Internationalization ๊ตฌํ˜„

  1. yml ํŒŒ์ผ์— spring.messages.basename={์‚ฌ์šฉํ•  properties ํŒŒ์ผ์˜ ์ด๋ฆ„}
#๋‹ค๊ตญ์–ด ํŒŒ์ผ ์„ค์ •
spring:
  messages:
    basename: messages # ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•  message ํŒŒ์ผ์˜ ์ด๋ฆ„
  1. resources ๋ฐ‘์— {yml ํŒŒ์ผ์—์„œ ์„ค์ •ํ•œ ์ด๋ฆ„}.properties๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค

-> ๋‹ค๊ตญ์–ด ๋ฒ„์ „์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐœ ์ƒ์„ฑ

# properties : ์ž๋ฐ”์˜ ๋ฆฌ์†Œ์Šค ๋ฒˆ๋“ค
# ํŠน์ • key๊ฐ’๊ณผ value๋ฅผ ์ €์žฅํ•ด ๋†“๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ๋จ
greeting.message=์•ˆ๋…•ํ•˜์„ธ์š”
// messages_fr.properties
greeting.message=Bonjour
  1. @SpringBootApplication๋ฐ‘์— @Bean์„ ์ƒ์„ฑํ•ด์„œ localeResolver ์ƒ์„ฑ
@Bean
// @SpringBootApplication์— @Bean์„ ๋“ฑ๋กํ•˜๊ฒŒ ๋˜๋ฉด Spring Boot๊ฐ€ ์ดˆ๊ธฐํ™”๋  ๋–„,
// ํ•ด๋‹น ์ •๋ณด์— ํ•ด๋‹น๋˜๋Š” ๊ฐ’์ด ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ผ๊ฐ€์„œ ๋‹ค๋ฅธ ํด๋ž˜์Šค๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
public LocaleResolver localeResolver() {		
	SessionLocaleResolver localeResolver = new SessionLocaleResolver();
	localeResolver.setDefaultLocale(Locale.KOREA); // default : ํ•œ๊ตญ์–ด ์ฒ˜๋ฆฌ
	return localeResolver;
}
  1. Controller์—์„œ ์„ค์ • -> Header๋กœ language ๊ฐ’์„ ๋ฐ›์•„์„œ ํ•ด๋‹น ์–ธ์–ด์— ๋งž๋Š” ๋ฉ”์„ธ์ง€๋ฅผ ์ถœ๋ ฅ
@Autowired // annotation์„ ํ†ตํ•œ ์˜์กด์„ฑ ์ฃผ์ž… : spring framework์— ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š” bean ์ค‘ ๊ฐ™์€ ํƒ€์ž…์„ ๊ฐ€์ง„ bean์„ ์ž๋™์œผ๋กœ ์ฃผ์ž…
private MessageSource messageSource;

@GetMapping(path="/hello-world-internationalized")
	public String helloWorldInternationalized(
		@RequestHeader(name="Accept-Language", required = false) Locale locale) {
		// "Accept-Language" ๊ฐ’์ด ์„ค์ •๋˜์ง€ ์•Š์•˜์„ ๋•Œ๋Š” ์ž๋™์œผ๋กœ default๊ฐ’์ธ ํ•œ๊ตญ์–ด๋กœ ์„ค์ •(๊ธฐ๋ณธ locale ๊ฐ’)
		// "Accept-Language" ๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด locale์— ์ €์žฅ

		// ์ฒซ๋ฒˆ์งธ ์ธ์ž: ์„ค์ • ํŒŒ์ผ์—์„œ์˜ key ๊ฐ’
		// ๋‘๋ฒˆ์งธ ์ธ์ž: ๋งŒ์•ฝ parameter๋ฅผ ๊ฐ€์ง„ ๊ฐ€๋ณ€๋ณ€์ˆ˜๋ผ๋ฉด parameter ์ง€์ •
		// ์„ธ๋ฒˆ์งธ ์ธ์ž: RequestHeader๋ฅผ ํ†ตํ•ด ์ „๋‹ฌ๋ฐ›์€ locale
		return messageSource.getMessage("greeting.message", null, locale);
	}

@Bean๊ณผ @Autowired

  1. @Bean @SpringBootApplication์— @Bean์„ ๋“ฑ๋กํ•˜๊ฒŒ ๋˜๋ฉด Spring Boot๊ฐ€ ์ดˆ๊ธฐํ™”๋  ๋•Œ, ํ•ด๋‹น ์ •๋ณด์— ํ•ด๋‹น๋˜๋Š” ๊ฐ’์ด ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ผ๊ฐ€์„œ ๋‹ค๋ฅธ ํด๋ž˜์Šค๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.
  2. @Autowired

annotation์„ ์ด์šฉํ•œ ์˜์กด์„ฑ ์ฃผ์ž… : spring framework์— ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š” bean ์ค‘ ๊ฐ™์€ ํƒ€์ž…์„ ๊ฐ€์ง„ bean์„ ์ž๋™์œผ๋กœ ์ฃผ์ž…

Response ๋ฐ์ดํ„ฐ ํ˜•์‹ ๋ฐ˜ํ™˜ - XML format

  1. Postman header์—์„œ key: Accept, value: application/xml๋กœ ๋ณ€ํ™˜ ์‹œ xmlํŒŒ์ผ ํ˜ธ์ถœ ๊ฐ€๋Šฅ

2. pom.xml์—์„œ dependency ์ถ”๊ฐ€

<!--xml ํŒŒ์ผ ๋ณ€ํ™˜ ์„ค์ •-->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.10.2</version>
</dependency>

⇒ ์„ค์ •ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ 406 ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค

HTTP(HyperText Transfer Protocol) 406 Not Acceptable ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜ ์‘๋‹ต ์ฝ”๋“œ๋Š” ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์˜ ์‚ฌ์ „ ์ฝ˜ํ…์ธ  ํ˜‘์ƒ ํ—ค๋”์— ์ •์˜๋œ ํ—ˆ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ’ ๋ชฉ๋ก๊ณผ ์ผ์น˜ํ•˜๋Š” ์‘๋‹ต์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ์„œ๋ฒ„๊ฐ€ ๊ธฐ๋ณธ ํ‘œํ˜„์„ ์ œ๊ณตํ•  ์˜์‚ฌ๊ฐ€ ์—†์Œ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

์‚ฌ์ „ ์ปจํ…์ธ  ํ˜‘์ƒ ํ—ค๋”์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ์ด ์˜ค๋ฅ˜๋Š” ๊ฑฐ์˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ตœ์ข… ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ์•”ํ˜ธํ™”๋˜์–ด ์ˆ˜์ •ํ•˜๊ธฐ ์–ด๋ ค์šด ์ด ์˜ค๋ฅ˜ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‘๋‹ตํ•˜๋Š” ๋Œ€์‹  ์„œ๋ฒ„๋Š” ๊ด€๋ จ ํ—ค๋”๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹ค์ œ ํŽ˜์ด์ง€๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์™„์ „ํžˆ ๋งŒ์กฑํ•˜์ง€ ์•Š๋”๋ผ๋„ ์˜ค๋ฅ˜ ์ฝ”๋“œ๋ณด๋‹ค ์„ ํ˜ธํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ฒ„๊ฐ€ ์ด๋Ÿฌํ•œ ์˜ค๋ฅ˜ ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ์— ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฆฌ์†Œ์Šค ํ‘œํ˜„ ๋ชฉ๋ก์ด ํฌํ•จ๋˜์–ด ์žˆ์–ด์•ผ ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Response ๋ฐ์ดํ„ฐ ์ œ์–ด๋ฅผ ์œ„ํ•œ Filtering

ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌ๋˜๋Š” ์ •๋ณด์˜ ๊ฐ’์„ ์ œ์–ด

  1. ๊ฐ’์„ ์•”ํ˜ธํ™”ํ•˜๊ฑฐ๋‚˜ ์น˜ํ™˜ํ•˜์—ฌ ์ „๋‹ฌ
  2. null๋กœ ๋ฐ˜ํ™˜

⇒ @JsonIgnore , @JsonIgnoreProperties ์‚ฌ์šฉ

@JsonIgnore // ๋ฐ˜ํ™˜ ์‹œ Json๊ฐ’์— ํฌํ•จ๋˜์ง€ ์•Š์Œ ->๋ฐ˜ํ™˜๊ฐ’ ์ œ์–ด
privateString password;

@JsonIgnore
privateString ssn;//์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ
//@JsonIgnoreProperties(value={"password", "ssn"}) // ํด๋ž˜์Šค ๋ธ”๋ก ๋‹จ์œ„๋กœ ํ•„ํ„ฐ๋ง

๊ฐœ๋ณ„ ์‚ฌ์šฉ์ž ์กฐํšŒ

// domain
// @JsonIgnoreProperties(value={"password", "ssn"}) // ํด๋ž˜์Šค ๋‹จ์œ„๋กœ ํ•„ํ„ฐ๋ง
@JsonFilter("UserInfo") // ๋ถ€์—ฌ๋œ ํ•„ํ„ฐ๊ฐ’์€ controller๋‚˜ service ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉ๋œ๋‹ค
@RequestMapping("/admin") // ๋ชจ๋“  api๊ฐ€ ๊ณตํ†ต์ ์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ด๋ฆ„์„ ํด๋ž˜์Šค ๋ธ”๋ก์— ์„ ์–ธ
@GetMapping("/users/{id}")
	// setFilters๋‚˜ setSerialView๋ฅผ ์‚ฌ์šฉ ์‹œ ๊ผญ MappingJacksonValue ํƒ€์ž…์œผ๋กœ ๊ฐ์‹ธ์„œ ๋ฐ˜ํ™˜
	public MappingJacksonValue retrieveUser(@PathVariable int id) { // ๊ด€๋ฆฌ์ž : ์œ ์ €์˜ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก!
		User user = service.findOne(id);
		if (user == null){
			throw new UserNotFoundException(String.format("ID[%s] is not found", id));
		}

		// bean์˜ property๋ฅผ ์ œ์–ด
		SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
			.filterOutAllExcept("id", "name", "joinDate", "ssn"); // ํฌํ•จ์‹œํ‚ค๊ณ  ์‹ถ์€ ํ•„ํ„ฐ๊ฐ’ ์„ ์–ธ

		// User์— @JsonFilter("UserInfo") ์ง€์ •
		FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);

		MappingJacksonValue mapping = new MappingJacksonValue(user); // ์œ ์ € ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
		mapping.setFilters(filters); // ํ•„ํ„ฐ ์ ์šฉ

		return mapping;
	}

 

์ •๋ฆฌ

ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌ๋˜๋Š” ์ •๋ณด์˜ ๊ฐ’์„ ์ œ์–ดํ•˜๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ• (1) ๊ฐ’์„ ์•”ํ˜ธํ™”ํ•˜๊ฑฐ๋‚˜ ์น˜ํ™˜ํ•˜์—ฌ ์ „๋‹ฌ (2) null์„ ๋„ฃ์–ด ์ „๋‹ฌ

-> ์šฐ๋ฆฌ๋Š” @JsonIgnore, @JsonIgnoreProperties๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ’์„ ์ œ์–ดํ•œ๋‹ค

  1. @JsonIgnore : ๋‹จ์œ„ ํ•„๋“œ ๊ฐ’์„ ์ œ์–ดํ•  ๋•Œ ์‚ฌ์šฉ. ๋ฐ˜ํ™˜ ์‹œ json ๊ฐ’์— ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค
  2. @JsonIgnoreProperties(value={"ํ•„๋“œ๊ฐ’"}) : ํด๋ž˜์Šค๋ฅผ ๋ธ”๋ก ๋‹จ์œ„๋กœ ํ•„ํ„ฐ๋ง -> ์กฐ๊ธˆ ๋” ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ• :@JsonFilter ์‚ฌ์šฉ
  3. domain์— @JsonFilter("ํ•„ํ„ฐ๋ช…") ์‚ฌ์šฉ : ๋ถ€์—ฌ๋œ ํ•„ํ„ฐ๊ฐ’์€ controller๋‚˜ service ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉ
  4. controller์—์„œ return๊ฐ’์„ MappingJacksonValue๋กœ ์„ ์–ธ
  • SimpleBeanPropertyFilter๋ฅผ ์ด์šฉํ•˜์—ฌ ํฌํ•จ์‹œํ‚ค๊ณ  ์‹ถ์€ ํ•„ํ„ฐ๊ฐ’ ์„ ์–ธ
  • FilterProvider์— domain์—์„œ ์ง€์ •ํ•œ ํ•„ํ„ฐ ์ง€์ •
  • MappingJacksonValue์— FilterProvider๋ฅผ ์ ์šฉํ•˜์—ฌ ๋ฆฌํ„ด

REST API ๋ฒ„์ „ ๊ด€๋ฆฌ

Richardson Maturity Model

URL์„ ์ด์šฉํ•œ ๋ฒ„์ „ ๊ด€๋ฆฌ

url์— ๋ฒ„์ „์„ ํ‘œ์‹œํ•จ์œผ๋กœ์จ ๋ฒ„์ „์„ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฒ„์ „ ๊ด€๋ฆฌ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์†Œ์Šค ์ฝ”๋“œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ œ๊ณตํ•˜๋ ค๋Š” ์„œ๋น„์Šค์˜ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•ด์„œ๋„ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

// ๋ฐ˜ํ™˜๋ฐ›์€ User๋ฅผ UserV2๋กœ ๋ณ€ํ™˜
		UserV2 userV2 = new UserV2();
		// ๋‘ ์ธ์Šคํ„ด์Šค ๊ฐ„์— ๊ณตํ†ต๋œ ํ•„๋“œ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฐ’์„ copyํ•จ
		BeanUtils.copyProperties(user, userV2); // id, name, joinDate, password, ssn
		userV2.setGrade("VIP");

Request Parameter๋ฅผ ์ด์šฉํ•œ ๋ฒ„์ „ ๊ด€๋ฆฌ

// ๋ฐ์ดํ„ฐ ๋’ค์— ๋ฒ„์ „์— ๋Œ€ํ•œ ์ •๋ณด๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ์ „๋‹ฌ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— URI ๋’ค์— "/"๊ฐ€ ๋ถ™๋Š”๋‹ค
	@GetMapping(value = "/users/{id}/", params = "version=1")

http://localhost:8088/admin/users/1/?version=1 ๋ฐฉ์‹์œผ๋กœ ํ˜ธ์ถœ

Header๋ฅผ ์ด์šฉํ•œ ๋ฒ„์ „ ๊ด€๋ฆฌ

@GetMapping(value = "/users/{id}", headers = "X-API-VERSION=2")

MIME ํƒ€์ž…์„ ์ด์šฉํ•œ ๋ฒ„์ „ ๊ด€๋ฆฌ

MIME ํƒ€์ž… → Multi-Purpose Internet Mail Extension(ํŒŒ์ผ ํ˜•์‹ ์ง€์ •)

์ด๋ฉ”์ผ๊ณผ ํ•จ๊ป˜ ์ „์†ก๋˜๋Š” ๋ฉ”์ผ๋ฅผ ํ…์ŠคํŠธ ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ด๋ฉ”์ผ ์„œ๋ฒ„์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•

// ์ œ๊ณตํ•˜๊ณ ์ž ํ•˜๋Š” MIME ํƒ€์ž… ์ง€์ •
	// ๊ธฐ์กด์— ์—†๋˜ ์ƒˆ๋กœ์šด ๊ฐ’ -> application
	// ๋ฒ„์ „ ์ด๋ฆ„ : appv1
	// ์ „๋‹ฌ ์‹œํ‚ค์กฐ์ž ํ•˜๋Š” ํƒ€์ž… : json
	@GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv1+json")

 

๋ฒ„์ „ ๊ด€๋ฆฌ

๋‹จ์ˆœํ•˜๊ฒŒ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ฃผ๋Š” ํ•ญ๋ชฉ์„ ์ œ์–ดํ•˜๋Š” ์šฉ๋„๊ฐ€ ์•„๋‹ˆ๋ผ REST API์˜ ์„ค๊ณ„๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋„ ๋ฒ„์ „์„ ๋ณ€๊ฒฝํ•ด์ฃผ์–ด์•ผ ํ•จ.

์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” ์–ด๋– ํ•œ API ๋ฒ„์ „์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ๊ฐ€์ด๋“œ ์ œ๊ณต ํ•„์š”

  • ์ผ๋ฐ˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅ
    • URI
    • Request Parameter
  • ์ผ๋ฐ˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰ ๋ถˆ๊ฐ€
    • Media type versioning(content negotiation, accept header) → ํ—ค๋”
    • (custom) headers versioning → MIME ํƒ€์ž…
  • ์œ ์˜ ์‚ฌํ•ญ
    • URI Pollution : ๋„ˆ๋ฌด ์ง€์ €๋ถ„ํ•˜๊ฑฐ๋‚˜ ๊ณผ๋„ํ•˜๊ฒŒ ์ •๋ณด๋ฅผ ํ‘œ๊ธฐํ•˜๋Š” ๊ฒƒ์€ ์ง€์–‘
    • Misuse of HTTP headers : ์ž˜๋ชป๋œ ํ—ค๋”๊ฐ’ ์ฃผ์˜
    • Caching : ์ ์ ˆํžˆ ์บ์‹œ ์‚ญ์ œํ•ด์„œ ์ œ๋Œ€๋กœ ๋œ ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
    • Can we execute the request on browser?
    • API Documentation
  •  

๋Œ“๊ธ€