@Service
@Transactional
@Slf4j
public class KisService {
private final WebClient webClient;
private final String baseUrl;
private final String appKey;
private final String appSecretKey;
@Autowired
public KisService(WebClient webClient, @Value("${spring.kis-api.endpoint-url}") String baseUrl, @Value("${spring.kis-api.app-key}")String appKey, @Value("${spring.kis-api.app-secret-key}") String appSecretKey) {
this.webClient = webClient;
this.baseUrl = baseUrl;
this.appKey = appKey;
this.appSecretKey = appSecretKey;
}
// Controller에서 이 메소드를 호출한다.
public Mono<String> getApprovalKeyFromKis() {
Map<String, String> bodyMap = new HashMap<>();
bodyMap.put("grant_type", "client_credentials");
bodyMap.put("appkey", appKey);
bodyMap.put("secretkey", appSecretKey);
String fullUrl = baseUrl + "/oauth2/Approval"; // 호스트와 경로를 조합
return webClient.post() // post로 요청
.uri(fullUrl) // 전체 URL을 명시적으로 지정
.contentType(MediaType.APPLICATION_JSON) // Content-Type: application/json
.body(BodyInserters.fromValue(bodyMap)) // json 형식으로 변환해서 응답 바디에 넣음
.retrieve() // 요청하고 응답받음
.bodyToMono(String.class); // 바디를 MONO 타입으로 변환
}
}
클라이언트 쪽에서 "localhost:8080/approval"로 요청을 보내면, KIS에 접근하기 위한 ApprovalKey를 반환하는 컨트롤러이다.
@RestController
@RequestMapping("/approval")
@RequiredArgsConstructor
public class ApprovalKeyController {
@Autowired
private final KisService kisService;
@GetMapping("/")
@Operation(
summary = "get Kis Approval Key",
responses = {
@ApiResponse(
responseCode = "200",
description = "Successful operation",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = SuccessResponse.class)
)
),
@ApiResponse(
responseCode = "400",
description = "Bad request",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class)
)
),
@ApiResponse(
responseCode = "401",
description = "Bad credentials",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = ErrorResponse.class)
)
)
}
)
public ResponseEntity<String> approvalKey() {
Mono<String> approvalKey = kisService.getApprovalKeyFromKis();
System.out.println(approvalKey.block());
return ResponseEntity.ok().body(approvalKey.block());
}
}
Swagger는 API 문서를 자동으로 구성하는 도구이다.
애플리케이션의 모든 End Point를 볼 수 있고, Postman 처럼 요청을 보내고 응답을 수신할 수 있다.
spring boot에서 Swagger를 사용하기 위해서는 다음의 과정을 거쳐야 한다.
의존성을 추가
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
SwaggerConfig 클래스 생성
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.components(new Components())
.info(apiInfo());
}
private Info apiInfo() {
return new Info()
.title("API Test") // API의 제목
.description("Swagger UI") // API에 대한 설명
.version("1.0.0"); // API의 버전
}
}
@Operation
어노테이션을 사용하여, 각 API에 대한 설명을 추가할 수 있다.@ApiResponses
어노테이션을 사용하면 응답 코드에 대한 정보를 나타낼 수도 있다.git의 issue는 기능 개발, 버그 수정 등의 상황을 트래킹 하기 위한 장치이다.
이슈마다 번호가 있다.
커밋 로그 마지막에 #이슈번호 를 넣으면 자동으로 git issue에 연동이 된다.
개발에서만 필요한 환경 설정 정보들이다.
appkey 같이 git에서 트래킹 하지 말아야 할 정보들을 적는다.
application.yml에서 다음과 같이 작성하면 application-dev.yml을 자동으로 설정정보로 추가한다.
spring:
profiles:
active: dev
Spring WebClient는 웹으로 API를 호출하기 위해 사용되는 Http Client 모듈 중 하나이다.
Java에서 가장 많이 사용하는 Http Client는 RestTemplate이다. 하지만, Spring이 이제부터 WebClient 쓰라고 권장했다.
RestTemplate과 WebClient의 차이점은 통신방법이 RestTemplate은 Blocking방식이고, WebClient는 Non-Blocking방식이라는거다.
WebClient를 사용하기 위해서는 WebClientConfig 클래스를 작성해야 한다.
그래야 WebClient를 스프링 빈으로 등록해서 사용할 수 있다.
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder
.build();
}
}
그리고, 외부 API를 호출해야 하는 로직에서 다음과 같이 작성한다.
// 요청 바디에 넣을 JSON 형식의 데이터를 저장할 Map
Map<String, String> bodyMap = new HashMap<>();
bodyMap.put("grant_type", "client_credentials");
bodyMap.put("appkey", appKey);
bodyMap.put("secretkey", appSecretKey);
String fullUrl = baseUrl + "/oauth2/Approval"; // 호스트와 경로를 조합
return webClient.post() // post로 요청
.uri(fullUrl) // 전체 URL을 명시적으로 지정
.contentType(MediaType.APPLICATION_JSON) // Content-Type: application/json
.body(BodyInserters.fromValue(bodyMap)) // json 형식으로 변환해서 응답 바디에 넣음
.retrieve() // 요청하고 응답받음
.bodyToMono(String.class); // 바디를 MONO 타입으로 변환
Mono는 Reactor 프로젝트에서 제공하는 Reactor 타입 중 하나로, 비동기 프로그래밍을 위한 단일 결과를 나타내는 클래스이다.
Mono는 한 개의 결과를 갖는데, 이 결과가 나타나면 스트림은 완료된다.
따라서 Mono는 0 또는 1개의 아이템을 발행한다.
Mono는 보통 단일 값이나 비동기 작업을 표현할 때 사용된다.
bodyToMono
응답 본문을 주어진 타입의 Mono로 변환한다.
여기서는 문자열로 응답을 받기를 원하기 때문에 String.class를 인자로 전달.
@Value 어노테이션은 Spring Framework에서 주입(Dependency Injection)된 값을 가져오는데 사용된다.
이 어노테이션을 사용하면 Spring의 Environment에서 값을 가져오거나, Spring의 프로퍼티 파일(application.properties 또는 application.yml)에서 값을 읽을 수 있다.
@Autowired
public KisService(WebClient webClient, @Value("${spring.kis-api.endpoint-url}") String baseUrl, @Value("${spring.kis-api.app-key}")String appKey, @Value("${spring.kis-api.app-secret-key}") String appSecretKey) {
this.webClient = webClient;
this.baseUrl = baseUrl;
this.appKey = appKey;
this.appSecretKey = appSecretKey;
}
java.lang.IllegalStateException: Cannot resolve parameter names for constructor
오류 발생
해결: DTO에 @NoArgsConstructor 넣기
즉, 기본 생성자를 만들어줌
@ModelAttribute
는 기본 생성자로 객체를 만들고 값을 set하는 방식이다.
따라서, DTO에 기본 생성자가 존재해야 @ModelAttribute
를 사용할 수 있다.