๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŒฟ Spring

[Spring Boot/RESTful] User Service API ๊ตฌํ˜„

by nitronium102 2022. 2. 17.

1) domain ํด๋ž˜์Šค ์ƒ์„ฑ

๋„๋ฉ”์ธ

ํŠน์ • ์ „๋ฌธ ๋ถ„์•ผ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ „๋ฌธ ์ง€์‹

์Šคํ…Œ๋ ˆ์˜คํƒ€์ž…

ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ ์–ด๋Š ์šฉ๋„๋กœ ์‚ฌ์šฉ๋  ๊ฒƒ์ธ์ง€ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฒƒ → ์˜์กด์„ฑ ์ฃผ์ž…ํ•  ๋•Œ ๋„์›€์ด ๋œ๋‹ค.

ex) @RestController, @Service

User domain

์—ฌ๊ธฐ์—์„œ๋Š” @Data annotation์„ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ @Data ์‚ฌ์šฉ์€ ๋˜๋„๋ก ์ž์ œํ•˜๊ณ  @Getter @Setter ๋“ฑ์„ ๋”ฐ๋กœ ์ง€์ •ํ•ด์ฃผ๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. 

(@Data ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ํ•œ๊บผ๋ฒˆ์— ๋งŽ์€ ๊ธฐ๋Šฅ์„ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๋„ ํ•˜๊ณ , @ToString์€ ์ž˜๋ชป ์“ฐ๋ฉด JPA์—์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ)

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

2) DAO + Service ํด๋ž˜์Šค ์ƒ์„ฑ

ํ•˜์ง€๋งŒ db ์—ฐ๊ฒฐ์„ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— DAO ํด๋ž˜์Šค๋Š” ํ•„์š”์—†๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ Service ํด๋ž˜์Šค๋งŒ ๋งŒ๋“ค์–ด๋ณด๊ฒ ๋‹ค. 

@Service
public class UserDaoService {
	private static List<User> users = new ArrayList<>();

	private static int usersCount = 3;

	static {
		users.add(new User(1, "minji", true, new Date()));
		users.add(new User(2, "nitro", false, new Date()));
		users.add(new User(3, "ewha", false, 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;
	}

	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;
	}

	public User updateById(int id){
		Iterator<User> iterator = users.iterator();
		while(iterator.hasNext()){
			User user = iterator.next();
			if (user.getId() == id){
				return user;
			}
		}
		return null;
	}
}

3) Controller ํด๋ž˜์Šค ์ƒ์„ฑ

์˜์กด์„ฑ ์ฃผ์ž…

- setter ๋ฉ”์†Œ๋“œ / ์ƒ์„ฑ์ž์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ / @Autowired๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

- userController์—์„œ service๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„ ์˜์กด์„ฑ ์ฃผ์ž…ํ•œ๋‹ค.(์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ์˜์กด์„ฑ ์ฃผ์ž…)

=> DEBUG ๋ ˆ๋ฒจ์˜ log์—์„œ userController๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋ฉด ์˜์กด์„ฑ ์ฃผ์ž…์ด ์™„๋ฃŒ๋˜์–ด ์•„๋ž˜์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œฌ๋‹ค.

'userController' via constructor to bean named 'userDaoService'
@RestController
public class UserController {

	private UserDaoService service;

	// ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ์˜์กด์„ฑ ์ฃผ์ž…
	public UserController(UserDaoService service) {
		this.service = service;
	}

	// GET users/10 -> String
	// ๊ธฐ๋ณธ์€ String์ด์ง€๋งŒ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ int ๋ฐ›์œผ๋ฉด ์ž๋™ ๋ณ€๊ฒฝ
	@GetMapping("/users/{id}")
	public User retrieveUser(@PathVariable int id) {
		User user = service.findOne(id);
		if (user == null){
			// ์ง์ ‘ ์ƒํ™ฉ์— ๋งž๋Š” ์˜ˆ์™ธ ํด๋ž˜์Šค ์ž‘์„ฑ
			throw new UserNotFoundException(
            	String.format("ID[%s] is not found", id));
		}
		return user;
	}

	@PostMapping("/users")
    	// @RequestBody : ํ˜„์žฌ variable์ด requestBody ํ˜•์‹์œผ๋กœ ๋“ค์–ด์˜ด์„ ์•Œ๋ฆผ
	public ResponseEntity<User> createUser(@RequestBody User user) { 
		User savedUser = service.save(user);

		// ServletUriComponentBuilder -> ํ˜„์žฌ ์š”์ฒญ์˜ URI๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.
		URI location = ServletUriComponentsBuilder.fromCurrentRequest()
			.path("/{id}")
			.buildAndExpand(savedUser.getId())
			.toUri();

		// ResponseEntity -> HTTP ์ƒํƒœ ์ฝ”๋“œ์™€ ์ „์†กํ•˜๊ณ  ์‹ถ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†ก
		// HTTP 201 Created : ์š”์ฒญ์ด ์„ฑ๊ณต์ ์ด์—ˆ์œผ๋ฉฐ ๊ทธ ๊ฒฐ๊ณผ๋กœ ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๊ฐ€ ์ƒ์„ฑ๋จ
		return ResponseEntity.created(location).build();
	}

 

HTTP Status Code

  • 2XX → OK
  • 4XX → Client
  • 5XX → Server

@ServletUriComponentsBuilder๋ฅผ ์ด์šฉํ•ด ํ˜ธ์ถœ ๋ฐฉ๋ฒ•์— ๋”ฐ๋ผ ์ ์ ˆํ•œ status code๋ฅผ ๋ฐ˜ํ™˜ํ•จ

@PostMapping("/users")
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 Status Code๋ฅผ ์ด์šฉํ•œ Exception Handling

customizedException์„ ์‚ฌ์šฉํ•˜๋ฉด ์ƒํ™ฉ์— ๋”ฐ๋ผ ์•Œ๋งž์€ HTTP ์ƒํƒœ ์ฝ”๋“œ์™€ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ณดํ†ต์€ customizedExceptionHandler๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์–‘ํ•œ customizedException์„ ์‚ฌ์šฉํ•œ๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” @ResponseStatus๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ CustomizedResponseEntityExceptionHandler.class์—์„œ UserNotFoundException์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, Exception Response๋ฅผ ๋ฐ˜ํ™˜(Http status code๋„ ๋ฐ˜ํ™˜)ํ•œ๋‹ค.
=> ResponseStatus ์ƒ๋žต ๊ฐ€๋Šฅ

[Controller]
@GetMapping("/users/{id}")
public User retrieveUser(@PathVariable int id) {
	User user = service.findOne(id);
	if (user == null){
		throw new UserNotFoundException(String.format("ID[%s] is not found", id));
	}
	return user;
}

[Exception]
@ResponseStatus(HttpStatus.NOT_FOUND) // ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ ๊ฐ’ ์ง€์ •
public class UserNotFoundException extends RuntimeException {
	public UserNotFoundException(String message) {
		super(message); // ex.getMessage()
	}
}

 

Spring AOP(Aspect Oriented Programming)๋ฅผ ์ด์šฉํ•œ Error Handling

์Šคํ”„๋ง ๋ถ€ํŠธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์—์„œ ๊ณตํ†ต์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜๋Š” ๋กœ์ง์ด๋‚˜ ๋ฉ”์†Œ๋“œ ์ถ”๊ฐ€

Aspect๋กœ ๋ชจ๋“ˆํ™”ํ•˜๊ณ  ํ•ต์‹ฌ์ ์ธ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ๋ถ„๋ฆฌํ•˜์—ฌ ์žฌ์‚ฌ์šฉ

1) ๊ธฐ๋ณธ Response ์„œ์‹ ์ง€์ •

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
	private Date timestamp;
	private String message;
	private String details;
}

2) ์˜ˆ์™ธ ์ฒ˜๋ฆฌ handler ์ƒ์„ฑ

  • @ControllerAdvice
  • ๋ชจ๋“  Controller๊ฐ€ ์‹คํ–‰๋  ๋•Œ annotation์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” bean์ด ํ•ญ์ƒ ๋จผ์ € ์‹คํ–‰๋จ
[CustomizedResponseEntityExceptionHandler]
@RestController
@ControllerAdvice // ๋ชจ๋“  @Controller๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์‹คํ–‰๋จ -> ์ „์—ญ ๋ฐœ์ƒ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
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));
		// ResponseEntity : http status code์™€ ์ „์†กํ•˜๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ์ „์†ก
		return new ResponseEntity(exceptionResponse,
        	HttpStatus.INTERNAL_SERVER_ERROR);
	}

3) ์ƒํ™ฉ๋ณ„ custome Exception ์ƒ์„ฑ

custom exception ์ƒ์„ฑ ํ›„์—๋Š” customizedExceptionHandler์— ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

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

 

๋Œ“๊ธ€