이전에 공통 응답 클래스를 만들어서 활용하기는 했지만 보기에만 조금 더 깔끔해진 것 같지 사용하기 좋아보이지는 않는다.
오늘은 공통 응답을 조금 더 수정하기 쉽고 사용하기 좋게 바꾸려고 한다.
controller/MemberController
package com.seungh1024.controller;
import com.seungh1024.Response;
import com.seungh1024.entity.Member;
import com.seungh1024.service.MemberService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/member")
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService){
this.memberService = memberService;
}
@PostMapping("/signup")
public Response<?> signup(@RequestBody Member member){
boolean result = memberService.createMember(member);
if(result){
return new Response<>().builder()
.code(200)
.message("성공")
.data(null)
.build();
}else{
return new Response<>().builder()
.code(400)
.message("실패")
.data(null)
.build();
}
}
...
}
기존 컨트롤러의 응답을 보면 공통되는 것들이 보인다. code, message, data 이렇게 같은 형식이 반복된다.
만약 서비스가 복잡해져서 저런 if문이 100개가 넘어버린다면 상당히 머리가 아플 것같다.
그래서 조금 더 효율적으로 공통 응답을 작성해보려 한다.
Enum(열거형)
우선 작성하기 전에 Enum을 사용하므로 뭔지 간단하게 알아보고 가자면
정의
Enum(열거형)은 Enumeration의 약자로 여러가지를 하나 하나 늘어놓는다는 의미이다. 즉 enum은 요소, 멤버라 불리는 값의 집합을 이루는 자료형이다.
Java에서 Enum타입은 상수 컬렉션을 정의하는 데 쓰이는 특수한 자바 클래스이다. 여기에는 상수, 메서드 등이 포함될 수 있다. 여기에서 Enum은 final 변수처럼 값을 바꿀 수 없는 상수 그룹을 나타내는 특수한 클래스로 class나 interface 대신 enum키워드를 사용하여 선언하고 상수는 쉼표로 구분하여 나타낸다. 이때 해당 상수는 대문자여야 한다.
이런 말보다는 예시를 보는게 이해가 빠른 것 같다.
예시로 요일을 enum 클래스로 만들어보면
public enum Day{
MON, TUE, WED, THU, FRI, SAT, SUN
}
이렇게 단순하게 요일을 나열하여 enum 클래스로 만들 수 있다.
여기에 해당 요소들에게 특정한 값을 넣어주고 싶다면 아래와 같이 만들 수 있다.
public enum Day{
MON("월요일"),
TUE("화요일"),
WED("수요일"),
THU("목요일"),
FRI("금요일"),
SAT("토요일"),
SUN("일요일")
;
private final String dayName;
Day(String dayName){
this.dayName = dayName;
}
public String dayName(){
return dayName();
}
}
Enum 요소에 특정한 값을 매핑하고 싶다면 dayName 과 같은 필드 값을 추가하고 원하는 값을 각 요소에 넣어주면 된다.
필드 값을 여러개 추가하고 싶다면 그만큼 추가하고 각 값들을 각각의 요소에 넣어주면 된다.
필드 값을 추가하면 생성자도 추가해야하며 특이한 점은 생성자 앞에 public, private이 붙지 않는다는 점이다.
또한 enum 클래스는 생성자가 있어도 new로 생성할 수 없다.
이제 enum을 활용하여 공통 응답을 만들어 볼텐데 그 전에 해당 공통 응답을 작성할 응답 모듈을 새로 생성하자.
아래와 같이 response라는 모듈을 만들었고 common이라는 폴더 안에 공통으로 사용될 모듈들을 하나의 폴더로 묶었다.
ResponseType(response 모듈)
package com.seungh1024;
public enum ResponseType{
SUCCESS(200),
FAILURE(400);
private int code;
ResponseType(int code){
this.code = code;
}
public int getCode(){
return this.code;
}
public String getMessage(){
return this.name();
}
}
우선 enum을 사용하여 공통되는 응답들을 묶어보았다. code와 message를 enum으로 만들었다고 생각하면 된다
나는 성공, 실패 2가지만 있지만 더 만들고싶다면 만들어서 사용하면 된다.
여기서 message 필드는 없는데 getMessage() 메소드는 있는 것을 볼 수 있다.
name() 메소드를 enum에서 호출하면 해당 필드를 String 형식으로 반환한다.
즉 SUCCESS 필드는 “SUCCESS”, FAILURE 필드는 “FAILURE”로 반환된다.
이제 이 enum 클래스를 이용해서 공통 응답을 만들고 이걸 컨트롤러에 적용할 것이다.
그럼 컨트롤러에서 builder 패턴으로 길고 가독성이 좋지 않았던 코드가 짧아질 것이다.
그 전에 공통 응답 클래스에서 사용할 Generic 타입에 대해 간단하게 알아보자.
Generic
Generic(제네릭) 이란?
데이터 형식에 의존하지 않고 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법이다.
ArrayList, LinkedList를 다 받을 수 있는 것이다.
기존에는 Object로 받을 수는 있었지만 다시 형 변환을 하는 등의 제약이 있었다.
제네릭은 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정되는 것을 의미한다. 즉, 타입을 미리 지정해주는 것이 아닌 필요에 의해 지정할 수 있도록 하는 일반(Generic) 타입이라는 것이다.
보통 제네릭은 아래 표의 타입을 많이 사용한다.
제네릭 사용법
public class GenericTest<T>{...}
제네릭 타입의 클래스나 인터페이스의 경우 위와 같이 선언한다.
T타입은 해당 블럭 안에서만 유효하다.
또한 아래와 같이 제네릭 타입을 두 개로 둘 수도 있다.
public class TwoGeneric<T,K>{...}
이제 이걸 사용할 땐 아래와 같이 구체적인 타입을 명시해서 사용해 주어야한다.
TwoGeneric<String,Integer> tg = new TestGeneric<String,Integer>();
어디서 많이 보던 형태일 것이다. 하나의 경우 ArrayList<>나 LinkedList<>, 두 개의 경우 HashMap<>이 위와 같은 형태를 띈 것을 보았을 것이다.
위 예시대로면 T → String, K → Integer가 되는데 타입 파라미터 T의 경우 참조 타입(Reference Type)만 명시할 수 있다. 즉 int, char, double 등의 원시 타입(Primitive Type)은 올 수 없다. 그래서 int, double 등의 원시 타입의 경우 Integer, Double과 같이 Wrapper Type으로 사용하는 이유이다.
class ClassName<E> {
private E data;
void set(E data) {
this.data = data;
}
E get() {
return data;
}
}
요런 제네릭타입 클래스가 있다고 하면 해당 클래스 내에서 제네릭 타입이 영향을 미치니 제네릭 타입 변수로 선언할 수도 있고 메소드로 선언할 수도 있다.
제네릭 메소드
일반적인 메소드 말고 메소드에도 제네릭을 적용시키는 방법이다.
메소드의 선언부에 적은 제네릭으로 리턴 타입, 파라미터의 타입이 정해지는 메소드이다.
[접근 제어자] <제네릭 타입> [반환 타입] [메소드명]([제네릭타입] [파라미터]){} 와 같이 선언된다
public static <T> T getGeneric(T data){return data};
여기서 제네릭 타입은 <T>가 해당이 되고 리턴 타입은 T가 되는 것이다.
T 타입 data가 들어왔으니 data를 리턴하면 에러 없이 잘 사용할 수 있다.
이렇게 제네릭 메소드를 사용하면 클래스의 T와 메소드의 T는 같은 문자를 사용하더라도 다른 문자를 의미한다.
여기까지 했다면 진짜 공통 응답을 만들어보자
공통 응답 만들기
Response(response 모듈)
package com.seungh1024;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import static com.seungh1024.ResponseType.SUCCESS;
import static com.seungh1024.ResponseType.FAILURE;
@Getter //이거 왜 필요하지?
@NoArgsConstructor
public class Response<T> {
private int code;
private String message;
private T data;
@Builder
public Response(ResponseType responseType, T data){
this.code = responseType.getCode();
this.message = responseType.getMessage();
this.data = data;
}
public static Response success(){
return Response.builder()
.responseType(SUCCESS)
.build();
}
public static <T> Response<T> success(T data){
return new Response<>(SUCCESS,data);
}
public static Response failure() {
return Response.builder()
.responseType(FAILURE)
.build();
}
}
code는 status code, message에는 응답 메세지, data에는 responsebody에 들어갈 데이터 내용이다.
data는 어떤 타입이 올지 모르니 제네릭을 사용하였다.
@Builder패턴을 사용하였고 이전에 만든 ResponseType의 SUCCESS, FAILURE를 변수로 선언하여 사용하고있다.
해당 Enum들을 이용하여 code와 message를 생성하는 것을 볼 수 있다.
success의 경우 response body가 없는 일반 메소드와 response body가 있는 제네릭 메소드 두 가지가 있다.
일반 메소드의 경우 status code, message만 응답하고 제네릭 메소드의 경우 data를 넣은 객체로 리턴해야하기 떄문에 T 타입을 사용한 것을 볼 수 있다.
위의 제네릭 메소드 선언하는 방식대로 <T> → 제네릭 타입, Response<T> → 리턴 타입, T → 파라미터 타입을 선언한 것을 볼 수 있다.
공통 응답 클래스의 Getter
마지막으로 공통 응답 클래스의 @Getter에서 내가 왜 필요한지 몰랐는데 찾아보니 이유가 있었다.
Spring Boot의 경우 return ResponseEntity.ok(data) 이런 방식으로 응답하는 경우가 많다.
이런 경우에 우리가 data를 넣고 200으로 응답을 보내지만 해당 response객체가 만들어지는 코드는 어디에도 없다. 이걸 Spring이 공통되는 내용이기 때문에 내부적으로 response객체에 status code, header, body 등을 세팅해 주는 것이다. jsp를 사용해 봤다면 그런 코드를 작성해 봤으니 바로 알 것이다.
RestController는 json 타입으로 리턴을 한다.
그렇다면 json타입으로 data를 바꿔주기 위해선 해당 data객체를 뜯어보고 꺼내와야 하므로 Getter 메소드가 필요한 것이다. 그럼 Spring이 알아서 Getter를 사용하여 json 객체로 바꾸어주고 우리가 보는 아래와 같은 형태로 응답을 받아볼 수 있는 것이다.
이제 공통 응답을 만들었으니 컨트롤러에 적용시켜보자.
controller/MemberController(member-api 모듈)
package com.seungh1024.controller;
import com.seungh1024.Response;
import com.seungh1024.entity.Member;
import com.seungh1024.service.MemberService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.seungh1024.Response.success;
import static com.seungh1024.Response.failure;
@RestController
@RequestMapping("/api/v1/member")
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService){
this.memberService = memberService;
}
@PostMapping("/signup")
public ResponseEntity signup(@RequestBody Member member){
boolean result = memberService.createMember(member);
if(result){
return new ResponseEntity(success(),HttpStatus.OK);
}else{
return new ResponseEntity(failure(),HttpStatus.BAD_REQUEST);
}
}
@GetMapping("/search/all")
public ResponseEntity searchAll(){
List<Member> result = memberService.allMemberList();
if(result.size() >0 ){
return new ResponseEntity(success(result),HttpStatus.OK);
}else{
return new ResponseEntity(failure(),HttpStatus.BAD_REQUEST);
}
}
@GetMapping("/search/{email}")
@ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity searchByEmail(@PathVariable("email") String email){
Member member = memberService.findMemberByEmail(email);
if(member != null){
return new ResponseEntity(success(member),HttpStatus.OK);
}else{
return new ResponseEntity(failure(),HttpStatus.BAD_REQUEST);
}
}
@PatchMapping("/update")
public ResponseEntity updateByPk(@RequestBody Member member){
boolean result = memberService.updateMemberPassword(member.getMemberId(), member.getMemberPassword());
if(result){
return new ResponseEntity(success(),HttpStatus.OK);
}else{
return new ResponseEntity(failure(),HttpStatus.BAD_REQUEST);
}
}
@DeleteMapping("/delete/{pk}")
public ResponseEntity deleteByPk(@PathVariable("pk") int pk){
boolean result = memberService.deleteMember(pk);
if(result){
return new ResponseEntity(success(),HttpStatus.OK);
}else{
return new ResponseEntity(failure(),HttpStatus.BAD_REQUEST);
}
}
}
훨씬 깔끔해진 것을 볼 수 있다.
헤더에 상태 코드를 넣어주기 위해 ResponseEntity를 사용하여 리턴했다.
원하던대로 각각 200, 400 으로 응답이 잘 왔다.
'Java > Spring boot 프로젝트' 카테고리의 다른 글
Spring Entity와 DTO 구분하기 -게시판 만들기(7) (0) | 2023.03.16 |
---|---|
@ControllerAdvice를 사용한 예외 처리,에러 핸들링 - 게시판 만들기 (6) (0) | 2023.03.16 |
JPA 레포지터리, Member CRUD - 게시판 만들기(4) (0) | 2023.02.20 |
Spring Boot JPA 설정, 테이블 생성(Entity) - 게시판 만들기(3) (2) | 2023.02.19 |
Spring Boot Mysql 연결, 민감한 정보 관리하기(환경 변수 설정하기)- 게시판 만들기(2) (0) | 2023.02.18 |