Application Programming Interface
응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스를 뜻한다. 주로 파일 제어, 창 제어, 화상 처리, 문자 제어 등을 위한 인터페이스를 제공한다.
REpresentational State Transfer
어떤 자원에 대해 CRUD(Create, Read, Update, Delete) 연산을 수행하기 위해 URI(Resource)로 요청을 보내는 것으로, Get, Post 등의 방식(Method)을 사용하여 요청을 보내며, 요청을 위한 자원은 특정한 형태(Representation of Resource)으로 표현된다
Resource
서버는 Unique한 ID를 가지는 Resource를 가지고 있으며, 클라이언트는 이러한 Resource에 요청을 보낸다. 이러한 Resource는 URI에 해당한다.
Method
서버에 요청을 보내기 위한 방식으로 GET, POST, PUT, PATCH, DELETE가 있다. CRUD 연산 중에서 처리를 위한 연산에 맞는 Method를 사용하여 서버에 요청을 보내야 한다.
Representation of Resource
클라이언트와 서버가 데이터를 주고받는 형태로 json, xml, text, rss 등이 있다. 최근에는 Key, Value를 활용하는 json을 주로 사용한다.
일관성
리소스에 대하여 통일되고 한정적으로 수행. 클라리언트가 플렛폼에 무관하고, 특정 언어나 기술에 종속받지 않는다.
무상태성
각각의 요청을 별개의 것으로 인식하고 처리, 이전 요청이 다음 요청에 연관되어서는 안된다. 그러므로 처리방식에 일관성이 있고, 서버의 부담이 적다.
캐시 가능
HTTP웹표준을 그대로 사용하기 떄문에 웹의 기존 인프라를 그대로 활용할 수 있다. 그러므로 REST API에서도캐싱 기능을 적용할 수 있다.
서버 - 클라이언트 구조
자원을 가지고 있는 쪽이 서버, 자원을 요청하는 쪽이 클라이언트에 해당한다. 서버는 API를 제공하며 클라이언트는 사용자 인증, 세션 관리 등 역활을 확실히 구분하여 서로 간의 의존성을 줄인다.
자체 표현
요청 메세지만 보고도 이를 쉽게 이해할 수 있는 자체 표현 구조로 되어있다.
계층 구조
Rest API 서버는 다중 계층으로 구성될 수 있으며, 보안, 로드밸런싱, 암호화 등을 위한 계층을 추가하여 구조를 변경할 수 있다. 또한 Proxy, Gateway 같은 네트워크 기반의 중간 매체를 사용할 수 있게 해준다.
package com.rptp.rptpSpringBoot.core.puppy.domain;
import com.rptp.rptpSpringBoot.core.vet.domain.Vet;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Puppy {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long puppyId;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer age;
@Column(nullable = false)
private String breed;
@ManyToOne
@JoinColumn(name = "vet_id")
private Vet vet;
@Builder
public Puppy(String name, Integer age, String breed) {
this.name = name;
this.age = age;
this.breed = breed;
}
}
package com.rptp.rptpSpringBoot.core.puppy.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PuppyRepository extends JpaRepository<Puppy,Long> {
}
import lombok.*;
import javax.validation.constraints.NotBlank;
@Getter
@Setter
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class PuppyRequest {
@NotBlank
private String name;
@NotBlank
private Integer age;
@NotBlank
private String breed;
}
package com.rptp.rptpSpringBoot.core.puppy.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.rptp.rptpSpringBoot.core.puppy.domain.Puppy;
import com.rptp.rptpSpringBoot.core.vet.domain.Vet;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PUBLIC)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class PuppyResponse {
private Long puppyId;
private String name;
private Integer age;
private String breed;
private Long vetId;
private String vetName;
public static PuppyResponse of(Puppy puppy) {
Vet vet = Optional.ofNullable(puppy.getVet()).orElseGet(Vet::new);
return new PuppyResponse(
puppy.getPuppyId(),
puppy.getName(),
puppy.getAge(),
puppy.getBreed(),
vet.getVetId(),
vet.getName()
);
}
public static List<PuppyResponse> listOf(List<Puppy> puppies) {
return puppies.stream()
.map(PuppyResponse::of)
.collect(Collectors.toList());
}
}
package com.rptp.rptpSpringBoot.core.puppy.service;
import com.rptp.rptpSpringBoot.common.exceptions.ResourceNotFoundException;
import com.rptp.rptpSpringBoot.core.puppy.domain.Puppy;
import com.rptp.rptpSpringBoot.core.puppy.domain.PuppyRepository;
import com.rptp.rptpSpringBoot.core.puppy.dto.PuppyRequest;
import com.rptp.rptpSpringBoot.core.puppy.dto.PuppyResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class PuppyService {
private final PuppyRepository puppyRepository;
@Transactional
public Long savePuppy(PuppyRequest req) {
return puppyRepository.save(
Puppy.builder()
.name(req.getName())
.age(req.getAge())
.breed(req.getBreed())
.build()
).getPuppyId();
}
@Transactional(readOnly = true)
public List<PuppyResponse> findAll() {
return PuppyResponse.listOf(puppyRepository.findAll());
}
@Transactional(readOnly = true)
public PuppyResponse findPuppy(Long puppyId) {
return PuppyResponse.of(findById(puppyId));
}
private Puppy findById(Long puppyId) {
return puppyRepository.findById(puppyId)
.orElseThrow(() ->
new ResourceNotFoundException("Puppy","id",puppyId));
}
}
package com.rptp.rptpSpringBoot.core.vet.domain;
import com.rptp.rptpSpringBoot.core.puppy.domain.Puppy;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Vet {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long vetId;
@Column(nullable = false)
private String name;
private String email;
@OneToMany(mappedBy = "vet", cascade = CascadeType.MERGE)
private List<Puppy> puppyList = new ArrayList<>();
@Builder
public Vet(String name, String email) {
this.name = name;
this.email = email;
}
}
package com.rptp.rptpSpringBoot.core.vet.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface VetRepository extends JpaRepository<Vet, Long> {
}
package com.rptp.rptpSpringBoot.core.vet.dto;
import lombok.*;
import javax.validation.constraints.NotBlank;
@Getter
@Setter
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
public class VetRequest {
@NotBlank
private String name;
@NotBlank
private String email;
}
package com.rptp.rptpSpringBoot.core.vet.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.rptp.rptpSpringBoot.core.puppy.dto.PuppyResponse;
import com.rptp.rptpSpringBoot.core.vet.domain.Vet;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.stream.Collectors;
@Getter
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PUBLIC)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VetResponse {
private Long vetId;
private String name;
private String email;
private List<PuppyResponse> puppyResponses;
public static VetResponse of(Vet vet){
return new VetResponse(
vet.getVetId(),
vet.getName(),
vet.getEmail(),
PuppyResponse.listOf(vet.getPuppyList())
);
}
public static List<VetResponse> listOf(List<Vet> vets) {
return vets.stream()
.map(VetResponse::of)
.collect(Collectors.toList());
}
}
package com.rptp.rptpSpringBoot.core.vet.service;
import com.rptp.rptpSpringBoot.common.exceptions.ResourceNotFoundException;
import com.rptp.rptpSpringBoot.core.vet.domain.Vet;
import com.rptp.rptpSpringBoot.core.vet.domain.VetRepository;
import com.rptp.rptpSpringBoot.core.vet.dto.VetRequest;
import com.rptp.rptpSpringBoot.core.vet.dto.VetResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class VetService {
private final VetRepository vetRepository;
@Transactional
public Long saveVet(VetRequest req) {
return vetRepository.save(
Vet.builder()
.name(req.getName())
.email(req.getEmail())
.build()
).getVetId();
}
@Transactional(readOnly = true)
public List<VetResponse> findAll() {
return VetResponse.listOf(vetRepository.findAll());
}
@Transactional(readOnly = true)
public VetResponse findVet(Long vetId) {
return VetResponse.of(findById(vetId));
}
private Vet findById(Long vetId) {
return vetRepository.findById(vetId)
.orElseThrow(() ->
new ResourceNotFoundException("Vet","id",vetId));
}
}
INSERT INTO `board`.`vet` (`vet_id`, `email`, `name`) VALUES ('1', 'aaa@aaaa.com', '홍길동');
INSERT INTO `board`.`vet` (`vet_id`, `email`, `name`) VALUES ('2', 'bbb@bbbb.bbb', '이익준');
INSERT INTO `board`.`vet` (`vet_id`, `email`, `name`) VALUES ('3', 'ccc.dddd.com', '채송화');
INSERT INTO `board`.`vet` (`vet_id`, `email`, `name`) VALUES ('4', 'kkk.kkkk.kr', '장겨울');
INSERT INTO `board`.`puppy` (`puppy_id`, `age`, `breed`, `name`, `vet_id`) VALUES ('1', '1', '말티즈', '두부', '3');
INSERT INTO `board`.`puppy` (`puppy_id`, `age`, `breed`, `name`, `vet_id`) VALUES ('2', '2', '비숑', '구름', '2');
INSERT INTO `board`.`puppy` (`puppy_id`, `age`, `breed`, `name`, `vet_id`) VALUES ('3', '3', '푸들', '푸우', '3');
INSERT INTO `board`.`puppy` (`puppy_id`, `age`, `breed`, `name`, `vet_id`) VALUES ('4', '1', '치와와', '앵두', '1');
INSERT INTO `board`.`puppy` (`puppy_id`, `age`, `breed`, `name`) VALUES ('5', '10', '말티즈', '만두');
import com.rptp.rptpSpringBoot.core.puppy.dto.PuppyResponse;
import com.rptp.rptpSpringBoot.core.puppy.service.PuppyService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/puppy")
@RequiredArgsConstructor
public class PuppyController {
private final PuppyService puppyService;
@GetMapping("")
public List<PuppyResponse> getPuppies() {
return puppyService.findAll();
}
@GetMapping("/{id}")
public PuppyResponse getPuppy(@PathVariable("id") Long id) {
return puppyService.findPuppy(id);
}
}
사용자의 HttpRequest에 대한 응답 데이터를 포함하는 클래스이다. 따라서 HttpStatus, HttpHeaders, HttpBody를 포함한다.
package com.rptp.rptpSpringBoot.controller.api;
import com.rptp.rptpSpringBoot.core.vet.dto.VetResponse;
import com.rptp.rptpSpringBoot.core.vet.service.VetService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/vet")
@RequiredArgsConstructor
public class VetController {
private final VetService vetService;
@GetMapping("")
public ResponseEntity<List<VetResponse>> getVets() {
return ResponseEntity.ok(vetService.findAll());
}
@GetMapping("/{id}")
public ResponseEntity<VetResponse> getVet(@PathVariable("id") Long id) {
return ResponseEntity.ok(vetService.findVet(id));
}
}
package com.rptp.rptpSpringBoot.controller.api;
import com.rptp.rptpSpringBoot.core.puppy.dto.PuppyResponse;
import com.rptp.rptpSpringBoot.core.puppy.service.PuppyService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/api/puppy")
@RequiredArgsConstructor
public class PuppyController {
private final PuppyService puppyService;
@GetMapping("")
public ResponseEntity<List<PuppyResponse>> getPuppies() {
return ResponseEntity.ok(puppyService.findAll());
}
@GetMapping("/{id}")
public ResponseEntity<PuppyResponse> getPuppy(@PathVariable("id") Long id) {
return ResponseEntity.ok(puppyService.findPuppy(id));
}
}
전통적인 Spring MVC 컨트롤러인 @Controller은 주로 View를 반환하기 위해 사용한다.
Spring MVC컨트롤러에서도 Data를 반환이 가능하다. Controller에서 데이터를 반환하기 위해 @ResponseBody를 활용해주어야 한다. 이를 통해 @Controller도 Json형태로 데이터를 반환할 수 있다.
1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
2. Mapping되는 Handler와 그 Type을 찾는 DispatcherServlet이 요청을 인터셉트한다.
3. @ResponseBody를 사용하여 Client에게 Json 형태로 데이터를 반환한다.
@RestController는 Spring MVC Controlle에 @ResponseBody가 추가된 것이다.RestController의 주용도는 Json 형태로 객체 데이터를 반환하는 것이다
@RestController가 Data를 반환하기 위해서는 viewResolver 대신에 HttpMessageConverter가 동작한다.
HttpMessageConverter에는 여러 Converter가 등록되어 있고, 반환해야 하는 데이터에 따라 사용되는 Converter가 달라진다.
단순 문자열인 경우에는 StringHttpMessageConverter가 사용되고, 객체인 경우에는 MappingJackson2HttpMessageConverter가 사용되며, 데이터 종류에 따라 서로 다른 MessageConverter가 작동하게 된다.
Spring은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해 적합한 HttpMessageConverter를 선택하여 이를 처리한다.
1. Client는 URI 형식으로 웹 서비스에 요청을 보낸다.
2. Mapping되는 Handler와 그 Type을 찾는 DispatcherServlet이 요청을 인터셉트한다.
3. RestController는 해당 요청을 처리하고 데이터를 반환한다.
https://ko.wikipedia.org/wiki/API
https://mangkyu.tistory.com/46
https://devlog-wjdrbs96.tistory.com/182
https://mangkyu.tistory.com/49