API를 작성하는 다양한 방법

사공광열·2023년 7월 19일
1

SpringBoot

목록 보기
4/8

GET API 만들기

GET API는 웹 애플리케이션 서버에서 값을 가져올때 사용하는 API입니다.
실무에서는 HTTP 메서드에 따라 컨트롤러 클래스를 구분하지 않습니다만 여기서는
메서드별로 클래스를 생성합니다.

@RestController 
@RequestMapping("/api/v1/get-api")
public class GetController {

}

컨트롤러에 @RestController @RequestMapping을 붙여 내부에 선언된 메소드는 공통 URL을 설정합니다.

@RequestMapping으로 구현하기

@RequestMapping 어노테이션을 별다른 설정 없이 선언하면 HTTP 모든 요청을 받습니다. 그러나 GET 형식의 요청만 받고싶으면 어노테이션에 별도 설정이 필요합니다.

package com.example.api.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello(){
        return "KwangyoulSagong";
    }


}

method 요소의 값을 RequestMethod.GET을 설정하면 요청 형식의 GET으로만 설정할 수 있습니다.

@RequestMapping("/api/v1/get-api") 쉽게 말해 이거는 공통 URL이고

@RequestMapping(value = "/hello", method = RequestMethod.GET)이것은 GET으로 설정한 /api/v1/get-api/hello 입니다.

스프링 4.3버전이후로는 @RequestMapping을 사용하지않고 특별한 경우를 제외한 HTTP 메서드에 맞는 어노테이션

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • DeleteMapping

작성한 메소드를 Talend API Tester 에 send를 하면
Response 응답한 값이 나옵니다

매개변수 없는 GET 메서드 구현

package com.example.api.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {

//    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    @GetMapping(value = "/name")
    public String getName(){
        return "사공광열";
    }


}

매개변수가 없는 요청은 http://localhost:8080/api/v1/get-api/name 요청할 때 스프링 부트 애플리케이션이 정해진 응답을 반환합니다.

@PathVariable을 활용한 GET 메서드 구현

실무 환경에서는 매개변수를 받지 않는 메서드는 거의 쓰이지 않습니다. 웹 통신의 기본 목적은 데이터를 주고받는 것이기 때문에 대부분 매개변수를 작성하게 됩니다.
주로 매개변수 받을때 URL자체에 값을 담아 요청합니다.

package com.example.api.controller;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
    
    @GetMapping(value = "variable1/{variable}")
    public String getVariable1(@PathVariable String variable){
        return variable;
    }



}

http://localhost:8080/api/v1/get-api/variable1/{String 값}

중괄호로{} 표시된 위치의 값을 받아 요청하는것을 알수 있습니다.

값을 간단히 전달할 떄 주로 사용하는 방법입니다. GET요청에서 많이 사용됩니다.

@GetMapping 언노테이션 값으로 URL을 입력할 때 중괄호를 사용해 어느 위치에서 값을 받을지 지정합니다.

메서드에 매개변수와 그 값을 연결하기 위해 @PathVariable을 명시하며, @GetMapping 어노테이션과 변수이름이 동일해야합니다.

만약 @GetMapping 어노테이션에서 지정한 변수의 이름과 메서드 배개변수의 이름을 동일하게 맞추기가 어려우면 @PathVariable 뒤에 괄호를 열어 @GetMapping 어노테이션 의 변수명을 지정합니다.

package com.example.api.controller;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {


    @GetMapping(value = "variable2/{variable}")
    public String getVariable1(@PathVariable("variable") String var){
        return var;
    }



}

조금더 풀어 쓰면

@GetMapping(value = "variable2/{variable}")
    public String getVariable1(@PathVariable(value="variable") String var){
        return var;
    }

@RequestParam을 활용한 GET 메서드 구현

URL 경로에 값을 담아 요청 외에 쿼리 형식으로 값을 전달 할 수 있습니다.
URL에서 '?'를 기준으로 우측에 '{키}={값}' 형태로 구성된 요청을 전송하는 방법입니다.

 @GetMapping(value = "/request1")
    public String getRequestParma1(
            @RequestParam String name,
            @RequestParam String email,
            @RequestParam String organization
    ){
        return name + " " + email + " " + organization;
    }

http://localhost:8080/api/v1/get-api/request1?name=kwangyoulsagong&email=sgky0511@naver.com&organization=value

쿼리스트링(query string)이 명시돼 있어서 변수의 이름이 모두 적혀 있기 때문에 이 값을 기준으로 메서드 매개변수에 이름을 매핑해서 값들이 이렇게 출력됩니다.

어떤값이 들어올지 모른다면 Map객체 사용하면 됩니다.

   @GetMapping("/request2")
      public String getRequestParam2(@RequestParam Map<String, String> param){
          StringBuilder sb = new StringBuilder();
          param.entrySet().forEach(map ->{
              sb.append(map.getKey() + " : " + map.getValue() + "\n");
          });
          return sb.toString();
      }

http://localhost:8080/api/v1/get-api/request2?name=kwangyoulsagong&email=sgky0511@naver.com&organization=value

값에 상관없이 요청을 받을 수 있습니다. 예를들어 회원가입 관련 API에서 사용자는 회원 가입을 하면서 ID 같은 필수 항목이 아닌 취미 같은 선택 항목에 대해서는 값을 기입하지 않는 경우가 있습니다. 이러한 경우에는 매개변수의 항목이 일정하지 않을 수 있어 Map 객체로 받는것이 효율적입니다.

URI 와 URL의 차이

URL은 우리가 흔히 말하는 웹 주소를 의미하면, 리소스가 어디에 있는지 알려주기 위한 경로입니다.
URI는 특정 리소스를 식별할 수 있는 식별자를 의미합니다.

웹에서는 URL을 통해 리소스가 어느 서버에 위치해 있는지 알 수 있으며, 그 서버에 접근해서 리소스에 접근하기 위해서는 대부분 URI가 필요합니다.

DTO 객체를 활용한 GET 메서드 구현

DTO란?

DTO는 Data Transfer Object 약자로, 다른 레이어 간의 데이터 교환에 활용합니다. 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체입니다.

DTO와 VO 차이

VO는 데이터 그자체로 의미가 있는 객체를 의미합니다. 가장 특징적인 부분은 읽기전용(Read-Only)으로 설계한다는 점입니다. 값을 변경할수 없게 만들어 데이터의 신뢰성을 유지해야 합니다.

DTO는 데이터 전송을 위해 사용되는 데이터 컨테이너로 볼 수 있습니다. 즉, 같은 애플리케이션 내부에서 사용되는 것이 아니라 다른 서버(시스템)로 전달하는 경우에 사용됩니다.

여기서 DTO는 다른 레이어 간의 데이터 교환에 활용된다고 설명했습니다. 여기서 레이어는 애플리케이션 내부에 정의된 레이어일 수도 있고 인프라 관점에서의 서버 아키텍처 상의 레이어일 수도 있습니다. 이러한 개념의 혼용이 DTO와 VO의 차이를 흐리게 만듭니다.

DTO 구현

package com.example.api.dto;

public class MemberDto {
    private String name;
    private String email;
    private String organization;

    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name=name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getOrganization() {
        return organization;
    }

    public void setOrganization(String organization) {
        this.organization = organization;
    }

    @Override
    public String toString() {
        return "MemberDto{" +
                "name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", organization='" + organization + '\'' +
                '}';
    }
}

DTO 클래스에는 전달하고자 하는 필드 객체를 선언하고 getter/setter 메서드를 구현합니다. DTO 클래스에 선언된 필드는 컨트롤러의 메서드의 쿼리 파라미터의 키와 매핑됩니다.

 @GetMapping("/request3")
        public String getRequestParam3(MemberDto memberDto){
        	//return memberDto.getName() + " " + memberDto.getEmail() + " " + memberDto.getOrganization();
            return memberDto.toString();
        }

파라미터가 많을경우 코드 가독성을 위해 return memberDto.toString(); 이렇게 구현합니다.

POST API 만들기

POST API는 웹 애플리케이션을 통해 데이터베이스 등의 저장소에 리소스를 저장할 때 사용되는 API 입니다.

GET API는 URL의 경로나 파라미터에 변수를 넣어 요청을 보냈다면

POST API는 저장하고자 하는 리소스나 값을 HTTP 바디(body)에 담아 서버에 전달합니다.

package com.example.api.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/post-api")
public class PostController {
    @RequestMapping(value = "/domain", method = RequestMethod.POST)
    public String postExample(){
        return "Hello Post API";
    }
}

method 요소를 RequestMethod.POST로 설정하는 부분을 제외하면 GET API랑 동일합니다.

@RequestBody를 활용한 POST 메서드 구현

일반적으로 POST형식의 요청은 클라이언트가 서버에 리소스를 저장하는데 사용합니다. 그러므로 클라이언트의 요청 트래픽 값에 포함돼 있습니다. 즉 POST 요처에서는 리소스를 담기 위해 HTTP Body 값을 넣어 전송합니다.

작성을하면

Body 영역에 작성되는 값은 일정한 형태를 취합니다. 일반적으로 JSON(JavaScript Object Notation) 형식으로 전송됩니다.
이렇게 서버에 들어온 요청은 다음과 처리할 수 있습니다.

 @PostMapping(value = "/member")
    public String postMember(@RequestBody Map<String,Object> postData){
        StringBuilder sb = new StringBuilder();

        postData.entrySet().forEach(map ->{
            sb.append(map.getKey() + " " + map.getValue() + "\n");
        });
        return sb.toString();
    }


포스트로 요청하면 응답을 받습니다
Map 객체는 요청을 통해 어떤값이 들어오게 될지 특정하기 어려울 때 주로 사용해서 사용했습니다.

여기서 @RequestBody라는 어노테이션은 HTTP의 Body 내용을 해당 어노테이션이 지정된 객체에 매핑하는 역활입니다.

JSON이란?

JSON은 (JavaScript Object Notation)의 줄이말로 자바스크립트의 객체 문법을 따르는 문자 기반의 데이터 포맷입니다. 대체로 네트워크를 통해 데이터 전달할 때 사용하며, 문자열 형태로 작성되기 때문에 파싱하기도 쉽다는 장점이 있습니다.

DTO객체를 매개변수로 삼아 작성

 @PostMapping(value = "/member2")
    public String postMember(@RequestBody MemberDto memberDto){
        return memberDto.toString();
    }

MeberDto의 멤버 변수를 요청 메시지의 키와 매핑해 값을 가져옵니다.

PUT API 만들기

PUT API는 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트 하는 데 사용합니다. POST API와 비교하면 요청을 받아 실제 데이터베이스에 반영하는 과정(서비스로직) 차이가 있지만 컨트롤러 구현하는 방법은 POST API와 거의 동일합니다. 그 이유는 서버에 전달하기 위해 HTTP Body를 활용하기 때문입니다.

 @PutMapping("/member")
    public String putMeber(@RequestBody Map<String, Object> putData){
        StringBuilder sb = new StringBuilder();

        putData.entrySet().forEach(map->{
            sb.append(map.getKey() + " " + map.getValue() +"\n");
        });
        return sb.toString();
    }
@PutMapping(value = "/member2")
    public MemberDto putMember(@RequestBody MemberDto memberDto){
        return memberDto;
    }

String 객체와 DTO 객체 차이

toString 메서드로 인해 나름의 형식 갖춰져 전달됐지만 HEADERS 항목의 context-type을 보면 'text/plain' 으로서 결괏값으로 일반 문자열이 전달됐음을 확인할 수 있습니다.

하지만 DTO 객체에서는 context-type을 보면 'application/json' 형식으로 전달된것을 확인할 수 있습니다. @RestController 어노테이션이 지정된 클래스는 @ResponseBody를 생략할 수 있는데, 이 @ResponseBody 어노테이션은 자동으로 값을 JSON과 같은 형식으로 변환해서 저달하는 역할을 수행 합니다.

ResponseEntity를 활용한 PUT 메서드 구현

스프링 프레임워크에는 HttpEntity라는 클래스가 있습니다. HttpEntity는 다음과 같이 헤더(Header)와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행합니다.

//일부 발췌
public class HttpEntity<T> {
        
        private final HttpHeaders headers;
        
        @Nullable
        private final T body;
    }

ResponseEntity와 ResponseEntity는 HttpEntity를 상속받아 구현한 클래스입니다.
ResponseEntity는 서버에 들어온 요청에 대한 응답 데이터를 구성해서 전달할 수 있게 합니다.

//일부발췌
public class ResponseEntity<T> extends HttpEntity<T>{
        
        private final Object status;
        
    }

이 클래스를 활용하면 응답 코드 변경은 물론 Header 와 Body를 더욱 쉽게 구성할 수 있습니다.

  @PutMapping(value = "/member3")
    public ResponseEntity<MemberDto> postMemberDto3(@RequestBody MemberDto memberDto){
        return ResponseEntity
                .status(HttpStatus.ACCEPTED)
                .body(memberDto);
  }

리턴 타입을 ResponseEntity로 설정하고 리턴값을 만듭니다.
status에 넣을 수 있는 값은 다양한데. HttpStatus.ACCEPTED는 응답 코드 202를 가지고 있습니다. 즉 이 메서드 대상으로 요청을 수행하면 응답 코드가 202로 바뀝니다.

DELETE API 만들기

DELETE API는 웹 애플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용합니다. 서버에서는 클라이언트로부터 리소스를 식별할 수 있는 값을 받아 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제 하는 역할을 수행합니다. 컨틀롤러에서 받는 단계에서는 간단한 값을 받기 때문에 GET 메서드와 같이 URI에 값을 넣어 요청을 받는 형식입니다.

@PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현

@DeleteMapping(value = "/{variable}")
    public String DeleteVariable(@PathVariable String variable){
        return variable;
    }

@DeleteMapping 어노테이션에 정의한 value의 이름과 메서드의 매개변수 이름을 동일하게 설정해야 삭제할 값이 주입됩니다.

@RequestParam 어노테이션을 통해 쿼리스틀링 값도 받을 수 있습니다.

@DeleteMapping(value = "/request")
    public String DeleteVariable(@RequestParam String email){
        return "email :" + email;
    }

REST API 명세를 문서화하는 방법 - Swagger

swagger 사용하기 위해 pom.xml에 의존성 주입

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>

Swagger 설정 코드


@Configuration
@EnableSwagger2
public class SwaggerConfiguration {


    @Bean
    public Docket api(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.api"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo(){
        return new ApiInfoBuilder()
                .title("Spring Boot Open API Test with Swagger")
                .description("설명 부분")
                .version("1.0.0")
                .build();
    }

}

Swagger 더 잘 활용하기

@RequestParam을 활용한 GET 메서드에 대한 명세의 세부 내용을 설정합니다

@ApiOperation(value = "GET 메서드 예제", notes = "@RequestParam을 활용한 GET Method")
        @GetMapping(value = "/request1")
        public String getRequestParma1(
                @ApiParam(value = "이름", required = true) @RequestParam String name,
                @ApiParam(value = "이메일", required = true) @RequestParam String email,
                @ApiParam(value = "회사", required = true) @RequestParam String organization){
            return name + " " + email + " " + organization;
        }
  • @ApiOperation: 대상 API의 설명을 작성하기 위한 어노테이션입니다.

  • @ApiParam: 매개변수에 대한 설명 및 설정을 위한 어노테이션입니다. 메서드의 매개변수뿐 아니라 DTO 객체를 매개변수로 사용할 경우 DTO 클래스 내의 매개변수에도 정의할 수 있습니다.

이용이 가능합니다.

로깅 라이브러리 - Logback

로깅(logging)이란 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는것을 의미합니다.

로깅은 개발 영역 중 '비기능 요구사항'에 속합니다. 즉, 사용자나 고객에게 필요한 기능은 아니라는 의미 입니다. 하지만 로깅은 디버깅하거나 개발 이후 발생한 문제를 해결할 때 원인을 분석하는 데 꼭 필요한 요소입니다.

Logback의 특징

  • 크개 5개의 로그 레벨(TRACE, DEBUG, INFO, WARN, ERROR)을 설정할 수 있습니다
  • - ERROR: 로직 수행중에 시스템에 심각한 문제가 발생해서 애플리케이션의 작동이 불가능한 경우를 의미합니다.
    - WARN: 시스템에 에러의 원인이 될 수 있는 경고 레벨을 의미합니다.
    - INFO: 애플리케이션의 디버깅을 위한 메시지를 표현하기 위한 레벨을 의미합니다.
    - DEBUG: 애플리케이션의 디버깅을 위한 메시지를 표시하는 레벨을 의미합니다.
    - TRACE: DEBUG 레벨보다 더 상세한 메시지를 표현하기 위한 레벨을 의미합니다.

  • 실제 운영 환경과 개발 환경에서 각각 다른 출력 레벨을 설정해서 로그를 확인할 수 있습니다.

  • Logback의 설정 파일을 일정 시간마다 스캔해서 애플리케이션을 재기동하지 않아도 설정을 변경할 수 있습니다.

  • 별도의 프로그램 지원 없이도 자체적으로 로그 파일을 압축할 수 있습니다.

  • 저장된 로그 파일에 대한 보관 기간 등을 설정해서 관리할 수 있습니다.

Logback 설정 파일 예시

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <property name="LOG_PATH" value="./logs" />

    <!-- Appenders -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <pattern>[%d{yyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <file>${LOG_PATH}/info.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/info_${type}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>

    <!--TRACE > DEBUG > INFO > WARN > ERROR > OFF -->
    <!-- Root Logger -->
    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="INFO_LOG" />
    </root>
</configuration>

Logback 적용하기

위에 설정 파일에서 Appender Root Logger 영역에서 레퍼런스로 잡혀 있지는 않은지 확인한 후 제거해야합니다

private final Logger LOGGER=  LoggerFactory.getLogger(GetController.class);
        @RequestMapping(value = "/hello", method = RequestMethod.GET)
        public String getHello(){
            LOGGER.info("getHello 메서드가 호출되었습니다.");
            return "Hello World";
        }
        @GetMapping(value = "/name")
        public String getName(){
            LOGGER.info("getName 메서드가 호출되었습니다.");
            return "Kwangyoul Sagong";
        }

콘솔로그에 출력이 됩니다.

로그를 통해 컨트롤러에 들어오는 값을 확인

  @GetMapping(value = "/variable1/{variable}")
        public String getVariable1(@PathVariable String variable){
            LOGGER.info("@PathVariable 통해 들어온 값: {}", variable);
            return variable;
        }


들어오는 값도 확인이 됩니다.

profile
Interactive Developer

2개의 댓글

comment-user-thumbnail
2023년 7월 19일

정말 유익한 글이었습니다.

1개의 답글