ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋ค์ํ ํ์๊ณผ ํด๋ผ์ด์ธํธ๊ฐ ์๋ก ๋ค๋ฅธ ๋ชจ๋์ด๋ ์๋น์ค๋ฅผ ์ฌ์ฉํ๊ฒ ๋ ๋, ์๋ต ํ์์ด ํต์ผ๋์ง ์์ผ๋ฉด ํผ๋์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
ํนํ, ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ
์ฒ(MSA)์์๋ ์ฌ๋ฌ ์๋น์ค๊ฐ ๋
๋ฆฝ์ ์ผ๋ก ๊ฐ๋ฐ๋๊ธฐ ๋๋ฌธ์, ๊ฐ ์๋น์ค๋ง๋ค ์๋ก ๋ค๋ฅธ ์๋ต ๊ตฌ์กฐ๋ฅผ ์ฌ์ฉํ๋ฉด ์ ์ฒด ์์คํ
์ ์ผ๊ด์ฑ์ ํด์น ์ ์์ต๋๋ค. ์ด๋ค ํ๋ก์ ํธ์์๋ ํต์ผ๋ ์๋ต ํฌ๋งท์ ํด๋ผ์ด์ธํธ์ ํ์
ํ๋ ํ์ ๋ชจ๋์๊ฒ ๋ช
ํํ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ์ฌ ๋๋ฒ๊น
๊ณผ ์ ์ง๋ณด์๋ฅผ ์ฉ์ดํ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
์ค๋ ํฌ์คํ
์์ ์๊ฐํ ๋ฐฉ๋ฒ ์ค ํ๋๋ Spring Boot์์ ์ ๊ณตํ๋ ResponseEntity์ ํจ๊ป, ์ ๋ค๋ฆญ ์ปค์คํ
Response ํด๋์ค๋ฅผ ํ์ฉํ๋ ๊ฒ์
๋๋ค!
Resposne ๊ฐ์ฒด ์์ฑ
์๋์ ๊ฐ์ด ๊ฐ๋จํ๋ฉด์๋ ํ์ฅ์ฑ์ด ๋ฐ์ด๋ Response ํด๋์ค๋ฅผ utility ๋ชจ๋์ ์์ฑํ์ฌ ์ฑ๊ณต, ์คํจ์ ๋ฐ๋ฅธ ์๋ต์ ํ์
ํ ์ ์๋๋ก ํ์ต๋๋ค.
Response custom class
package com.example.module_utility.response;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Response<T> {
private final int status;
private final String message;
private final T data;
public Response(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
public static <T> Response<T> success(T data) {
return new Response<>(200, "success", data);
}
public static Response<Void> successWithoutData() {
return new Response<>(200, "success", null);
}
public static <T> Response<T> error(int status, String message) {
return new Response<>(status, message, null);
}
}
@JsonInclude annotation์ ํตํด successWithoutData์ error ์๋ต๊ณผ ๊ฐ์ด data ์ null ์ด ๋ค์ด๊ฐ๋ ๊ฒฝ์ฐ, ์๋ต์์ null ์ด ๋ณด์ด์ง ์๋๋ก ํด์ฃผ์์ต๋๋ค.
๋จ์ POJO ์ฝ๋์ด๊ธฐ ๋๋ฌธ์, utility ๋ชจ๋์ ๋ค๋ฅธ ๋ชจ๋์ ์คํ์ํฌ ๋ ๊ฐ์ด ์คํ์์ผ ์ค ํ์๊ฐ ์๊ณ , ํด๋น ๋ชจ๋์ ๋ํ ์์กด์ฑ๋ง ์ถ๊ฐํด์ฃผ๋ฉด ๋ฉ๋๋ค.
์์กด์ฑ ์ถ๊ฐ ์์
project(':module-exchange') {
dependencies {
implementation project(':module-utility')
// ๊ธฐํ ํ์ํ ์์กด์ฑ
}
}
Response Entity
์์ Response ํด๋์ค๋ง ์ฌ์ฉํด๋ ์ถฉ๋ถํ ์ผ๊ด๋ ์๋ต ๊ตฌ์กฐ๋ฅผ ๋ง๋ค ์ ์์ง๋ง, ์ฌ๊ธฐ์ ResponseEntity๋ก ์ถ๊ฐ๋ก ํ ๋ฒ ๊ฐ์ธ๋ ๋ฐฉ๋ฒ์ ์ฑํํ์ต๋๋ค.
Response Entity๊น์ง ์ฌ์ฉํ๋ ์ด์ ๋ HTTP ์๋ต์ ์ํ ์ฝ๋, ํค๋ ๋ฑ ๋ถ๊ฐ์ ์ธ ์ ๋ณด๋ฅผ ๋ช
์์ ์ผ๋ก ์ ์ดํ๊ธฐ ์ํจ์
๋๋ค. ์ฆ, ํด๋ผ์ด์ธํธ์ ์ ์ก๋๋ ์๋ต ๋ณธ๋ฌธ ๋ฟ๋ง๋์๋๋ผ, HTTP ํ๋กํ ์ฝ ์ฐจ์์์์ ์ํ ์ฝ๋๋ ํค๋ ์ค์ ๊น์ง ์์ฝ๊ฒ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
๋จ์ํ API์์๋ ๊ณตํต Response ํด๋์ค๋ฅผ ๋จ๋
์ผ๋ก ์ฌ์ฉํด๋ ๋์ง๋ง, ResponseEntity์ ํจ๊ป ์ฌ์ฉํจ์ผ๋ก์จ ๋ณด๋ค ๋ช
ํํ ์๋ฌ ํธ๋ค๋ง, ์ํ ์ฝ๋ ๊ด๋ฆฌ ๋ฐ ํ์ฅ์ฑ์ ํ๋ณดํ ์ ์์ต๋๋ค.
Response Entity์ Response๋ฅผ ์ด์ฉํ ์๋ต ๊ตฌํ
์ด์ ์๋ ์๋น์ค ๋จ์์ Response ๊ฐ์ฒด๋ฅผ ์ง์ ์์ฑํ์ฌ ๋ฐํํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ ์ ์ด ์์์ต๋๋ค.
@Service
public class ExchangeService {
public Response<AccountUpdateResponseDTO> executeTransactionProcess(TransactionDTO transactionDTO) {
// ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌ
AccountUpdateResponseDTO dto = // ... ๋ก์ง ๊ฒฐ๊ณผ
// ์๋น์ค ๊ณ์ธต์์ ๋ฐ๋ก Response ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฐํ
return Response.success(dto);
}
}
๊ทธ๋ฆฌ๊ณ ์ปจํธ๋กค๋ฌ์์๋ ๋จ์ํ ์ด ๋ฐํ๊ฐ์ ๊ทธ๋๋ก ์ ๋ฌํ๋ ๋ฐฉ์์ด์์ต๋๋ค.
@PostMapping("/transactions")
public ResponseEntity<Response<AccountUpdateResponseDTO>> saveTransaction(@RequestBody TransactionDTO transactionDTO) {
// ์๋น์ค ๋จ์์ Response๋ฅผ ์์ฑํด์ ๋ฐํํ๋ฏ๋ก, ์ปจํธ๋กค๋ฌ๋ ๋ณ๋์ ์๋ต ๊ตฌ์ฑ ๋ก์ง์ด ํ์ ์์
Response<AccountUpdateResponseDTO> response = exchangeService.executeTransactionProcess(transactionDTO);
return ResponseEntity.ok(response);
}
๊ทธ๋ฌ๋, ์๋น์ค ๊ณ์ธต์ ๋น์ฆ๋์ค ๋ก์ง๋ง ์ฒ๋ฆฌํ๊ณ , ์๋ต ๊ตฌ์ฑ๊ณผ ๊ด๋ จ๋ ์ฒ๋ฆฌ๋ ์ปจํธ๋กค๋ฌ์์ ๋ด๋นํ๋ ๊ฒ์ด ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ ์์น ๋ฐ ๋จ์ผ ์ฑ
์ ์์น์ ๋ถํฉํฉ๋๋ค.
- ๊ด์ฌ์ฌ์ ๋ถ๋ฆฌ(Separation of Concerns)
์๋น์ค ๊ณ์ธต์ ๋น์ฆ๋์ค ๋ก์ง์ ์ง์คํด์ผ ํฉ๋๋ค. HTTP ์ํ ์ฝ๋, ์๋ต ํฌ๋งท, ํค๋ ๋ฑ์ ํ๋ ์ ํ ์ด์ ๊ณ์ธต(์ปจํธ๋กค๋ฌ)์ ์ฑ ์์ผ๋ก ๋์ด์ผ ํฉ๋๋ค. - ๋จ์ผ ์ฑ
์ ์์น(Single Responsibility Principle)
์๋น์ค ๊ณ์ธต์ด ์๋ต ์์ฑ๊น์ง ๋ด๋นํ๋ฉด, ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์๋ต ํฌ๋งท ์ค์ ์ด๋ผ๋ ๋ ๊ฐ์ง ์ฑ ์์ ๋์์ ์ง๊ฒ ๋ฉ๋๋ค. ์ด๋ ์ ์ง๋ณด์์ ํ ์คํธ์ ๋ถ๋ฆฌํ ์ ์์ต๋๋ค.
๋ฐ๋ผ์, ํ์ฌ๋ ์๋น์ค ๋จ์์๋ ๋จ์ํ Response DTO(์: AccountUpdateResponseDTO)๋ง ๋ฐํํ๊ณ , ์ปจํธ๋กค๋ฌ์์ ์ด๋ฅผ ๋ฐ์์ ResponseEntity์ ๊ณตํต Response ํด๋์ค๋ก ๊ฐ์ธ์ ์๋ต์ ๊ตฌ์ฑํ๋๋ก ๋ณ๊ฒฝํ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ExchangeController์์๋ ์๋์ ๊ฐ์ด ๊ตฌํํฉ๋๋ค.
ExchangeController ์์ ResponseEntity์ Response ๊ฐ์ฒด ์ฌ์ฉ
@PostMapping("/transactions")
public ResponseEntity<Response<AccountUpdateResponseDTO>> saveTransaction(@RequestBody TransactionDTO transactionDTO) {
AccountUpdateResponseDTO accountUpdateResponseDTO = exchangeService.executeTransactionProcess(transactionDTO);
Response<AccountUpdateResponseDTO> response = Response.success(accountUpdateResponseDTO);
return ResponseEntity.ok(response);
}
์ค์ ์ฝ๋์์์ ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์์ ๊ฐ์ต๋๋ค.
ํ์ฌ๋ ์๋ต ๊ตฌ์ฑ์ด ๋จ์ํ์ฌ builder ํจํด์ ์ฌ์ฉํ์ง ์๊ณ ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋ ๋ฐฉ์(Response.success())์ ์ฌ์ฉํ๊ณ ์์ง๋ง, ํฅํ ์๋ต์ ํค๋๋ ์ฟ ํค ๋ฑ ์ถ๊ฐ์ ์ธ ์์๋ฅผ ์ธ๋ฐํ๊ฒ ์ ์ดํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ builder ํจํด์ ๋์
ํ ์์ ์
๋๋ค.
ํ์ฌ ์ํฉ์์๋, ์ ์ ํฉํ ๋ฆฌ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋ฏธ ์ ๋ฌ(ok, badRequest ๋ฑ)๊ณผ ๊ฐ๊ฒฐํจ ์ธก๋ฉด์์ ์ฅ์ ์ด ์์ด ์ด ๋ฐฉ์์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
์๋ต ์์
Response Entity์ Response ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ ์๋ต์ ์ฑ๊ณต ์์๋ ์๋์ ๊ฐ์ต๋๋ค.
{
"status": 200,
"message": "success",
"data": {
"id": 2,
"totalValue": 2130.00
}
}
HTTP ์ํ ์ฝ๋์ ๋ฉ์์ง, ๊ทธ๋ฆฌ๊ณ ์ค์ ๋ฐ์ดํฐ๊ฐ ๋ช
ํํ๊ฒ ๊ตฌ๋ถ๋์ด ์์์ ํ์ธํ ์ ์์ต๋๋ค.
Global Exception Handler
์ ์ญ ์์ธ ์ฒ๋ฆฌ(GlobalExceptionHandler)๋ฅผ ๋์
ํ์ฌ ๋ชจ๋ ์์ธ์ ๋ํด ์ผ๊ด๋ ์๋ฌ ์๋ต์ ๋ฐํํ๋๋ก ๊ตฌ์ฑํ์ต๋๋ค. ์ ์ญ ์์ธ ์ฒ๋ฆฌ๊ธฐ๋ HTTP ์์ฒญ ์ฒ๋ฆฌ ๊ณผ์ ์์ ๋ฐ์ํ ์์ธ๋ฅผ ์ก์ Response ๊ฐ์ฒด๋ก ๋ณํํ์ฌ ํด๋ผ์ด์ธํธ์ ์ ๋ฌํฉ๋๋ค.
package com.example.module_utility.response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public ResponseEntity<Response<Void>> handleRuntimeException(RuntimeException e) {
Response<Void> response =Response.error(500, e.getMessage());
return new ResponseEntity<>(response,HttpStatus.INTERNAL_SERVER_ERROR);
}
}
์ด์ ์ Response class๋ ๋จ์ POJO ์ฝ๋์๊ธฐ์ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋ ํ์๊ฐ ์์์ผ๋, Global Exception Handler๋ @RestControllerAdvice ์ด๋
ธํ
์ด์
์ด ๋ถ์ ์ปดํฌ๋ํธ์
๋๋ค. ์ด๋ ์คํ๋ง ์ปจํ
์ด๋์ ๋น์ผ๋ก ๋ฑ๋ก๋์ด์ผ๋ง ๋์ํ๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ๊ฐ์ ์ปดํฌ๋ํธ ์ค์บ ์ค์ ์ด ์ ๋๋ก ๋์ด ์์ง ์์ผ๋ฉด, ํด๋น ๋น์ด ๋ฑ๋ก๋์ง ์์ ์์ธ ์ฒ๋ฆฌ ๋ก์ง์ด ์คํ๋์ง ์์ต๋๋ค.
์ค์บ ์ค์
@SpringBootApplication(scanBasePackages = {"com.example.module_exchange", "com.example.module_utility"})
์์ ๊ฐ์ด scan์ด ํ์ํ ํจํค์ง๋ฅผ ๋ช
์ํด ์ฃผ์์ต๋๋ค.
์๋ต ์์
throw new RuntimeException("์์ก ๋ถ์กฑ: ์ถ๊ธ ๊ธ์ก์ด ๊ณ์ข ์์ก๋ณด๋ค ํฝ๋๋ค.");
throw๋ก ์์ธ๋ฅผ ๋์ง๋ฉด, ์ ์ญ ์์ธ ์ฒ๋ฆฌ๊ธฐ๋ ์ด๋ฅผ ๊ฐ๋ก์ฑ์ ๋ค์๊ณผ ๊ฐ์ด ์๋ตํฉ๋๋ค
{
"status": 500,
"message": "์์ก ๋ถ์กฑ: ์ถ๊ธ ๊ธ์ก์ด ๊ณ์ข ์์ก๋ณด๋ค ํฝ๋๋ค."
}
runtimeException์ ํด๋นํ๋ 500 status์ ํจ๊ป error message๊ฐ ๋ฐํ๋ฉ๋๋ค.
์ด์ฒ๋ผ Response ๊ฐ์ฒด๋ฅผ ํ์ฉํ๋ฉด, HTTP ์ํ ์ฝ๋์ ๋ฉ์์ง๊ฐ ํญ์ ๋์ผํ ๊ตฌ์กฐ๋ฅผ ์ ์งํ๊ฒ ๋์ด ํด๋ผ์ด์ธํธ ์ธก์ ํ์ฑ์ด๋ ์๋ฌ ํธ๋ค๋ง์ด ํ์ธต ์์ํด์ง๋๋ค. ๋ํ, ์ถ๊ฐ์ ์ธ ํ๋(์: timestamp, ์ค๋ฅ ์ฝ๋ ๋ฑ)๋ฅผ ์ถํ ํ์ฅํ ๋์๋ ๋ชจ๋ ์๋ฌ ์๋ต์ ์ผ๊ด๋๊ฒ ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก, ์ ์ฒด API์ ์์ ์ฑ๊ณผ ๊ฐ๋
์ฑ์ด ๋์์ง๋๋ค.
์ฐธ๊ณ
https://woodcock.tistory.com/19
'๐ชBackend > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] DI(์์กด์ฑ ์ฃผ์ ): ์ ํ์ํ๊ณ ์ด๋ป๊ฒ ์ฌ์ฉํ ๊น? (5) | 2025.02.07 |
---|