첫 번째 REST API 만들기

IKNOW·2023년 12월 21일
0

모든 마이크로 서비스로 구성된 시스템에서 커뮤니케이션은 핵심이다. 어떤 서비스도 고립되어 있지 않다. 마이크로 서비스를 연결하기 위한 수많은 메커니즘이 있지만, 인터넷을 모방하는 것으로 시작하는 경우가 많다.

api는 개발자가 라이브러리, 다른 애플리케이션 등 다른 코드를 사용할 수 있도록 작성하는 인터페이스다.

REST: representational state transfer의 약자로 한 app 다른 app과 통신할 때 app A는 현재 상태를 가져오고 app B는 통신 호출간 상태를 유지하지 않는다는 약간 난해한 의미. 약간 풀어서 설명하면 두 프로그램이 서로 대화 할 때, A는 B에게 자신이 어떤 상태인지 알려주면서 대화한다. 즉, app B는 대화 기록이나 상태를 따로 저장하지 않고 app A가 필요한 모든 정보를 갖고 대화의 참여하는 것.

이런 방식을 사용하는 이유:

  • 두 app간 통신이 끊어지거나 B app이 꺼지고 다시 시작해도 대화 과정을 잃어버리지 않을 수 있음. A app이 요청을 보낼때 상태를 포함해서 보내기 때문에!
  • B app이 내용을 저장할 필요가 없음. A가 저장하고 있기 때문에
  • 인터페이스만 할면 서로 다른 app들이 쉽게 연결될 수 있음.

이러한 개념을 stateless service라고도 함.

간단한 도메인 만들기

간단한 coffee 클래스를 작성한다.

coffee클래스에는 coffee를 고유하게 실별하는 데 사용되는 id 필드,

커피를 이름으로 설명하는 name필드

id를 final필드로 선언하여 절대 수정할 수 없도록 설정해야한다. 그렇기 때문에 Coffee 클래스의 인스턴스를 생성할 때 id필드를 할당해야하며 뮤테이터 메서드(set method)가 없다.

두개의 생성자를 작성한다. 먼저 작성된 생성자는 id, name을 매개변수로 받는 생성자이고 다른 하나는 coffee를 생성할 때 id가 제공되지 않는 경우 고유 식별자를 제공하는 생성자이다.

다음으로는 getter, setter(or accessor, mutator) 메서드를 작성한다.

RestController

웹 개발에서 데이터, 전달, 표현(Model, Controller, View)를 분리하는 Spring MVC라는 방식이 있는데 이 방식을 사용할 때 @RestController를 사용한다.

@Controller어노테이션은 @Componenet어노테이션의 별칭으로 애플리케이션이 시작되면 해당 클래스에서 Spring Bean이 생성된다.

Spring Bean이란 애플리케이션의 내부의 Spring IoC(Inversion of Control) 컨테이너에서 생성 및 관리ㅗ되는 객체이다.

@Controller 어노테이션이 있는 클래스는 Model객체를 수용하여 프레젠테이션 레이어에 모델 기반 데이터를 제공하고 ViewResolver와 함께 작동하여 애플리케이션이 렌더링 된 특정 뷰를 표시하도록 지시한다.

클래스 또는 메서드에 @ResopnseBody어노테이션을 추가하여 형식이 지정된 응답을 Json 또는 XML구문과 같은 데이터 형식으로 반환하도록 컨트롤러 클래스에 지시할 수 있다. 이렇게 하면 메서드의 객체/이터러블 반환값이 모델의 일부로 반환되는 대신 웹 요청에 대한응답의 전체 본문이 된다.

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "HelloWorld!";
    }
}

RestController 어노테이션은 @Controller와 @ResponseBody를 하나의 어노테이션으로 결합하여 코드를 간소화 하고 의도를 보다 명확하게 표현하는 편의 표기법이다.

컨트롤러 클래스에 @RestController어노테이션을 추가하여 Rest API생성을 시작할 수 있다.

REST API 시작하기

REST API는 객체를 다루는 데, 이 객체들은 혼자 있거나, 관련된 객체들의 그룹으로 존재할 수 있다. 예제의 커피 클래스로 예를 들면, 특정 커피 하나를 조회할 수도 있고, 모든 커피 또는 다크 로스팅 커피, 특정 ID범위 내의 커피 등을 조회 할 수 있다.

먼저 커피 객체 목록을 생성하여 여러개의 커피 객체를 반환하는 메서드를 지원하도록 하였으며, 클래스 정의에 나와 있다. 멤버 변수 유형의 상위 인터페이스로 List를 선택했지만 실제로는 ArrayList를 할당한다.

@RestController
class RestApiDemoController{
    private List<Coffee> coffees = new ArrayList<>();
}

가장 높은 수준의 타입을 사용하면 api를 깔끔하게 만족시킬 수 있다.

예상대로 작동하는지 확인하기 위해

객체 생성시 커피 목록을 채우는 코드를 추가하여 REstApiDemoController클래스에 대한 생성자를 생성한다.

@RestController
class RestApiDemoController{
    private List<Coffee> coffees = new ArrayList<>();

		public RestApiDemoController() {
				coffees.addAll(List.of(
						new Coffee("Cafe Cereza"),
						new Coffee("Cafe Ganador"),
						new Coffee("Cafe Lareno"),
						new Coffee("Cafe Tres Pontas")
				));
		}
}

RestApiDemocontroller클래스에서 멤버 변수 coffees로 표시되는 리스트를 반환하는 메서드를 생성할 수 있다. 어떤 이터러블 유형이든 이 API의 기능을 만족시키기 때문에 Iterable를 사용할 수도 있다.

GET-ing

@RequestMapping을 사용하여 커피 목록 가져오기

@RestController
class RestApiDemoController{
    private List<Coffee> coffees = new ArrayList<>();

		public RestApiDemoController() {
				coffees.addAll(List.of(
						new Coffee("Cafe Cereza"),
						new Coffee("Cafe Ganador"),
						new Coffee("Cafe Lareno"),
						new Coffee("Cafe Tres Pontas")
				));
		}

		@RequestMapping(value="/coffees", method = RequestMethod.GET)
		Iterable<Coffee> getCoffees() {
				return coffees;
		}
}

@RequestMapping어노테이션을 사용하면 /coffees라는 경로로 들어오는 요청 중에서 HTTP GET 요청만 처리하도록 설정 할 수 있다. 이렇게 하면 해당 메서드는 데이터 조회를 담당하고, 다른 종류의 수정 작업은 처리하지 않게 된다. SpringBott는 Spring Web에 포함된 Jackson라이브러리를 통해 객체를 JSON이나 다른 형식으로 자동 변환해주기 때문에 복잡한 작업 없이 데이터를 쉽게 주고 받을 수 있다.

여기서 @GetMapping어노테이션을 사용하면 Get 요청만 허용한다는 의미를 내포하기 때문에 @RequestMapping과는 달리 method타입을 명시할 필요없어 보일러 플레이트가 줄어들기 때문에 가독성의 이점이 생긴다.

@GetMapping("/coffees")
Iterable<Coffee> getCoffees() {
		return coffees;
}

PathVariable

특정 커피 하나를 검색하는 법

지정된 경로의 {id} 부분은 URI(uniform resource identifier) 변수이며, 해당 값은 @PathVariable로 어노테이션하여 id 메서드 매개변수를 통해 getCoffeeById메서드에 전달된다.

@GetMapping("/coffees/{id}")
Optional<Coffee> getCoffeeById(@PathVariable String id) {
		for (Coffee c: coffees) {
				if (c.getId().equals(id)) {
						return Optional.of(c);
				}
		}
		return Optinal.empty();
}

POST-ing

POST는 비교적 간단하다.

SpringBott의 자동 마샬링덕분에 작성된 서비스는 지정된 커피 세부 정보를 coffee 객체로 수신하고 이를 커피 목록에 추가한다.

다음 스프링부트에서 기본적으로 JSON으로 자동 언마샬링된 Coffee 객체를 요청하는 애플리케이션에 반환한다.

@PostMapping("/coffees")
Coffee postCoffee(@RequestBody Coffee coffee) {
		coffees.add(coffee);
		return coffee;
}

PUT-ting

일반적으로 PUT요청은 기존 리소스를 업데이트 하는데 사용된다.

IERF의 HTTP/1.1문서에 따르면 PUT요청은 지정된 리소스가 있는 경우 해당 리소스를 업데이트 해야하며, 리소스가 아직 존재하지 않는 경우 리소스를 생성해야 한다.
https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.4

아래 코드는 특정 id를 갖는 커피를 탐색하고 업데이트한다. 만약 해당 id의 커피가 존재하지 않는다면 직접 생성한다.

@PutMapping("/coffees/{id}
Coffee putCoffee(@PathVariable String id, @RequestBody Coffee coffee) {
		int coffeeIndex = -1;

		for (Coffee c: coffees) {
				if (c.getId().equals(id)) {
						coffeeIndex = coffees.indexOf(c);
						coffees.set(coffeeIndex, coffee);
				}
		}
		return (coffeeIndex == -1) ? postCoffee(coffee) : coffee;
}

DELETE-ing

리소스를 삭제하려면 DELETE요청을 삭제한다. 커피의 식별자를 @PathVariable로 받아들이고 removeIf() Collection메서드를 사용해서 리스트에서 해당 커피를 제거하는 메서드를 만든다.

removeIf()는 Predicate를 받아들이므로 평가할 람다를 만들어서 제거할 커피에 대해 참이라는 bool값을 반환할 수 있다.

@DeleteMapping("/coffees/{id}")
void deleteCoffee(@PathVariable String id) {
		coffees.removeIf(c -> c.getId().equals(id));
}

AND MORE

먼저 코드의 반복을 줄이기 위해 RestApiDemoController 에서 클래스 내의 모든 메서드에 공통으로 적용되는 URI 매핑 부분을 클래스 수준의 @RequestMapping 어노테이션인 “/coffees”로 올린다.

@RestController
@RequestMapping("/coffees")
class RestApiDemoController {
		private List<Coffee> coffees = new ArrayList<>();

		public RestApiDemoController() {
				coffees.addAll(List.of(
						new Coffee("Cafe Cereze"),
		...

IERF문서를 참고하면 HTTP 상태 코드는 GET 메서드에는 지정되어 있지 않고 POST, DELETE에는 제안 되어 있지만 PUT 메서드 응답에는 필요하다.

putCoffee메서드는 업데이트 되거나 생성된 Coffee 객체만 반환하는 대신 해당 Coffee와 적절한 HTTP 상태 코드가 포함된 ResponseEnrity를 반환해야한다. 요청 받은 커피가 아직 존재하지 않으면 201(Created), 커피가 존재하고 업데이트 된 경우 200(OK)를 반환해야 한다

@PutMapping("/{id}")
ResponseEntity<Coffee> putCoffee(@PathVariable String id, @RequestBody Coffee coffee) {
    int coffeeIndex = -1;

		for (Coffee c: coffees) {
				if (c.getId().equals(id)) {
						coffeeIndex = coffees.indexOf(c);
						coffees.set(coffeeIndex, coffee);
				}
		}
		return (coffeeIndex == -1) ?
				new ResponseEntity<>(postCoffee(coffee), HttpStatus.CREATED):
				new ResponseEntity<>(coffee, HttpStatus.OK);

확인하기

목록에 미리 채워진 네 가지 커피가 모두 표시된다.

특정 id의 커피를 요청할 경우 해당하는 커피의 entity를 제대로 반환하고 있다.

정상적으로 생성되는 것 또한 확인 할 수 있다.

PUT 메서드로 요청을 보냈을때는 해당 id가 존재하지 않을 경우 201 상태코드를 반환하는 것을 확인 할 수 있다.

profile
조금씩,하지만,자주

0개의 댓글

관련 채용 정보