[JoyMall] 자바 제네릭(Generics)을 이용한 ResponseEntity 공통화

청포도봉봉이·2024년 4월 29일
1

JoyMall

목록 보기
3/13
post-thumbnail

제네릭(Generics)이란

지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크(compile-time type check)를 해주는 기능입니다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어듭니다.

ArrayList<String> list = new ArrayList<>();

<>이 꺽쇠 괄호가 제네릭입니다. 다룰 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여줍니다. 위 코드의 리스트 클래스의 자료형은 String 타입으로 지정되어 문자열 데이터만 적재할 수 있게 됩니다.

class Box {
	Object item;
    
    void setItem(Object item) {
    	this.item = item;
    }
    Object getItem() {
    	return item;
	}
}

위와 같은 코드를 제네릭으로 바꿔보면 아래와 같이 됩니다.

class Box<T> { // 지네릭 타입 T를 선언
	T item;
    
    void setItem(T item) {
    	this.item = item;
	}
    T getItem() {
		return item;
	}
}

Box<T>에서 T를 타입 변수(type variable)라고 하며 Type의 첫 글자에서 따온 것입니다. 타입 변수는 T가 아닌 다른 것을 사용해도 됩니다.

기존 Controller

기존에 Controller의 코드는 아래와 같았습니다.

    @PostMapping("/customers")
    public ResponseEntity<CustomerResponse> signup(@RequestBody @Valid CustomerDTO customerDTO) {
        CustomerResponse customerResponse = customerService.save(customerDTO);
        return ResponseEntity.status(HttpStatus.CREATED).body(customerResponse);
    }

    @PostMapping("/customers/login")
    public ResponseEntity<CustomerResponse> login(@RequestBody @Valid CustomerLoginRequest customerLoginRequest) {
        CustomerResponse customerLoginResponse = customerService.findByEmailAndPassword(customerLoginRequest);
        return ResponseEntity.status(HttpStatus.OK).body(customerLoginResponse);
    }

    @GetMapping("/customers")
    public ResponseEntity<List<CustomerResponse>> findCustomers() {
        return ResponseEntity.status(HttpStatus.OK).body(customerService.findAll());
    }

    @GetMapping("/customers/{id}/address")
    public ResponseEntity<AddressDTO> findAddressByCustomerId(@PathVariable Long id) {
        return ResponseEntity.status(HttpStatus.OK).body(customerService.findAddressByCustomerId(id));
    }

위 코드를 보면

ReponseEntity.status(`상태코드`).body(`전달할 객체`);

를 공통화해줄 수 있다는 생각을 했습니다. 이렇게 하면 코드가 간결해지고 유지보수하기 간편해질 것입니다.

그래서 처음에 생각한건 2가지 방법이 있었습니다.

공통 Class 생성

  1. 전역변수 ResponseEntity.BodyBuilder 반환
public class ApiResponse {
    public static final ResponseEntity.BodyBuilder OK = ResponseEntity.status(HttpStatus.OK);
    
    
}
  1. 정적 팩토리 메서드
public class ApiResponse {
	public static <T> ResponseEntity<T> OK(T object) {
		return ResponseEntity.ok().body(object);
	}
}

전역변수 ResponseEntity.BodyBuilder 반환

    @GetMapping("/products/search")
    public ResponseEntity<ProductPageResponse> search(@ModelAttribute ProductSearchDTO productSearchDTO) {
        Pageable pageable = PageRequest.of(productSearchDTO.getPageNumber(), productSearchDTO.getPageSize());
        ProductPageResponse productPageResponse = productService.search(productSearchDTO.getKeyword(), pageable);
        return OK.body(productPageResponse); // body를 매번 호출해야함.
    }

첫 번째 방법은 static final을 이용해서 불변의 전역변수를 생성 후 반환하여 Controller에서 body()만 바꿔주는 방법입니다.

정적 팩토리 메서드 이용

    @GetMapping("/products/search")
    public ResponseEntity<ProductPageResponse> search(@ModelAttribute ProductSearchDTO productSearchDTO) {
        Pageable pageable = PageRequest.of(productSearchDTO.getPageNumber(), productSearchDTO.getPageSize());
        ProductPageResponse productPageResponse = productService.search(productSearchDTO.getKeyword(), pageable);
        return OK(productPageResponse);
    }

두 번째 방법은 ApiResponse 클래스에서 body의 내용을 포함한 객체를 생성해서 반환해주는 방법입니다.

저는 이 두 번째 방법을 채택하였습니다. 이 방법은 정적 팩토리 메서드 패턴을 활용합니다. 정적 팩토리 메서드는 객체 생성 로직을 캡슐화하고, 메서드를 통해 객체를 생성하여 반환하는 기법입니다. 여기서는 OK()라는 정적 메서드를 통해 ResponseEntity 인스턴스를 생성하고 반환합니다.

@RestController
@RequiredArgsConstructor
public class ProductController {

    private final ProductService productService;

    @GetMapping("/products")
    public ResponseEntity<List<ProductDTO>> findAll() {
        return OK(productService.findAll());
    }

    @GetMapping("/products/search")
    public ResponseEntity<ProductPageResponse> search(@ModelAttribute ProductSearchDTO productSearchDTO) {
        Pageable pageable = PageRequest.of(productSearchDTO.getPageNumber(), productSearchDTO.getPageSize());
        ProductPageResponse productPageResponse = productService.search(productSearchDTO.getKeyword(), pageable);
        return OK(productPageResponse);
    }

    @GetMapping("/products/{id}")
    public ResponseEntity<ProductDTO> findById(@PathVariable Long id) {
        return OK(productService.findById(id));
    }
}

위와 같이 좀 더 깔끔하게 리팩토링 할 수 있었습니다.

profile
서버 백엔드 개발자

0개의 댓글