๋ค์ด๊ฐ๋ฉฐ
REST API ๋ฅผ ๊น์ด ๊ณต๋ถํ๋ฉฐ RESTful ์ํคํ ์ฒ ์ ์ฝ์ฌํญ์ ๋ํด์ ์๊ฒ๋์๋ค. ๊ทธ ์ค Cacheable ์กฐ๊ฑด์ ๋ํด์ ์ฒ์ ์๊ฒ ๋์๋๋ฐ, ์์ฒญ์ ๋ํ ์๋ฒ์ ์๋ต์ ๋ฐ์ดํฐ๊ฐ ์บ์ ๊ฐ๋ฅํ์ง ๋ถ๊ฐ๋ฅํ์ง ๋ช ์ํด์ผ ํ๋ ์กฐ๊ฑด์ด๋ค. HTTP ํค๋ ์ค Cache-Control ์ด๋ผ๋ ํค๋๋ฅผ ํ์ฉํ๋ฉด ๋๋๋ฐ, ๋ธ๋ผ์ฐ์ ๊ฐ ์บ์๋ฅผ ์ ์ฅํ๊ณ ํ์ฉํ ์ ์๋๋ก ๋์์ฃผ๋ ํ์ค ํค๋์ด๋ค. ์ด๊ฑธ๋ก ๋ธ๋ผ์ฐ์ ๊ฐ ์์์ ์บ์ฑํ ์ ์๋๋ก ์ค์ ํ ์ ์๋ค.
๋ธ๋ผ์ฐ์ ์บ์์ ETag
์น ๋ธ๋ผ์ฐ์ ์ ์บ์๋ ์ฒ์ ๊ตฌํํด ๋ณด๊ธฐ ๋๋ฌธ์ ์ ๋ฆฌํด๋ณด์๋ค. HTTP ํค๋ ์ค Cache-Control ์ด๋ผ๋ ํค๋๋ฅผ ํ์ฉํ๋ฉด ์บ์๋ฅผ ์ ์ดํ ์ ์๋ค. ๊ทธ๋ ์ง๋ง ๋ฐ์ดํฐ๋ฅผ ์๋ชป ์บ์ํ๊ฒ ๋๋ฉด ๋ฐ์ดํฐ์ ๋ถ์ผ์น๊ฐ ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก ์บ์ ๊ฒ์ฆ๋ ๊ฐ์ด ์ํํด์ผ ํ๋ค. ๊ฒ์ฆ์ ์ํ ํค๋๊ฐ ETag ์ด๋ค.
Cache-Control ํค๋ : ์บ์์ ์ ํจ๊ธฐ๊ฐ์ ๋ช ์ํ๋ ์๋ต ํค๋.
ETag ํค๋ : ํน์ ๋ฒ์ ์ ๋ฆฌ์์ค๋ฅผ ์๋ณํ๋ ๊ณ ์ ์๋ณ์. ์๋ฒ๋ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ๋ง๋ค ์ ETag ๋ฅผ ์์ฑํ๋ค.
ETag ๋ง๋๋ ๋ฐฉ๋ฒ
ETag ๋ ์๋ต ๋ฐ์ดํฐ์ ๋ด์ฉ์ด ๋ฐ๋๋ฉด ์๋ก์ด ๊ฐ์ ์์ฑํ๋๋ก ํด์ผ ํ๋ค. (๊ทธ๋์ผ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์๋์ง ์ฌ๋ถ๋ฅผ ์ฝ๊ฒ ํ๋จํ ์ ์๋ค) ๋ฐ๋ผ์, ETag ๋ ๋ค์ ์์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ฑํ ์ ์๋ค.
์๋ต ๋ฐ์ดํฐ ์์ฒด
- ์ : products.toString()
๋ฐ์ดํฐ์ ๋ง์ง๋ง ์์ ์๊ฐ
- ์ : products.getUpdatedAt()
๋ฐ์ดํฐ ์์ฒด๋ฅผ ํด์ํํ ๊ฐ
- ์ : MD5, SHA-256 ๋ฑ ํด์ฑ ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉ
์ด ์ค์์ ํด์๊ฐ์ ์ฌ์ฉํ๋ ๊ฒ์ด ETag ๋ฅผ ์งง๊ณ ํจ์จ์ ์ผ๋ก ์ด์ฉํ ์ ์์ด 3๋ฒ์ ์ ํํ๋ค. (์ฐธ๊ณ ๋กโฆ MD5 ์ SHA-256 ์ ์ฐจ์ด๋ ํด๋น ๊ธ์ ์ฐธ๊ณ ํ๋๋ก ํ์โฆ) ์ผ๋ฐ์ ์ผ๋ก MD5 ๋ณด๋ค SHA ๋ฐฉ์์ด ์๋๋ ๋๋ฆฌ์ง๋ง ์์ ํ๋ฏ๋ก, SHA ๋ฅผ ํ์ฉํ์ฌ ํด์ฑํด๋ณด์ !
@GetMapping("/products")
public ResponseEntity<List<Products>> getAllProducts(HttpHeaders headers) {
try {
// 1๏ธโฃ ์กฐํํ ๋ฐ์ดํฐ
List<Products> products = productsService.findAll();
// 2๏ธโฃ ETag ์์ฑ (SHA-256 ํด์ฑ)
String data = objectMapper.writeValueAsString(products);
String etag = "\"" + generateSHA256ETag(data) + "\"";
// 3๏ธโฃ If-None-Match ํค๋์ ๋น๊ตํ์ฌ 304 ์๋ต
if (etag.equals(headers.getFirst(HttpHeaders.IF_NONE_MATCH))) {
return ResponseEntity
.status(HttpStatus.NOT_MODIFIED)
.eTag(etag)
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES))
.build();
}
// 4๏ธโฃ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋ฐํ
return ResponseEntity.status(200)
.eTag(etag)
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES))
.body(products);
} catch (JsonProcessingException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return ResponseEntity.status(500).build();
}
}
์ด๋ ๊ฒ ํ์ ๋, ๋ฐ์ดํฐ์ ๋ณ๊ฒฝ์ด ์๋ค๋ฉด ๋ธ๋ผ์ฐ์ ๋ 304 ์๋ต์ ๋ฐํํ๋ค.
์น ์บ์์ ํ๊ณ ?
๊ทธ๋ฐ๋ฐ ์กฐ๊ธ ๊ฑฐ์ฌ๋ฆฌ๋ ๋ถ๋ถ์ด ์์๋๋ฐ, ๋ธ๋ผ์ฐ์ ์บ์๋ฅผ ์ค์ ํด๋๋ ์๋ฒ ์ ์ฅ์์๋ ์บ์๋ ๋ฐ์ดํฐ์ ๋ณ๊ฒฝ์ด ์๋์ง ์์๋ณด๋ ค๋ฉด ๊ฒฐ๊ตญ DB ๋ฅผ ๋ ์กฐํํด์ ETag ๋ฅผ ์์ฑํด ๋น๊ตํด์ผ ํ๋ค. DB ์กฐํ๋ฅผ ์ต๋ํ ์ค์ด๊ธฐ ์ํด ์บ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ธ๋ฐ, ์ด๋ ๊ฒ ๋๋ฉด ๋๋ฃจ๋ฌต ์๋๊ฐ? ์ถ์๋คโฆ ๊ทธ๋์ ์น ์บ์๋ฅผ ์ด์ฉํจ๊ณผ ๋์์ redis ๋ฅผ ํ์ฉํ ์๋ฒ ์บ์๋ ๊ฐ์ด ์ด์ฉํ๋ฉด ์๋ฒฝํ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. ๋ค์ ์๊ฐ์ ๊ณ์ ์์๋ณด์ !
๐ ์ฐธ๊ณ ์๋ฃ