[Spring MVC] API 계층

인자약·2023년 11월 26일
0

Spring

목록 보기
3/9
post-thumbnail

📕 Chapter - Spring MVC 아키텍처

✏️ Spring MVC란?

서블릿(Servlet) API를 기반으로 클라이언트의 요청을 처리하는 모듈이 있는데, 이 모듈 이름이 바로 Spring Web MVC(Spring MVC)

👉 서블릿(Servlet)이란?

클라이언트의 요청을 처리하도록 특정 규약에 맞추어서 Java 코드로 작성하는 클래스 파일
아파치 톰캣(Apache Tomcat)은 이러한 서블릿들이 웹 애플리케이션으로 실행이 되도록 해주는 서블릿 컨테이너(Servlet Container) 중 하나

📌 Model

작업의 처리 결과 데이터

📌 View

Model 데이터를 이용해서 웹브라우저 같은 클라이언트 애플리케이션의 화면에 보이는 리소스(Resource)를 제공하는 역할
View 형태는 Model 데이터를 JSON 프로토콜 데이터로 변환하는 것

📌 Controller

클라이언트 측의 요청을 직접적으로 전달받는 엔드포인트(Endpoint)로써 Model과 View의 중간에서 상호 작용을 해주는 역할

👉 Model, View, Controller 간의 처리 흐름
Client가 요청 데이터 전송 → Controller가 요청 데이터 수신 → 비즈니스 로직 처리 → Model 데이터 생성 → Controller에게 Model 데이터 전달 → Controller가 View에게 Model 데이터 전달 → View가 응답 데이터 생성

✏️ Spring MVC의 동작 방식과 구성 요소

📕 Chapter - Controller

개요

✏️ [기본] Controller 클래스 설계 및 구조 생성

@RestController를 클래스에 추가함으로써 해당 클래스를 REST API의 리소스(자원, Resource)를 처리하기 위한 API 엔드포인트로 동작하게 해 준다.
@RequestMapping을 Controller 클래스 레벨에 추가하여 클래스 전체에 사용되는 공통 URL(Base URL)을 설정할 수 있다.

✏️ [기본] 핸들러 메서드(Handler Method)

  • 클라이언트의 요청을 전달받아서 처리하기 위해서는 요청 핸들러 메서드(Request Handler Method)가 필요하다.
  • Spring MVC에서는 HTTP Method 유형과 매치되는 @GetMapping, @PostMapping 등의 애너테이션을 지원한다.
  • @PathVariable 애너테이션을 사용하면 클라이언트 요청 URI에 패턴 형식으로 지정된 변수의 값을 파라미터로 전달받을 수 있다.
  • @RequestParam 애너테이션을 사용하면 쿼리 파라미터(Query Parmeter 또는 Query string), 폼 데이터(form-data), x-www-form-urlencoded 형식의 데이터를 파라미터로 전달받을 수 있다.
  • @GetMapping, @PostMapping 등에서 URI를 생략하면 클래스 레벨의 URI 경로만으로 요청 URI를 구성한다.

✏️ [기본] 응답 데이터에 ResponseEntity 적용

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

@RestController
@RequestMapping(value = "/v1/members", produces = {MediaType.APPLICATION_JSON_VALUE})
public class MemberController {
    @PostMapping
    public String postMember(@RequestParam("email") String email,
                             @RequestParam("name") String name,
                             @RequestParam("phone") String phone) {
        System.out.println("# email: " + email);
        System.out.println("# name: " + name);
        System.out.println("# phone: " + phone);

        // 코드 개선이 필요한 부분
        String response =
                "{\"" +
                   "email\":\""+email+"\"," +
                   "\"name\":\""+name+"\",\"" +
                   "phone\":\"" + phone+
                "\"}";
        return response;
    }

    @GetMapping("/{member-id}")
    public String getMember(@PathVariable("member-id") long memberId) {
        System.out.println("# memberId: " + memberId);

        // not implementation
        return null;
    }

    @GetMapping
    public String getMembers() {
        System.out.println("# get Members");

        // not implementation
        return null;
    }
}
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/v1/members") // (1) produces 설정 제거됨
public class MemberController {
    @PostMapping
    public ResponseEntity postMember(@RequestParam("email") String email,
                                     @RequestParam("name") String name,
                                     @RequestParam("phone") String phone) {
        // (2) JSON 문자열 수작업을 Map 객체로 대체
        Map<String, String> map = new HashMap<>();
        map.put("email", email);
        map.put("name", name);
        map.put("phone", phone);

        // (3) 리턴 값을 ResponseEntity 객체로 변경
        return new ResponseEntity<>(map, HttpStatus.CREATED);
    }

    @GetMapping("/{member-id}")
    public ResponseEntity getMember(@PathVariable("member-id") long memberId) {
        System.out.println("# memberId: " + memberId);

        // not implementation

        // (4) 리턴 값을 ResponseEntity 객체로 변경
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity getMembers() {
        System.out.println("# get Members");

        // not implementation

        // (5) 리턴 값을 ResponseEntity 객체로 변경
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
  • 핸들러 메서드의 리턴 값으로 Map 객체를 리턴하면 Spring MVC 내부적으로 JSON 형식의 데이터를 생성해 준다. 즉, 클래스 레벨의 @RequestMapping에 ‘produces’ 애트리뷰트를 지정할 필요가 없다.
  • ResponseEntity 클래스로 응답 데이터를 래핑함으로써 조금 더 세련된 방식으로 응답 데이터를 리턴할 수 있다.
  • POST Method 형식의 클라이언트 요청에 대한 응답 상태는 HttpStatus.OK보다는 HttpStatus.CREATED가 조금 더 자연스럽다.

✏️ HTTP 헤더(Header)

  • HTTP 헤더(Header)는 HTTP 메시지(Messages)의 구성 요소 중 하나로써 클라이언트의 요청이나 서버의 응답에 포함되어 부가적인 정보를 HTTP 메시지에 포함할 수 있다.
  • HTTP Request 헤더(Header) 정보 얻기
    1)@RequestHeader 애너테이션을 이용해서 개별 헤더 정보 및 전체 헤더 정보를 얻을 수 있다.
    2)HttpServletRequest 또는 HttpEntity 객체로 헤더 정보를 얻을 수 있다.
  • HTTP Response 헤더(Header) 정보 추가
    1)ResponseEntity와 HttpHeaders를 이용해 헤더 정보를 추가할 수 있다.
    2)HttpServletResponse 객체를 이용해 헤더 정보를 추가할 수 있다.

✏️ Rest Client

  • 웹 브라우저는 웹 서버로부터 HTML 콘텐츠를 제공받는 클라이언트 중 하나이다.
  • 어떤 서버가 HTTP 통신을 통해서 다른 서버의 리소스를 이용한다면 그때만큼은 클라이언트의 역할을 한다.
  • Rest Client란 Rest API 서버에 HTTP 요청을 보낼 수 있는 클라이언트 툴 또는 라이브러리를 의미합니다.
  • RestTemplate은 원격지에 있는 다른 Backend 서버에 HTTP 요청을 전송할 수 있는 Rest Client API이다.
  • RestTemplate 사용 단계
    1)RestTemplate 객체를 생성한다.
    2)HTTP 요청을 전송할 엔드포인트의 URI 객체를 생성한다.
    3)getForObject(), getForEntity(), exchange() 등을 이용해서 HTTP 요청을 전송한다.
  • RestTemplate을 사용할 수 있는 기능 예
    결제 서비스
    카카오톡 등의 메시징 서비스
    Google Map 등의 지도 서비스
    공공 데이터 포털, 카카오, 네이버 등에서 제공하는 Open API
    기타 원격지 API 서버와의 통신

📕 Chapter - DTO(Data Transfer Object)

✏️ [기본] HTTP 요청/응답에서의 DTO(Data Transfer Object)

@RestController
@RequestMapping("/v1/members")
public class MemberController {
    // 회원 정보 등록
    @PostMapping
    public ResponseEntity postMember(@RequestBody MemberPostDto memberPostDto) {
        return new ResponseEntity<>(memberPostDto, HttpStatus.CREATED);
    }

    // 회원 정보 수정
    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(@PathVariable("member-id") long memberId,
                                      @RequestBody MemberPatchDto memberPatchDto) {
        memberPatchDto.setMemberId(memberId);
        memberPatchDto.setName("홍길동");

        // No need Business logic

        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }

    // 한명의 회원 정보 조회
    @GetMapping("/{member-id}")
    public ResponseEntity getMember(@PathVariable("member-id") long memberId) {
        System.out.println("# memberId: " + memberId);

        // not implementation
        return new ResponseEntity<>(HttpStatus.OK);
    }

    // 모든 회원 정보 조회
    @GetMapping
    public ResponseEntity getMembers() {
        System.out.println("# get Members");

        // not implementation

        return new ResponseEntity<>(HttpStatus.OK);
    }

    // 회원 정보 삭제
    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(@PathVariable("member-id") long memberId) {
        // No need business logic

        return new ResponseEntity(HttpStatus.NO_CONTENT);
    }
}
  • DTO는 Data Transfer Object의 약자로 마틴 파울러(Martin Fowler)가 ‘Patterns of Enterprise Application Architecture’라는 책에서 처음 소개한 엔터프라이즈 애플리케이션 아키텍처 패턴의 하나이다.
  • DTO는 주로 클라이언트에서 서버 쪽으로 전송하는 요청 데이터를 전달받을 때, 서버에서 클라이언트 쪽으로 전송하는 응답 데이터를 전송하기 위한 용도로 사용된다.
  • DTO가 필요한 이유
    1)클라이언트의 Request Body를 하나의 객체로 모두 전달받을 수 있기 때문에 코드 자체가 간결해진다.
    2)Request Body의 데이터 유효성(Validation) 검증이 단순해진다.
  • JSON 형식의 Request Body를 전달받기 위해서는 DTO 객체에 @RequestBody 애너테이션을 붙여야 한다.
  • Response Body를 JSON 형식으로 전달하기 위해서는 @ResponseBody 애너테이션을 메서드 앞에 붙여 주어야 하지만 ResponseEntity 객체를 리턴 값으로 사용할 경우 @ResponseBody를 생략할 수 있다.
  • 클라이언트 쪽에서 JSON 형식의 데이터를 서버 쪽으로 전송하면 서버 쪽의 웹 애플리케이션은 전달받은 JSON 형식의 데이터를 DTO 같은 Java의 객체로 변환하는데 이를 역직렬화(Deserialization)이라고 한다.
  • 서버 쪽에서 클라이언트에게 응답 데이터를 전송하기 위해서 DTO 같은 Java의 객체를 JSON 형식으로 변환하는 것을 직렬화(Serialization)라고 한다.

✏️ [기본] DTO 유효성 검증(Validation)

public class MemberPatchDto {
    private long memberId;

    @NotSpace(message = "회원 이름은 공백이 아니어야 합니다") // (1)
    private String name;

    @Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
            message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다")
    private String phone;

    public String getName() {
        return name;
    }

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

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public long getMemberId() {
        return memberId;
    }

    public void setMemberId(long memberId) {
        this.memberId = memberId;
    }
}
  • 프론트엔드 쪽에서 유효성 검증을 진행했다 하더라도 서버 쪽에서 추가적으로 유효성 검증을 반드시 진행해야 한다.
  • 프론트엔드 쪽에서의 유효성 검증 프로세스는 사용자 편의성 측면에서 필요한 작업이다.
  • Jakarta Bean Validation의 애너테이션을 이용하면 Controller 로직에서 유효성 검증 로직을 분리할 수 있다.
  • Jakarta Bean Validation은 애너테이션 기반 유효성 검증을 위한 표준 스펙이다.
  • Hibernate Validator는 Jakarta Bean Validation 스펙을 구현한 구현체이다.
  • Spring에서 지원하는 @Validated 애너테이션을 사용하면 쿼리 파라미터(Query Parameter 또는 Query String) 및 @Pathvariable에 대한 유효성 검증을 진행할 수 있다.
  • Jakarta Bean Validation에서 빌트인(Built-in)으로 지원하지 않는 애너테이션은 Custom Validator를 통해 Custom Annotation을 구현한 후, 적용할 수 있다.
profile
인자약velog

0개의 댓글