[Springboot] Springboot REST

서버와 클라이언트의 분리 - RESTful 서비스

그래서 서버는 RESTful 서비스만 제공해주도록 역할이 축소가 되었다. REST란 Representational State Transfer 의 약자로 리소스만 xml 혹은 json 형태로 제공해준다는 것이다.

게시판을 예를 들어보자. 게시판을 안드로이드 앱으로 개발한다면, 안드로이드에서는 서버에 API만 호출해서 데이터를 받은 후에 받은 데이터를 안드로이드의 고유 UI를 사용해서 네이티브 앱을 개발하게 된다. 아이폰도 마찬가지이다.

게시판의 내용을 가져오기 위해서는 HTTP의 GET 메서드를 게시판을 작성시에는 HTTP의 POST 메서드, 게시판 내용을 수정할때는 HTTP 의 PUT 메서드, 게시판 내용을 삭제할때는 DELETE 메서드를 사용하게 되면 CRUD를 모두 구현할 수 있으므로 서버와 클라이언트가 분리되게 되었다.

게시판 데이터를 클라이언트가 렌더링 하게 됨. 다양한 데이터가 제이슨 형태로 받아서 받게 됨.

클라이언트 사이드 렌더링 SPA

라우팅과 SEO의 단점

만약 메타태그 바꿔야 되는데 싱글페이지라 헤더가 안 바뀜. 그래서 나온 솔루션이 nuxt를 써서 서버사이드 렌더링 구현하던데 뭐 그런 방식이 있다.

20210718_210611

배포, CI/CD , 젠킨스 , 등 백엔드에서 어느정도 알아야 한다.

axios를 많이 사용 httpClient 라이브러리 사용 App 컴포넌트

띄움

vue는 라이프사이클 메서드가 있어서 마운티드라는 메서드가 있는데 화면에 호출된 지궇에 실행됨. mounted가 helloworld를 화면에 그린 직후에 mount라는 라이프사이클을 자동으로 호출해줌. 여기에 http 호출

여기부터 자바스크립트 문법이 들어감.

axios

hello를 찍으면 axios라는 라이브러리 통해서 axios호출하고 콘솔통해 찍겠다.

CORS 문제 local에서 8080호출해야되는데 8081호출 그래서 도메인이 달라서 호출 못함.

브라우저가 이쪽으로 proxy가 찍혀야 한다

뷰를 직접 저장하는게 아니라 모델을 만들어 놓고 그걸 만들어서 모델 데이터에 따라 변동되는 버츄어 변동식

모델 데이터 바인딩 하면 자동으로 렌더링 시키겠다.


Web Service & Web Application

Web Service & Web Application의 개요

20210724_212029

Web Service 개발 방법 SOAP 과 REST의 이해

  • 네트워크 상에서 서로 다른 종류의 컴퓨터들 간에 상호작용 하기 위한 소프트웨어 시스템.

Q. SOAP도 아니고 REST도 아닌 HTTP API는 뭐라고 하나요?

A. RESTful API와 HTTP API에는 약간 차이가 있습니다. RESTful API는 REST 명세에 따른 제약 조건이 몇가지 있습니다. 반환 하는 문서의 데이터 타입(XML, JSON), 지원하는 HTTP METHOD의 종류(GET/POST/PUT/DELETE) 등이 있습니다.

HTTP API는 HTTP 전송 프로토콜을 사용하는 모든 API입니다. 여기에는 RESTful API이나 SOAP도 포함되어 있을 수 있습니다.

  • Web service란

웹 서비스(web service)는 네트워크 상에서 서로 다른 종류의 컴퓨터들 간에 상호작용을 하기 위한 소프트웨어 시스템이다. 웹 서비스는 서비스 지향적 분산 컴퓨팅 기술의 일종이다. 웹 서비스 프로토콜 스택은 SOAP, WSDL, UDDI 등으로 이루어진다. 모든 메시징에 XML이 사용되어 상호운용성이 높다.

3가지 키워드로

  • 머신과 머신, 어플리케이션과 어플리케이션 간의 상호작용.
  • 플랫폼 독립적으로 운영
  • 어플리케이션 간에 네트워크를 통한 통신 지원.

  • Web Application이란.

웹 애플리케이션(web application) 또는 웹 앱은 소프트웨어 공학적 관점에서 인터넷이나 인트라넷을 통해 웹 브라우저에서 이용할 수 있는 응용 소프트웨어를 말한다.

웹 애플리케이션은 클라이언트로서 웹 브라우저를 사용하는 사람이 많기 때문에 인기를 누리고 있다. 수천만 대의 PC에 굳이 소프트웨어를 배포해서 설치하지 않아도 웹 애플리케이션을 유지 관리할 수 있다는 점이 장점 중의 하나이다. 웹 애플리케이션은 웹 메일, 온라인 전자상거래 및 경매, 위키, 인터넷 게시판, 블로그 및 MMORPG 게임 등 다양한 기능을 구현할 수 있다.

인터넷 웹브라우저는 http 통신을 제공받아서 클라이언트에게 보여줌.

20210724_225323

웹 어플리케이션에서 웹서비스로 전달하는 값을 request(웹 서비스 입장에선 input정보)이라 하고

웹 서비스에서 처리된 결과값을 클라이언트로 반환하는 걸 response(output)이라고 한다.

서비스 정의하기 위해 문서 포맷, 문서 구조 요청 서비스 위치, (endpoint)가 필요

최근엔 xml보다 문서양이 더 적은 json을 더 많이 쓴다.

20210724_225309

20210724_230126

SOAP는 우리가 사용할 수 있는 http 프로토콜 등을 이용해서 xml을 전달할 수 있는 시스템.

우리가 지금 사용하는 웹서비스의 전송수단이기도 하고 서비스 통신을 위해 xml rtc 사용 그리고 구조도 envelope, header, body로 구성.

SOAP은 복잡하고 무겁고 어려워서 REST라는 개발방식이 더 많이 쓰인다.

REST

프로그래밍에 독립적이고 개발하기 수월하며 2000년 중반부터 특히 모바일 동작의 등장으로서 인기가 계속 이어짐

20210724_230922

Rest는 SOAP과 마찬가지로 클라이언트 통신 방식

여기서 말하는 상태는 컴퓨터가 가지고 있는 자원을 의미.

컴퓨터가 가지고 있는 자원이라는 의미는 컴퓨터에 저장된 파일이거나 디비에 저장된 데이터일수 있거나 모든 자원은 각각 고유한 이름 가지고 표현할 수 있게 한다.

그 자원이 가진 상태를 주고받는 서비스 형태

  • REST는 자원의 형태를 표현하기 위한 형태
  • HTTP 메서드를 이용해서 리소스를 처리하기 위한 아키텍쳐

get,post, put, delete로 크게 분류된다. 모든 http요청은 서버로부터 결과 처리후 응답코드와 함꼐 받게 됨.

어떤 상태로 받게 되는지는 http status 코드를 보면 알 수 있다.

200번은 정상 동작, 400은 클라이언트 오, 500은 서버측의 오류 발생임을 알 수 있다.

REST API는 이런 인터페이스를 말하고 REST API를 제공하는게 RESTful이라고 할 수 있다.

20210724_234654

20210724_234654

각각 고유값을 가진다.

이걸 URI이라 한다. 고유하고 유니크 하며 응답할 떄는 XML, HTML, JSON과 같은 문서포맷이 사용된다.

SOAP vs REST

20210724_235046

먼저 접근 제한성이나 시스템 아키텍쳐에 맞는 걸 선택

그리고 그외에도 4개 더 따져보고 선택.


Spring Boot로 개발하는 RESTful Service

Springboot 개요

20210725_000612

  1. Springboot Application
  2. Auto Configureation
  3. Component Scan

  4. 첫번째로 실행되는 스프링 어플리케이션은 auto Configuration으로 설정을 자동화 하고 설정에 따라 개발자가 등록한 환경을 불러온다.

그리고 3번째에서 각종 컴포넌트를 불러옴.

사용 용도에 따라 Respository, entity, service 등으로 가져오게 된다.


REST API 설계

20210725_000829

group id 는 어플리케이션을 통해서 개발하는 회사 이름이라던가 그룹, 도메인의 이름 등록

보통 이런게 패키지의 이름으로 사용.

그 밑의 아티팩트 아이디는 어플리케이션의 이름정도로 알면 된다.

20210725_002017


20210725_033758

인텔리제이에서 이 옵션을 켜줘야 롬복 및 어노테이션을 제대로 쓸 수 있다.

20210725_034058

그리고 STS에서 처럼 intelj에도 롬복 플러그인을 설치해줘야한다.


DispatcherServlet 과 프로젝트 동작의 이해.

20210725_131635

yml은 properties와 동일. 그냥 표현 방식에서의 차이

DispatcherServlet

20210725_140259

RestConroller

20210725_141126

RestConroller = 기존의 컨트롤러 + ResponseBody로 쓰임.


PathVariable 사용

20210725_143705

@RestController
public class HelloWorldController {
    // GET

    // /hello-world(endpoint)
    // @RequestMapping(method=RequestMethod.GET, path="/hello-world")
    @GetMapping(path="/hello-world")
    public String helloWorld(){
        return "Hello World";
    }


    @GetMapping(path="/hello-world-bean")
    public HelloWorldBean helloWorldBean(){
        return new HelloWorldBean("Hello World");
        //자바 빈 형태로 주면 json형태로 줄 것.
        //responsebody 안 넣더라도 RestController로 인해 자동으로 json으로 넣어져 들어간다.
    }

    @GetMapping(path="/hello-world-bean/path-variable/{name}")
    public HelloWorldBean helloWorldBean(@PathVariable String name){
      return new HelloWorldBean(String.format("Hello World, %s", name));
  }
}


20210725_150738

쨘.


USER SERVICE API 구현

20210725_151053

20210725_163242


    @PostMapping("/users")
    public void createUser(@RequestBody User user){
        //json이나 xml 같은걸 전달하기 위해 매개변수 타입에 지금 전달받는게 RequestBody 을 선언해야 한다.
        User savedUser = service.save(user);
    }

postman으로 post 메서드를 날리기 위해서는 body에 데이터를 싣고 날려서 확인해야 한다.

20210725_164413

20210725_164758

이제 저 post로 추가한 뉴 유저가 get방식 호출로 확인하면 들어간 게 보인다.

20210725_165842

headers 탭의 로케이션으로 새로운 사용자가 추가된걸 확인 가능. 그리고 복사해서 메서드 요청해보자.

20210725_165912

20210725_165912

20210725_170108


Spring Aop 를 이용한 Exception Handling

예외가 발생한 시간, 메시지, 상세정보를 가진 일반화된 자바 객체 선언


@RestController
@ControllerAdvice   //이게 있어야 상태창에 제대로 설정한 대로 보인다.
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request){
        ExceptionResponse exceptionResponse =
                new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));

        return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);//500 에러
    }
    @ExceptionHandler(UserNotFoundException.class)
    public final ResponseEntity<Object> handleUserNotFoundExceptions(Exception ex, WebRequest request){
        ExceptionResponse exceptionResponse =
                new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));

        return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND); //404 에러
    }
}



사용자 삭제를 위한 API 구현 - DELETE HTTP METHOD
@DeleteMapping("/users/{id}")
    public void deleteUser(@PathVariable int id) {
        User user = service.deleteById(id);

        if (user == null) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }
    }

RESTful Service 기능 확장

  • validation
  • Internationalization
  • XML format 으로 변환하기
  • Filtering
  • version 관리

유효성 체크를 위한 Validation API 사용

20210725_225649


다국어 처리를 위한 Internationalization 구현 방법

20210725_232132

20210725_233828

다국어 처리를 위해 yml에 지정해주자.

그리고 파일로 message.properies를 만들어 주고 이 안에 각 설정을 해주자(en은 hello, fr은 bonjour)

@GetMapping(path="/hello-world-internationalized")
   public String helloWorldInternationalized(
           @RequestHeader (name="Accept-Language", required=false) Locale locale){
    //이건 앞에 설명한 language란 헤더값이 포함 안되면 디폴트 값(한국어)가 들어감
    //메시지 값 반환하기 위해 메시지 소스를 만들자.(맨위)

   }


@GetMapping(path="/hello-world-internationalized")
    public String helloWorldInternationalized(
            @RequestHeader (name="Accept-Language", required=false) Locale locale){
     //이건 앞에 설명한 language란 헤더값이 포함 안되면 디폴트 값(한국어)가 들어감
     //메시지 값 반환하기 위해 메시지 소스를 만들자.(맨위)
        return messageSource.getMessage("greeting.message",null, locale);
    }

20210726_000652

아무런 값을 안 넣으면 디폴트 한글 출력

20210726_000732

헤더로 넣어서 en값 주고 출력결과는 hello가 보인다.

우리가 가지지 않은 국가코드는 default인 한글이 나올 것.


Response 데이터 형식 변환 -XML 포맷

만약 위에서 했던 걸 xml로 줘야하면?

20210726_00125920210726_001333

406인데 클라이언트 에러로 없는 데이터 형태인 xml을 요청해서 406 에러가 뜬다.


Response데이터 제어를 위한 Filtering

사용자 정보 관리중 클라이언트에 전달해줄 데이터를 관리하는 걸 필터를 통해 가능하다

20210726_004224

주민번호나 패스워드 같은 걸 건네주고 초기화 하거나 이런걸 하기위해 . 그리고 오른쪽은 실행시키기 위해 필요한 데이터.

중요한 데이터는 제어해야되는데 그 방법을 필터를 통해 한다.

@JsonIgnoreProperties(value={"password","ssn"})
public class User {
    private Integer id;

    @Size (min=2, message = "2글자 이상 입력해주세요")
    private String name;
    @Past   //현재 미래는 못오고 과거만 오게.
    private Date joinDate;

//    @JsonIgnore
    private String password;
//    @JsonIgnore //이걸 넣어주면 json넣어줄떄 무시된다.
    private String ssn;
}

여기서 JsonIgnore 로 무시하고 싶은 변수들을 넣어서 무시할 수 있다.

20210726_015006

이 처럼 제어하고 싶을 떄 필터를 이용하면 좀 더 수월하게 컨트롤이 가능하다.


필터로 받은건 List도메인을 못받고 MappingJacksonVlaue로 전환시켜 받아야 한다.

20210726_015420


@RestController
@RequestMapping("/admin")
public class AdminUserController {
    private UserDaoService service;

    public AdminUserController(UserDaoService service) {
        this.service = service;
    }

    @GetMapping("/users")
    public MappingJacksonValue retrieveAllUsers() {
        List<User> users = service.findAll();

        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
                .filterOutAllExcept("id", "name", "joinDate", "password");

        FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);

        MappingJacksonValue mapping = new MappingJacksonValue(users);
        mapping.setFilters(filters);

        return mapping;
    }

    // GET /admin/users/1 -> /admin/v1/users/1
//    @GetMapping("/v1/users/{id}")
//    @GetMapping(value = "/users/{id}/", params = "version=1")
//    @GetMapping(value = "/users/{id}", headers="X-API-VERSION=1")
    @GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv1+json")
    public MappingJacksonValue retrieveUserV1(@PathVariable int id) {
        User user = service.findOne(id);

        if (user == null) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }

        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
                .filterOutAllExcept("id", "name", "password", "ssn");

        FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo", filter);

        MappingJacksonValue mapping = new MappingJacksonValue(user);
        mapping.setFilters(filters);

        return mapping;
    }

    //    @GetMapping("/v2/users/{id}")
//    @GetMapping(value = "/users/{id}/", params = "version=2")
//    @GetMapping(value = "/users/{id}", headers="X-API-VERSION=2")
    @GetMapping(value = "/users/{id}", produces = "application/vnd.company.appv2+json")
    public MappingJacksonValue retrieveUserV2(@PathVariable int id) {
        User user = service.findOne(id);

        if (user == null) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }

        // User -> UserV2
        UserV2 userV2 = new UserV2();
        BeanUtils.copyProperties(user, userV2); // id, name, joinDate, password, ssn
        userV2.setGrade("VIP");

        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
                .filterOutAllExcept("id", "name", "joinDate", "grade");

        FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2", filter);

        MappingJacksonValue mapping = new MappingJacksonValue(userV2);
        mapping.setFilters(filters);

        return mapping;
    }
}

20210726_025002

버전관리는 단순히 사용자에게 보여주는 거만 지칭하는게 아니라 REST API 설계가 변경되거나 어플리케이션 구조가 바뀔 떄도 버전을 변경해서 사용해야한다.

사용자에게 어떠한 버전을 사용해야 되는지도 명시해 줘야한다.

우리는 REST에서 버전관리 방식 봤는데 URI 파라미터 , MIME, 헤더 방식 있었는데 1,2는 일반 브라우저에서 URI을 통해 실행 가능하지만.

3,4는 Header부분에 설정하고 넣어야 해서 일반 브라우저에서는 실행이 불가능하다.


Spring Boot API 사용

20210726_031416





© 2021.03. by yacho

Powered by github