User Domain 생성, Get

 

 

 

 

프로젝트 구조와 경로

 

package com.example.restfulwebservice.user;

import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class User {
	
	private Integer id;
	private String name;
	private Date joinDate;

}
package com.example.restfulwebservice.user;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.stereotype.Service;

@Service
public class UserDaoService {

	private static List<User> users = new ArrayList<>();
	
	private static int usersCount = 3;
	
	static {
		users.add(new User(1,"kenneth", new Date()));
		users.add(new User(2,"elena", new Date()));
		users.add(new User(3,"chris", new Date()));
	}
	
	public List<User> findAll() {
		return users;
	}
	
	public User save(User user) {
		if (user.getId() == null) {
			user.setId(++usersCount);
		}
		
		users.add(user);
		return user;
	}
	
	public User findOne(int id) {
		for (User user : users) {
			if (user.getId() == id ) {
				return user;
			}
		}
		
		return null;
	}
	
}
package com.example.restfulwebservice.user;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
	
	private UserDaoService service;
	
	public UserController(UserDaoService service) {
		this.service = service;
	}
	
	//전체 목록 조회
	@GetMapping ("/users")
	public List<User> retrieveAllUsers() {
		return service.findAll();
	}
	
	//개별 조회
	//Get : /users/1 or /users/10 ... -> String
	@GetMapping("/users/{id}")
	public User retrieveUser(@PathVariable int id) {
		return service.findOne(id);
	}

}

@AllArgsConstructor

@RestController

@PathVariable 

 

 

 

 

 

 


 

 

 

 

 

Post

 

 

 

 

 

 

package com.example.restfulwebservice.user;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
	
	private UserDaoService service;
	
	public UserController(UserDaoService service) {
		this.service = service;
	}
    
    	//사용자 추가
	@PostMapping ("/users")
	//Post 사용 시, 클라이언트로부터 xml, json이 아닌 
   	//오브젝트 형 데이터를 받을 때에는  매개변수에 @RequestBody 사용)
	public void createUser(@RequestBody User user) {
		User savedUser = service.save(user);
	}

}

 

4번째 유저가 추가 됐다.

 

 

 

 

 

 

 

 

Status Code, Exception Handling

 

 

create 메소드에 코드를 조금 더 추가했다.

 

	public ResponseEntity<User> createUser(@RequestBody User user) {
		User savedUser = service.save(user);
		
		URI location = ServletUriComponentsBuilder.fromCurrentRequest()
						.path("/{id")
						.buildAndExpand(savedUser.getId())
						.toUri();
			
		return ResponseEntity.created(location).build();
	}

 

 

 

그리고 다시 한번 실행했을 때, 이번엔 다른 상태 코드를 받았다.

 

 

 

 

HTTP 상태 코드

 

200 : OK. 성공적으로 처리했을 때 쓰인다. 가장 일반적으로 볼 수 있는 HTTP 상태.

201 : Created. 요청이 성공적으로 처리되어서 리소스가 만들어졌음을 의미한다.

 

 

 

용도에 맞는 적당한 전달방식을 택해서

200번만이 아닌 적절한 상태 코드로 결과를 받는 것이 올바른 REST API 개발이다.

 

 

존재하지 않는 id를 호출할 경우에는 null 값을 받아 화면에 출력되는 것은 없지만

역시나 같은 상태 코드를 받는다.

데이터가 존재하지 않을 뿐, 서버의 요청이나 전송에는 문제가 없기 때문에 일어나는 현상이다.

 

그러므로 이 경우를 예외로 처리해본다.

 

 

 

 

 

 

기존의 개별 조회 메소드에 user 값이 null 일 경우 예외로 처리하는 메소드를 작성한다.

 

	//개별 조회
	//Get : /users/1 or /users/10 ... -> String
	@GetMapping("/users/{id}")
	public User retrieveUser(@PathVariable int id) {
		User user = service.findOne(id);
		
		if (user == null) {
			throw new UserNotFoundException(String.format("ID[%s] not found", id));
		}
		
		return user;
	}
package com.example.restfulwebservice.user;

public class UserNotFoundException extends RuntimeException {
	
	public UserNotFoundException (String message) {
		super(message);
	}

}

 

 

 

 

이번에는 200번 정상 응답이 아닌

500번 에러로 처리된다.

 

 

HTTP 상태 코드 500 Internal Server Error(내부 서버 오류): 

서버에 오류가 발생해 작업을 수행할 수 없을 때 사용된다. 

보통 설정이나 퍼미션 문제. 아니면 HTTP 요청을 통해 호출한 문서가 실제 HTML 문서가 아니라 JSP, PHP, 서블릿 등의 프로그램일 경우 그 프로그램이 동작하다 세미콜론을 빼먹는 등의 각종 에러로 비정상 종료를 하는 경우 이 응답코드를 보낸다.

 

 

이런 예외 화면을 클라이언트에게 그대로 노출하는 것은 지양해야 한다.

예외 코드를 그대로 노출하는 것은 미관상으로도 보안상으로도 적절하지 않은 일이라

프로그램을 완성할 때에는 에러 페이지를 따로 만들어 대체하는 것이 좋다.

 

 

그런데 사실 100번 사용자가 화면에 출력되지 않는 이유는 데이터가 없기 때문이지

내부 서버 오류 때문은 아니기 때문에

이것을 500번 오류가 아닌 400번대 Not Found 오류로 다시 처리해본다.

어노테이션으로 간단히 처리할 수 있는데,

예외처리를 위해 만든 클래스인 UserNotFOundException에

@ResponseStatus 어노테이션을 추가하는 것이다.

 

package com.example.restfulwebservice.user;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
	
	public UserNotFoundException (String message) {
		super(message);
	}

}

 

 

 

 

 

 

 

 

에러를 핸들링하기 위한 패키지를 따로 생성했고,

ExceptionResponse 라는 이름의 클래스를 생성했다.

 

package com.example.restfulwebservice.exception;

import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
	
	private Date timestamp;	// 예외 발생 일시
	private String message;	// 예외 메시지
	private String details;	// 예외 상세 내용

}
package com.example.restfulwebservice.exception;

import java.util.Date;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestController
// 프로젝트 내의 모든 컨트롤러가 실행될 때, 반드시 이 어노테이션을 가진 빈이 반드시 실행된다.
@ControllerAdvice 
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
	
	@ExceptionHandler(Exception.class)
	public final ResponseEntity<Object> handleAllExcptions(Exception ex, WebRequest request) {
		ExceptionResponse exceptionResponse = 
				new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
		
		return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
	}
}

 

 

 

 

에러코드 그대로 노출되는 것이 아니라

의도한 내용이 출력된다.

 

 

 

 

데이터가 존재하지 않는 100번 아이디에 대한 상태코드에 따른 에러 메시지로는 적절치 않기 때문에

400번 대를 처리할 예외 메소드를 다시 만들어 본다.

 

	@ExceptionHandler(UserNotFoundException.class)
	public final ResponseEntity<Object> handleUserNotFoundExcptions(Exception ex, WebRequest request) {
		ExceptionResponse exceptionResponse = 
				new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
		
		return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
	}

 

 

 

 

 

 

 


 

Delete

 

 

UserDaoService 클래스에 유저의 아이디를 가지고

정보를 검색한 후, 존재하는 정보가 있으면 해당 데이터를 삭제하는 메소드를 작성한다.

 

	public User deleteById(int id) {
		
		//컬렉션 프레임워크에서 저장된 요소들을 읽어오는 방법 중 하나
		Iterator<User> iterator = users.iterator();
		
		while (iterator.hasNext()) {
			User user = iterator.next();
			if(user.getId() == id) {
				iterator.remove();
				return user;
			}
		}
		
		return null;
	}
	//사용자 삭제 컨트롤러
	@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));
		}
	}

 

200번 OK로 상태코드가 반환되고, 빈 내용이 뜬다면 정상 처리 된 것이다. (void 이기 때문에)

 

전체 유저 조회를 해본다.

 

 

'프로젝트 > 파이널 프로젝트' 카테고리의 다른 글

RESTful Service 기능 확장 (1) - 유효성 체크  (0) 2022.11.23
파이널 프로젝트  (1) 2022.11.23
RESTful Service (1) - 기초  (0) 2022.11.22
Web Service / Web Application / SOAP  (0) 2022.11.22
API / REST / REST API  (0) 2022.11.22

다음과 같은 구조로

사용자를 관리하는 REST API을 생성하여 글을 작성할 수 있도록 예제 프로젝트를 만들어 본다.

 

 

 

 

 

스프링 부트 프로젝트 생성 방법

https://kiwikiwisae.tistory.com/224

 

 

 

 

 

 

application.properties에서 포트번호를 변경했다.

 

server.port=8088

 

 

프로젝트 명을 우클릭해서 Run As > Spring Boot App으로 실행하면 잘 실행된다.

 

 

 

 

Hello World를 출력 시켜보기 위한 컨트롤러를 생성했다.

 

package com.example.restfulwebservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloWorldController {

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

}

 

 

 

 

이것을 자바 빈 형식으로 바꾸어 json 형태로 출력해  본다.

 

package com.example.restfulwebservice;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor	// 생성자 어노테이션
public class HelloWorldBean {
	
	private String message;
	
}
package com.example.restfulwebservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloWorldController {

	@GetMapping(path = "/hello-world-bean")
	public HelloWorldBean helloWorldBean() {
		return new HelloWorldBean ("Hello World");
	}

}

 

 

 

DispatcherServlet

클라이언트의 모든 요청을 한 곳으로 받아서 처리한다.

요청에 맞는 Handler로 요청을 전달하고, Handler의 실행 결과를 Http Response 형태로 만들어 반환한다.

 

RestController : @Controller + @ResponseBody

View를 갖지 않는 REST Data (JSON / XML) 반환

 

 

 

가변 변수, Path Variable

 

 

 

package com.example.restfulwebservice;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloWorldController {

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

}

 

웹 서비스

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

 

머신-머신 또는 앱-앱 간의 상호작용을 위한 설계

플랫폼 독립적인 구조

어플리케이션 간 네트워크를 통한 통신

 

 

웹 어플리케이션

원격 서버에 저장되고 브라우저 인터페이스로 인터넷을 통해 제공되는 응용 프로그램

 

 

 

 

request (input) : 웹 서비스로 전달되는 정보

response (output) : 웹 서비스에서 처리된 정보를 클라이언트로 반환

 

 

 

일반적으로 문서의 포맷은 xml이나 json을 사용한다.

 

 

 

 

SOAP, Simple Object Access Protocol

일반적으로 xml 형식을 사용하며, 이미 정의된 형식을 사용하는 프로토콜

http, https, SMTP 등의 통신 프로토콜을 기반으로 xml 메시지를 요청하고 응답받는다.

정의된 형식을 사용하다보니 복잡한 구성으로 인해 오버헤드가 많고, http 상에서 전달되기에 무겁고, 인코딩/디코딩 과정 등 개발의 난이도가 높다.

그러나 이러한 보수적인 방식 때문에 보안, 트랜잭션, 데이터의 무결성 등의 특성으로 금융 정보 같은 민감한 데이터를 주고받을 때 많이 사용된다.

 

 

 

 

 

 

참고 :

https://sabarada.tistory.com/30

https://blog.wishket.com/soap-api-vs-rest-api-%EB%91%90-%EB%B0%A9%EC%8B%9D%EC%9D%98-%EA%B0%80%EC%9E%A5-%ED%81%B0-%EC%B0%A8%EC%9D%B4%EC%A0%90%EC%9D%80/

https://inf.run/huPx

기계-기계가 웹을 통해 정해진 규약인 http를 이용해 통신하는 규칙

 

 

API, Application Programming Interface

응용 프로그램에서 사용할 수 있도록 다른 응용 프로그램을 제어할 수 있게 만든 인터페이스.

API를 사용하면 내부 구현 로직을 알지 못해도 정의되어 있는 기능을 쉽게 사용할 수 있다.

 

여기서 인터페이스란 어떤 장치간에 정보를 교환하기 위한 수단이나 방법을 의미한다.

쉽게 말해 컴퓨터의 기능을 실행시키는 방법이 API.

 

REST, Representational State Transfer

자원의 이름으로 구분하여 해당 자원의 상태를 교환하는 것을 의미. (파일, 데이터 등을 자원이라 표현한다.)

서버와 클라이언트의 통신 방식 중 하나로,

HTTP URI를 통해 자원을 명시하고,

HTTP Method를 통해 자원을 교환한다. (CRUD - Get, Put, Post, Delete)

자원을 교환하기 위해 보낸 이 요청들의 결과는 HTTP 상태 코드로 알 수 있다. (200, 404, 500...)

 

 

REST의 규칙

Server-Client 구조 Data, 자원을 가진 쪽이 Server, 요청하는 쪽이 Client
클라이언트와 서버는 독립되어 있다. -> 자원을 공유하는 교집합 존재가 없어야 한다.
Stateless 클라이언트의 정보가 서버에 저장되지 않는다.
서버는 각각의 요청을 완전한 별개의 것으로 인식하고 처리한다.
Cacheable HTTP 프로토콜을 그대로 사용하기 때문에,
HTTP의 특징인 캐싱 기능을 적용하여 대량의 요청을 효율적으로 처리.
계층화
(Layered System)
클라이언트는 서버의 구성과 상관없이 REST API 서버로 요청.
서버는 다중 계층으로 구성될 수도 있음 (로드 밸런싱, 보안 요소, 캐시 등)
Code on Demand
요청을 받으면 서버에서 클라이언트로 코드 또는 스크립트(로직)을 전달,
클라이언트 기능을 확장시킨다
인터페이스 일관성
(Uniform Interface)
정보가 표준 형식으로 전송되기 위해 구성 요소 간 통합 인터페이스를 제공.
HTTP 프로토콜을 따르는 모든 플랫폼에서 사용 가능하도록 설계

 

그렇기 때문에 REST는 다음과 같은 장점이 있다.

  • HTTP 표준 프로토콜을 사용하는 모든 플랫폼에서 호환
  • 서버 - 클라이언트의 역할을 명확하게 분리
  • 여러 서비스 설계에서 생길 수 있는 문제를 최소화

 

REST API

이러한 REST의 아키텍쳐의 조건을 준수하는 어플리케이션 프로그래밍 인터페이스를 의미한다.

최근 많은 API가 REST 형식을 따른 API로 제공되고,

이런 웹 서비스를 RESTful 하다고 표현한다.

 

시스템을 분산하여 확장성과 재사용성을 높인다. (Layered System)

HTTP 표준을 따르고 있어 여러 프로그래밍 언어로 구현할 수도 있다.

 

 

REST API 설계 규칙

1. 웹 기반의 REST API 설계 시, URI를 통해 자원을 표현한다

       URI : https://kiwikiwisae.tistory.com/java/728  

       Resource : java

       Resource id : 728

2. 자원에 대한 조작은 HTTP 메소드 (CRUD) 를 통해 표현한다

     - URI에 행위가 들어가지 않고, Header를 통해 CRUD를 표현하여 동작을 요청한다.

3. 메시지를 통해 리소스를 조작한다

     - Header를 통해 content-type을 지정하여 데이터를 전달 (html, xml, json, text 등)

4. URI에는 소문자만 사용한다.

5. Resource의 이름이나 URI가 길어질 경우 하이픈 - 을 통해 가독성을 높인다. (언더바 _ 는 사용하지 않는다.)

6. 파일 확장자를 표시하지 않는다.

 

 

(Resource : URI (Uniform Resource Identifier), 인터넷 자원을 나타내는 고유한 주소. xml, html, json)

 

 

출처 :

https://youtu.be/lceS3HbGXt4

https://youtu.be/PmY3dWcCxXI

https://inf.run/huPx

+ Recent posts