본문 바로가기
Backend boot camp/Session3

[Spring MVC] API Layer

by orioncsy 2022. 10. 29.

샘플 애플리케이션 구현 및 프로젝트 환경 구성

샘플 애플리케이션

소개

  • 간단한 애플리케이션을 제작해보면서 Spring 기술을 터득

샘플 애플리케이션

  • 이름 : 커피 주문 웹 애플리케이션
  • 설명
    • 커피 주문을 위해 필요한 정보를 제공하는 서버용 웹 애플리케이션 제작
    • 커피만 주문할 수 있는 것으로 기능을 제한한다(애플리케이션 경계)
  • 기능
    • 정보
      • 커피 자체 정보(Coffee)
      • 고객 정보(Member)
      • 주문 정보(Order)
    • 주인이 커피 정보 등록하는 기능
    • 주문에 대한 결제 기능
    • 배달, 방문 선택 기능
    • 포인트, 스탬프 처리 기능
    • 배달, 픽업 완료 기능

실습 환경 구성

  • Spring Initializr에서 Spring project 생성
  • Gradle Project, Java 11, Jar packaging 선택
  • Dependency
    • Lombok
    • Spring Web

Spring MVC

개요

Spring Framework

  • Spring에서 지원하는 모든 기능을 포함한 것

Spring-webmvc

  • 웹 계층을 담당하는 모듈 중 Servlet API 기반으로 클라이언트 요청 처리하는 모듈
  • Spring MVC, Spring MVC Framework라고도 부름

Spring MVC 역할

  • 클라이언트 요청을 편리하게 처리

Servlet

  • 클라이언트 요청 처리를 위해 특정 규약에 맞게 Java코드로 작성하는 클래스 파일
  • Apache Tomcat은 servlet이 웹 애플리케이션에서 실행되도록 도와주는 Servlet Container
  • Spring MVC 내부에서는 Servlet을 기반으로 웹 애플리케이션 동작

Model

개념

  • Spring MVC에서 클라이언트의 요청을 받아 작업을 처리한 결과 데이터를 응답으로 돌려주는 작업 처리 결과 데이터를 Model이라고 한다.
  • Service Layer - 클라이언트 요청을 구체적으로 처리
  • Business Logic - 요청 사항을 처리하기 위해 Java 코드로 구현하는 것

View

개념

  • Model data를 이용해 클라이언트 애플리케이션의 화면에 보이는 리소스를 제공

형태

  • HTML 페이지 출력
    • HTML 페이지를 직접 렌더링해서 클라이언트 측에 전송
    • HTML 태그로 구성된 페이지에 Model 데이터를 넣고, 최종 HTML 페이지 클라이언트에게 전송
    • Thymeleaf, FreeMarker, JSP + JSTL, Tiles 기술 사용
  • PDF, Excel 등 문서 형태 출력
    • Model data를 가공해 PDF, Excel 등의 문서로 만들어 클라이언트 측에 전송
    • 문서 내 데이터가 동적 변경되어야 하는 경우 사용
  • XML, JSON 형식의 포맷으로 변환
    • Model data를 특정 프로토콜 형태로 변환해서 변환된 데이터를 클라이언트 측에 전송
    • 특정 형식의 데이터를 전송하고 클라이언트 측에서 HTML 페이지 제작
    • Front end, Back end 영역이 명확하게 구분
    • 개발 유지 보수 용이
    • Front end에서 비동기 클라이언트 애플리케이션 제작 가능
    • JSON(JavaScript Object Notation)
      • Spring MVC에서 클라이언트 애플리케이션과 서버 애플리케이션이 주고받는 데이터 형식
      • XML보다 가볍고 간편한 JSON을 주로 사용
      • 기본 포맷
        • {”속성”:“값”} 형태
      • Gson 라이브러리를 활용하여 JSON형태로 변환
      Gson gson=new Gson();
      String jsonString=gson.toJson([객체]);
      System.out.println(jsonString);
      

Controller

개념

  • 클라이언트의 요청을 직접 전달 받는 엔드포인트로 Model, View의 중간에서 상호작용
  • 요청을 전달 받아 비즈니스 로직을 거치고 Model이 만들어지면 View로 전달하는 역할

흐름

  • Client 요청 데이터 전송 → Controller 요청 데이터 수신 → 비즈니스 로직 처리 → Model 데이터 생성 및 Controller에게 전달 → Controller가 View에게 Model 전달 → View가 응답 데이터 생성

JSON 표기법

  • 기본 자료형 : Number, String, Boolean, Array, Object
  • Number
    • 정수
    • 실수(고정 소수점) ex) 3.25
    • 실수(부동 소수점) ex) 4.36e+4
  • String
    • “”(큰 따옴표)로 묶어야한다.
    • \b(백스페이스), \f(폼 피드), \n(개행), \r(캐리지 리턴), \t(탭), \”(따옴표), \/(슬래시) \\(역슬래시), \uHHHH(16진수 4자리 유니코드 문자)
  • Array
    • 로 묶는다.
    • 기본 자료형, 배열, 객체를 요소로 가진다.
    • , (쉼표)로 구분
  • Object
    • 객체는 이름, 값의 쌍으로 {}(중괄호) 사용
  • JSON to Java

Spring MVC 동작 방식과 구성 요소

동작 흐름

  1. 클라이언트가 요청을 전송하면 DispatcherServlet 클래스에 요청 전달
  2. DispatcherServlet는 클라이언트 요청 처리를 할 Controller 검색을 HandlerMapping 인터페이스에게 요청
  3. HandlerMapping은 클라이언트 요청과 매핑되는 핸들러 객체를 DispatcherServlet에게 리턴
  4. DispatcherServlet 는 HandlerAdapter에게 Handler 메서드 호출 위임
  5. HandlerAdapter는 전달받은 Controller 정보 기반으로 해당 Controller Handler 메서드 호출
  6. Controller의 handler 메서드가 비즈니스 로직을 처리한 후 리턴 받은 Model 데이터를 HandlerAdapter에게 전달
  7. HandlerAdapter가 전달받은 Model과 View 정보를 다시 DispatcherServlet으로 전달
  8. DispatcherServlet은 view 정보를 ViewResolver에게 전달해서 view 검색 요청
  9. ViewResolver는 view 정보에 해당하는 view를 찾아서 view를 리턴
  10. DispatcherServlet은 ViewResolver로부터 받은 view를 통해 model 데이터를 넘겨주면서 클라이언트에게 전달할 응답 데이터 생성을 요청
  11. View는 응답 데이터를 생성해서 DispatcherServlet에게 전달
  12. DispatcherServlet는 view로부터 받은 응답 데이터를 최종적으로 클라이언트에게 전달

API Layer

Controller 클래스 설계 및 구조 생성

개요

  • 계층형 아키텍처 : API Layer → Business Layer → Data Access Layer
  • API Layer를 Spring MVC 기반 코드로 구현
  • Controller가 Spring MVC에서 클라이언트 요청의 최종 목적지

패키지 구조 생성

  • 기능 기반 패키지 구조
    • 애플리케이션의 패키지를 애플리케이션에서 구현해야 하는 기능을 기준으로 구성
    • 하나의 기능을 완성하기 위한 계층별 클래스들이 모여있다(API layer, service layer, data access layer)
  • 계층 기반 패키지 구조
    • 패키지를 하나의 계층으로 보고 계층별로 묶어서 관리하는 구조
  • 테스트와 리팩터링 용이, 마이크로 서비스 시스템으로 분리가 용이한 기능 기반 패키지 권고

Controller 설계

  • 클라이언트 요청을 처리할 서버 애플리케이션 기능 구상
  • 커피 정보 등록 기능
    • 커피 정보 등록, 수정, 삭제, 조회 기능
  • 고객이 커피 정보 조회 기능
    • 커피 정보 조회 기능
  • 커피 주문 기능
    • 주문 등록, 취소, 조회 기능
  • 주인이 주문한 커피 조회
    • 커피 주문 조회
    • 고객 전달 완료한 주문 완료 처리 기능

애플리케이션 필요 리소스

  • REST API 기반 애플리케이션은 제공해야 될 기능을 리소스로 분류
  • 회원, 커피, 주문이라는 리소스 구성
  • 고객과 주인을 회원이라는 리소스를 포함하여 둘 사이 role을 통해 기능을 구분 가능

Entrypoint 클래스 작성

  • main() 메서드가 포함된 엔트리 포인트(애플리케이션 시작 지점)을 작성
  • Spring Initializr는 엔트리포인트 클래스 이미 작성 완료
@SpringBootApplication
public class Section3Week1Application {

	public static void main(String[] args) {
		SpringApplication.run(Section3Week1Application.class, args);
	}

}
  • @SpringBootApplication
    • 자동 구성 활성화
    • @Component가 붙은 클래스 scan 하고 Spring Bean으로 등록
    • @Configuration이 붙은 클래스 scan하고 Spring Bean으로 등록
  • SpringApplication.run(Section3Week1Application.class, args);
    • Spring 애플리케이션을 부트스트랩, 실행
    • 부트스트랩(Bootstrap) - 설정 작업을 하여 실행 가능 상태로 만드는 것

Controller 구조

MemeberController / CoffeeController / OrderController

@RestController
@RequestMapping("/v1/orders")
public class OrderController {
}
  • @RestController
    • REST API의 리소스를 처리하기 위한 API 엔드포인트로 동작
    • Spring bean으로 등록
  • @RequestMapping
    • 클라이언트 요청과 핸들러 메서드를 매핑

REST API

  • HTTP 네트워크 상의 리소스를 URI라는 고유 주소로 접근하는 접근 방식을 REST
  • REST방식으로 리소스에 접근하는 서비스 API를 REST API
  • URI는 식별자, URL은 웹 상의 주소
  • REST API URI 규칙
    • 마지막이 /로 끝나지 않는다
    • 동사보다 명사 사용
    • 단수보다 복수형 명사
    • 기본 소문자 사용
    • 하이픈(-) 사용
    • 파일 확장자는 URI에 포함하지 않는다

@SpringBootApplication

  • @EnableAutoConfiguration
    • 자동 구성
  • @ComponentScan
    • @Component 스캔
  • @SpringBootConfiguration
    • @Configuration 클래스 임포트

Handler Method

@RestController
@RequestMapping(value ="/v1/members", produces = {MediaType.APPLICATION_JSON_VALUE})
public class MemberController{
    @PostMapping
    public String postMemeber(@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(@RequestParam("member-id") long memberId){
        System.out.println("# memberId: " + memberId);
        return null;
    }
    public String getMembers(){
        System.out.println("# get Members");
        return null;
    }
}
  • @RequestMapping
    • produces : 응답 데이터를 어떤 미디어 타입으로 전송할지 결정
    • MediaType.APPLICATION_JSON_VALUE 값으로 JSON 형태 데이터로 응답을 전송
  • @PostMapping
    • 클라이언트 요청 데이터를 서버에 생성할 때 사용하는 애너테이션 http method 타입을 POST로 할 때 mapping 된다
  • @RequestParam
    • 핸들러 메서드의 파라미터 종류 중 하나
    • 클라이언트에서 요청 데이터를 쿼리 파라미터 형식으로 전송하면 서버 쪽에서 전달받을 때 사용하는 애너테이션
    • 쿼리 파라미터는 ?page=10&size=20 형태로 표현
  • 리턴 값
    • 클라이언트에서 JSON 형태의 데이터를 전송받아야 해서 JSON 형식에 맞는 문자열 반환
    • POST method를 처리하는 핸들러 메서드의 경우 생성한 데이터를 반환하는 것이 관례
  • @GetMapping
    • 클라이언트가 서버에 리소스를 조회할 때 사용하는 애너테이션
    • /v1/members/{member-id}
    • {member-id}는 회원 식별자로 URI로 어떤 값을 지정하느냐에 따라 동적으로 바뀌는 값
  • @PathVariable
    • 핸들러 메서드의 파라미터
    • @GetMapping 괄호 안에 있는 문자열과 동일한 값을 넣어야 한다

ResponseEntity

@RestController
@RequestMapping("/v1/coffees")
public class CoffeeController {
    @PostMapping
    public ResponseEntity postCoffee(@RequestParam("engName") String engName,
                                     @RequestParam("korName") String korName,
                                     @RequestParam("price") long price){
        System.out.println("# engName: " + engName);
        System.out.println("# korName: " + korName);
        System.out.println("# price: " + price);
        Map<String, Object> map =new HashMap<>();
        map.put("engName", engName);
        map.put("korName", korName);
        map.put("price", price);
        return new ResponseEntity<>(map, HttpStatus.CREATED);
    }
    @GetMapping("/{coffee-id}")
    public ResponseEntity getCoffee(@PathVariable("coffee-id") long coffeeId){
        System.out.println("# coffeeId: " + coffeeId);
        return new ResponseEntity(HttpStatus.OK);
    }
    @GetMapping
    public ResponseEntity getCoffees(){
        System.out.println("# get Coffees");
        return new ResponseEntity<>(HttpStatus.OK);
    }
}
  • produce 속성을 제거
  • JSON을 수작업했던 부분을 Map을 통해 구성
  • ResponseEntity 객체를 반환 ( 파라미터로 응답 데이터, HTTP header, HTTP 응답 상태 전달)

HTTP Header

개념

  • 요청이나 응답에 포함되어 부가 정보를 HTTP 메시지에 포함한 내용

사용 목적

  • Content-Type
    • HTTP 메세지 바디의 데이터 형식이 어떤 것인지 알려주는 역할
    • application/json(json 형태 데이터)
  • Authorization
    • 클라이언트가 자격 증명을 가지고 있는지 확인하는 정보
  • User-Agent
    • 다양한 유형의 클라이언트가 하나의 서버에 요청을 할 경우 구분하여 응답 데이터를 다르게 보내줄 때 사용

HTTP Request header 얻기

  • @RequestHeader("user-agent") String userAgent
    • 특정 헤더 정보 가져오기
  • @RequestHeader Map<String, String> headers
    • 전체 헤더 정보 받기
  • HTTPServletRequest httpServletRequest
  • httpServletRequest.getHeader(”user-agent”)
    • HTTPServletRequest 객체로 받아서 getHeader를 통해 얻기
  • HttpEntity httpEntity
  • httpEntity.getHeaders
    • HttpEntity 객체로 받아서 getHeaders로 가져오기
    • get(”host”) 혹은 getHost()로 헤더 중 자주 사용하는 헤더 가져올 수 있다

HTTP Response Header 정보 추가

  • HttpHeaders headers=new HttpHeaders();
  • headers.set(”Client-Geo-Location”, “Korea,Seoul”);
  • ResponseEntity<>(new Member(…), headers, HttpStatus.CREADTED);
    • HttpHeaders 객체를 생성하여 headers를 set하고 ResponseEntity로 구성한다
  • HttpServletResponse response
  • response.addHeader(”Client-Geo-Location”, “Korea,Seoul”);
    • HttpServletResponse 객체로 받아서 addHeader로 추가한다

Rest Client

Client - Server 관계

  • front end와 back end처럼 서버도 다른 서버로부터 리소스를 제공받는다
  • client -server 관계는 상대적인 것

Rest Client

  • Rest API 서버에 HTTP 요청을 보낼 수 있는 클라이언트 툴 혹은 라이브러리
  • ex) Postman

RestTemplate

  • Java에서 사용할 수 있는 HTTP Client 라이브러리
    • java.net.HttpURLConnection, Apache HttpComponents, OkHttp 3, Netty
  • Spring에서는 다른 Backend 서버에 HTTP 요청을 보낼 수 있는 Rest Client API를 RestTemplate으로 제공
  • Rest 엔드포인트 지정, 헤더 설정, 파라미터 및 바디 설정을 쉽게 가능하게 한다

RestTemplate 생성

RestTemplate restTemplate =
	new RestTemplate(new HttpComponentsClientHttpRequestFactory());
  • dependencies에 implementation 'org.apache.httpcomponents:httpclient' 추가

URI 생성

UriComponents uriComponents =
	UriComponentsBuilder
		.newInstance()
		.scheme("http")
		.host("worldtimeapi.org")
		.port(80)
		.path("/api/timezone/{continents}/{city}")
		.encode()
		.build();
	URI uri = uriComponents.expand("Asia", "Seoul").toUri();
  • UriComponents 객체를 생성
  • UriComponentsBuilder에서 newInstance()로 객체 생성
  • scheme 설정
  • host 설정
  • port 설정
  • path 설정(템플릿 변수를 사용)
  • encode로 템플릿 변수 인코딩
  • UriComponents 객체를 최종 생성
  • uriComponents에서 파라미터로 템플릿 변수 값을 넣어 URI 객체 생성

요청 전송

String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
  • Rest 엔드포인트로 request 전송
  • getForObject() 메서드는 HTTP GEt 요청을 통해 서버의 리소스 조회
  • 파라미터로 URI와 응답으로 전달받을 클래스 타입 지정
WorldTime worldTime = restTemplate.getForObject(uri, WorldTime.class);
worldTime.getDateTime();
worldTime.getTimezone();
worldTime.getDay_of_week();
  • 커스텀 클래스를 하나 생성해서 받을 수도 있다.
ResponseEntity<WorldTime> response =
                restTemplate.getForEntity(uri, WorldTime.class);
response.getBody().getTimezone();
response.getStatusCode();
response.getHeaders().getContentType();
response.getHeaders().entrySet();
  • ResponseEntity 객체로 getForEntity 메서드를 사용해서 받으면 헤더와 바디 정보를 모두 받을 수 있다.
ResponseEntity<WorldTime> response =restTemplate.exchange(uri,HttpMethod.GET,null,
                        WorldTime.class);

  • exchange() 메서드를 사용해서 파라미터로 URI, HttpMethod, HttpEntity, Class를 전달

DTO(Data Transfer Object)

개념

  • 데이터를 전송하기 위한 객체
  • 서버와 클라이언트 사이의 요청 응답에 사용 가능

목적

  • @RequestParam을 통해 파라미터에 추가되는 것을 하나의 객체로 전달하면 코드를 간결하게 작성할 수 있다.
  • HTTP 요청의 수를 줄이기 위해 사용
  • 도메인 객체와 분리

데이터 유효성 검증의 단순화

  • 이메일 주소 형식처럼 유효한 데이터를 전달받기 위해 데이터를 검증하는 것을 데이터 유효성 검증이라고 한다.
  • 핸들러 메서드에서 유효성 검사 로직을 외부로 빼면 간결함을 유지 가능
  • DTO 객체에서 @email을 필드에 붙여 유효성 검증을 시도한다.
  • DTO 객체를 파라미터에 넣을 때에 @Valid를 붙여 핸들러 메서드를 간결하게 유지 가능

HTTP 요청/응답 데이터에 DTO 사용

  • @RequestParam을 사용한 부분에 @RequestBody를 통해 따로 생성한 DTO 객체를 넣어준다.
  • @RequestBody
    • JSON 형식의 Request Body를 DTO 클래스 객체로 변환
    • 즉, 클라이언트가 전송하는 요청 바디가 JSON 형식이어야 한다.
  • @ResponseBody
    • JSON 형식의 Response Body를 전달하기 위해 DTO 클래스 객체를 JSON 형식으로 변환하는 역할
    • @ResponseEntity나 @ResponseBody를 붙이면 JSON 형식으로 변환

DTO 유효성 검증

필요성

  • frontend에서 유효성 검증을 해도 중간에 값을 변경할 수 있기 때문에 backend 쪽에서 한번 더 유효성 검증을 한다.

라이브러리

  • dependencies에 implementation 'org.springframework.boot:spring-boot-starter-validation' 추가

유효성 검증

  • email
    • 값이 비어있지 않거나 공백 아님
    • 유효한 이메일 주소 형식
  • name
    • 값이 비어있지 않거나 공백 아님
  • phone
    • 값이 비어있지 않거나 공백 아님
    • 010으로 시작하는 11자리 숫자와 ‘-’로 구성
  • @NotBlank
    • 비어있지 않거나 공백 아님
    • null 값, 공백, 스페이스 허용 안 한다
  • @Email
    • 유효한 이메일 형식 주소인지 검증
  • @Pattern
    • 정규표현식에 매치되는 번호인지 검증
    • regexp=””로 표현
  • 핸들러 매서드에 @Valid를 추가해야 한다

쿼리 파라미터 및 @Pathvariable에 대한 유효성 검증

  • @PathVariable 뒤에 @Min(1)을 설정하면 1 이상의 값으로 검증
  • 클래스 레벨에 @Validated를 검사

Jakarta Bean Validation

  • 유효성 검증을 위한 표준 스펙
  • 애너테이션을 구현한 구현체는 HIbernate Validator

Custom Validator를 사용한 유효성 검증

  • Custom annotation 정의
  • custom validator 구현
  • DTO 클래스의 멤버 변수에 Custom Annotation을 추가하여 유효성 검증

'Backend boot camp > Session3' 카테고리의 다른 글

[Spring MVC] 트랜잭션  (0) 2022.11.07
[Spring MVC] JPA 데이터 액세스 계층  (0) 2022.11.07
[Spring MVC] JDBC DB Access Layer  (0) 2022.10.29
[Spring MVC] Exception Handle  (0) 2022.10.29
[Spring MVC] Service layer  (0) 2022.10.29