본문 바로가기
Java/Spring

Spring) Rest API - 개요, GET (java.time 패키지 처리 관련 의존 설정)

by 박채니 2022. 9. 6.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.

 

Rest API

- HTTP를 더 HTTP 스럽게!

 

구성요소
1. uri : 자원을 표현함
    * 동사보다는 명사를 사용할 것
    * 계층구조로 표현
    * 파일확장자는 생략함.
2. method : 기본 crud(행위)는 메소드로 표현
    * POST : Create
    * GET : Read
    * PUT : Update
    * DELETE : Delete

더보기

# RestAPI
> REST(Representational State Transfer)는 HTTP/1.0과 1.1의 스펙 작성에 참여하였고 아파치 HTTP 서버 프로젝트의 공동설립자인 로이 필딩 (Roy Fielding)의 2000년 논문에서 처음 소개되었다. 

> 발표 당시의 웹이 HTTP의 설계 상 우수성을 제대로 사용하지 못하고 있는 상황을 보고 웹의 장점을 최대한 활용할 수 있는 아키텍쳐로서 REST를 소개하였고 이는 HTTP 프로토콜을 의도에 맞게 디자인하도록 유도하고 있다. 
> **REST의 기본 원칙을 성실히 지킨 서비스 디자인을 “RESTful”이라고 표현한다.**

구성요소
1. uri : 자원을 표현함
    * 동사보다는 명사를 사용할 것
    * 계층구조로 표현
    * 파일확장자는 생략함.
2. method : 기본 crud(행위)는 메소드로 표현
    * POST : Create
    * GET : Read
    * PUT : Update
    * DELETE : Delete


## 특징
`Uniform (유니폼 인터페이스)`
Uniform Interface는 URI로 지정한 리소스에 대한 조작을 통일되고 한정적인 인터페이스로 수행하는 아키텍처 스타일

`Stateless (무상태성)`
REST는 무상태성 성격임. 다시 말해 작업을 위한 상태정보를 따로 저장하고 관리하지 않음.
세션 정보나 쿠키정보를 별도로 저장하고 관리하지 않기 때문에 API 서버는 들어오는 요청만을 처리.
서비스의 자유도가 높아지고 서버에서 불필요한 정보를 관리하지 않음으로써 구현이 단순.

`Cacheable (캐시 가능)`
REST의 가장 큰 특징 중 하나는 HTTP라는 기존 웹표준을 그대로 사용하기 때문에, 웹에서 사용하는 기존 인프라를 그대로 활용함. 따라서 HTTP가 가진 캐싱 기능이 적용 가능. 
HTTP 프로토콜 표준에서 사용하는 Last-Modified태그나 E-Tag를 이용하면 캐싱 구현이 가능함.

`Self-descriptiveness (자체 표현 구조)`
REST의 또 다른 큰 특징 중 하나는 REST API 메시지만 보고도 이를 쉽게 이해 할 수 있는 자체 표현 구조로 되어 있음.

`Client - Server 구조`
REST 서버는 API 제공, 클라이언트는 사용자 인증이나 컨텍스트(세션, 로그인 정보)등을 직접 관리하는 구조로 각각의 역할이 확실히 구분되기 때문에 클라이언트와 서버에서 개발해야 할 내용이 명확해지고 서로간 의존성이 줄어듬.

`계층형 구조`
REST 서버는 다중 계층으로 구성될 수 있으며 보안, 로드 밸런싱, 암호화 계층을 추가해 구조상의 유연성을 둘 수 있고 PROXY, 게이트웨이 같은 네트워크 기반의 중간매체를 사용할 수 있음.


## Rest작성시 유의사항
* URI는 정보의 자원을 표현해야 한다.
    * 리소스명은 동사보다 명사를 사용할 것
    * /(슬래쉬)로 계층관계 표현
    * URI 마지막은 /(슬래쉬)를 사용하지 않음.
    * 파일확장자는 사용하지 않음 - Accept header를 이용해서 파일종표현
    * _(언더스코어)대신 가독성을 위해 -(대쉬)를 사용함.
* 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.

 

restAPI extension

https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo

 

Advanced REST client

The web developers helper program to create and test custom HTTP requests.

chrome.google.com

 


GET

 

Controller

DevRestController

(이하 service, dao 코드 생략)

@RequestMapping("/dev")
@Controller
@Slf4j
public class DevRestController {
	@Autowired
	DemoService demoService;
	
	@GetMapping
	@ResponseBody
	public List<Dev> dev() {
		return demoService.selectDev();
	}
}

ARC에서 요청을 보냈더니 이와 같은 에러메세지가 출력되는 것을 확인할 수 있습니다.

`java.time.LocalDateTime` not supported by default: add Module &quot;com.fasterxml.jackson.datatype:jackson-datatype-jsr310&quot; to enable handling (through reference chain: java.util.ArrayList[0]-&gt;com.ce.spring2.demo.model.dto.Dev[&quot;createdAt&quot;])

jackson에서 데이터 바인드에 java.time 패키지를 처리할 수 있는 기능이 빠져있어서 오류가 발생하였고, 관련 의존을 추가해줍니다.

 

pom.xml

java.time 패키지 클래스를 json으로 변환하기 위한 의존

<!-- java.time 패키지 클래스를 json으로 변환하기 위한 의존 -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.13.3</version>
</dependency>

 

실행 결과

데이터를 잘 가져오는 것을 확인할 수 있습니다.

 

모든 메소드가 객체를 반환한다면, @ResponseBody 어노테이션을 작성해야하는 번거로움이 있습니다.

이를 해결하기 위해 클래스 단에 @RestController 어노테이션을 이용해줍니다.

 

@RestController

- @Controller 기능 + 모든 메소드에 @ResponseBody 처리가능

@RequestMapping("/dev")
@RestController
@Slf4j
public class DevRestController {
	@Autowired
	DemoService demoService;
	
	@GetMapping
	public List<Dev> dev() {
		return demoService.selectDev();
	}
}

 


GET - 한 건 조회

 

@PathVariable

- 경로 변수

- url에 포함된 특정 값을 변수 처리

@GetMapping("/{no}")
public Dev dev(@PathVariable int no) {
    log.debug("no = {}", no);
    return demoService.selectOneDev(no);
}

@콘솔출력값
DEBUG: com.ce.spring2.demo.controller.DevRestController - no = 1

 

존재하지 않는 no를 입력한다면?

아무것도 조회되지 않았기 때문에 null이 리턴 되었을 것이고, 그로 인해 출력되는 데이터가 없습니다.

하지만, StatusCode가 정상처리를 의미하는 '200'이 리턴된 것을 확인할 수 있습니다.

404가 더 적합해보이므로, 만일 데이터가 없다면 '404' 처리 되도록 해보겠습니다.

 

데이터가 없을 시, 404 처리

@GetMapping("/{no}")
public ResponseEntity<?> dev(@PathVariable int no) {
    log.debug("no = {}", no);
    Dev dev = demoService.selectOneDev(no);

    if(dev != null) {
        return ResponseEntity.ok(dev);			
    } else {
        return ResponseEntity.notFound().build(); // builder패턴
    }
}

존재하지 않는 값을 입력하니 404 처리가 된 것을 확인할 수 있습니다.

 


GET - 한 건 조회

 

@GetMapping("/lang/{lang}")
public ResponseEntity<?> dev(@PathVariable String lang) {
    List<Dev> devList = demoService.selectDev();
    List<Dev> resultList = new ArrayList<>();

    for(Dev dev : devList) {
        List<String> langList = Arrays.asList(dev.getLang());
        if(langList.contains(lang)) {
            resultList.add(dev);
        }
    }

    if(resultList.isEmpty()) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(resultList);
}

 

 

존재하지 않는 lang 조회

 

 

대소문자 구분하지 않고 요청할 경우

 

방법 1) 초기화 블록 이용

@GetMapping("/lang/{lang}")
public ResponseEntity<?> dev(@PathVariable String lang) {
    lang = lang.toLowerCase();

    List<Dev> devList = demoService.selectDev();
    List<Dev> resultList = new ArrayList<>();

    for(Dev dev : devList) {
        List<String> langList = new ArrayList<String>() {
            {
                for(String _lang : dev.getLang()) {
                    add(_lang.toLowerCase());
                }
            }
        };
        log.debug("{}", langList);
        if(langList.contains(lang)) {
            resultList.add(dev);
        }
    }

    if(resultList.isEmpty()) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(resultList);
}

@콘솔출력값
DEBUG: com.ce.spring2.demo.controller.DevRestController - [java, python]
DEBUG: com.ce.spring2.demo.controller.DevRestController - [python]
DEBUG: com.ce.spring2.demo.controller.DevRestController - [javascript]

모두 소문자로 변환되어 처리되었기 때문에 'java'로 값을 넘겨줘도 잘 출력되는 것을 확인할 수 있습니다.

 

방법 2) 커스텀 IgnoreCase 메소드 생성

String#equalsIgnoreCase() → 대소문자를 구분하지 않고 문자열을 비교

@GetMapping("/lang/{lang}")
public ResponseEntity<?> dev(@PathVariable String lang) {
    List<Dev> devList = demoService.selectDev();
    List<Dev> resultList = new ArrayList<>();

    for(Dev dev : devList) {
        List<String> langList = Arrays.asList(dev.getLang());
        if(containsIgnoreCase(langList, lang)) {
            resultList.add(dev);
        }
    }

    if(resultList.isEmpty()) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(resultList);
}

private boolean containsIgnoreCase(List<String> strList, String str) {
    for(String s : strList) {
        if(s != null && s.equalsIgnoreCase(str)) {
            return true;
        }
    }
    return false;
}