이제 본격적으로 CRUD를 위한 준비를 해보겠다
1.Header<T> 생성
Header 클래스는
- transaction_time (통신 시각)
- status (통신 상태 코드 OK, ERROR)
- description (세부 내용:: 통신이 발생한 서버 주체)
과 같은 공통 정보를 갖고 있고, 매 요청과 응답마다 정보를 넘겨야 하므로 Header라는 클래스의 필드를 작성할 수 있다.
헤더는 요청과 응답을 감싸야 하므로 Header 클래스에는 UserApiRequest, UserApiResponse 클래스같은 요청,응답 타입만 들어온다.
이는 조금 더 확대해서 해석하자면 헤더는 어차피 요청과 응답에만 관여를 하는 데이터 객체이므로 2가지의 타입이 들어온다는 것이다.
그럼 우리는 타입 체크를 위해 제너릭이라는 선택지를 이용할 수 있다.
기존의 Header 클래스에서 타입을 붙혀서 아래와 같이 만들어보자.
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Header<T> {
private LocalDateTime transactionTime;
private String status;
private String description;
private T data;
}
첫 번쨰의 제너릭을 사용해서 컴파일 시점에 타입을 체크할 수 있게 했다.
이제 Header의 기능을 완벽하게 만들어보자.
여기서 말 하는 완벽하게는 모든 요청에 통신 시각, 통신 코드, 추신, 데이터를 추가해서
Header 클래스에서 Static으로 요청의 성공과 실패 여부를 OK, ERROR 메서드로 분리시켜보자.
메서드를 제네릭 메서드로 만들어주고 단순 성공 OK와 데이터가 존재하는 성공 OK(T data) 그리고 실패 ERROR() 데이터가 존재하는 실패 ERROR(description)이 네가지의 메서드를 만들어보자.
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Header<T> {
private LocalDateTime transactionTime;
private String status;
private String description;
private T data;
public static <T> Header<T> OK(){
return (Header<T>)Header.builder()
.transactionTime(LocalDateTime.now())
.status("OK")
.description("정상")
.build();
}
public static <T> Header<T> OK(T data){
return (Header<T>)Header.builder()
.transactionTime(LocalDateTime.now())
.status("OK")
.description("정상")
.data(data)
.build();
}
public static <T> Header<T> ERROR(String description){
return (Header<T>)Header.builder()
.transactionTime(LocalDateTime.now())
.status("ERROR")
.description(description)
.build();
}
public static <T> Header<T> ERROR(){
return (Header<T>)Header.builder()
.transactionTime(LocalDateTime.now())
.status("ERROR")
.build();
}
}
2.CrudInterface<Request,Response> 생성
공통 부분인 CRUD를 하나의 CrudInterface로 관리하기 위해 CrudInterface 인터페이스를 생성한다.
제네릭을 사용하면 특정 객체만 들어올 수 있게 제한할 수 있다.
그럼 어떤 데이터들이 CrudInterface를 사용할까?
아직 생성하지는 않았지만
바로 UserRequest, UserResponse, ItemRequest, ItemResponse, OrderRequest, OrderResponse 이런식의
Request,Response 객체들이다
하지만 이 요청과 응답 객체들은 서로 함께 움직여야 한다.
무슨 말이냐면 Request가 들어오면 Response로 반환하는 것이 기본 매커니즘이다.
이들 모두를 포함할 수 있는 제네릭은 바로 <Request, Response>이다. 그럼 인터페이스를 만들어보자
public interface CrudInterface<Request, Response> {
Response create(Request request);
Response read(Long id);
Response update(Request request);
Response delete(Long id);
}
여기서 잠깐, read()와 delete()는 왜 request가 아니라 ID 일지 생각해보자.
우리는 사용자의 검색에 대한 요청을 PathVariable 형태로 받을 것이다. 그러므로 사실상 요청 데이터에는 url의 경로가 사용될 것이므로 통신 객체가 아닌 Primitive 형태이므로 Long 형태를 사용한다.
이런 식으로 CRUD 메서드마다 요청과 응답이 서로 존재하는 형태가 될 것이다.
하지만 여기서 모든 요청과 응답은 Header에 의해 감싸져야 한다고 했으므로 아래와 같이 바뀌는게 더욱 적절하다.
(Request,Response 를 줄여서 Req,Res라고 쓰겠다)
public interface CrudInterface<Req, Res> {
Header<Res> create(Header<Req> request);
Header<Res> read(Long id);
Header<Res> update(Header<Req> request);
Header<Res> delete(Long id);
}
3.Controller 추상화 적용하기, Service 추상화 적용하기
Controller 추상화를 적용하면 중복코드 사용을 줄일 수 있다.
서비스로직클래스 역시 마찬가지로 ItemApiLogicService, UserApiLogicService등 여러 Service클래스는 해당 Entity의 Repository를 직접 작성해 사용해왔고, Service 추상화를 적용해 중복코드 사용을 줄일 수 있다.
Controller 추상화, Service추상화를 적용하면, 프로젝트를 진행할 때 온전히 C,R,U,D구현에 집중할 수 있다.
1) Service 추상클래스 생성
@Component
// Service 추상화, <Req,Res,Entity>까지 Entity를 추가로 받는데, API컨트롤러에서 Entity를 받으면
// Service 추상클래스에서 해당 Entity의 Repository 의존성 주입
public abstract class BaseService<Req,Res,Entity> implements CrudInterface<Req,Res> {
// 해당 Entity의 Repository 의존성 주입
@Autowired(required = false)
protected JpaRepository<Entity,Long> baseRepository; // 상속받는 클래스만 접근가능
// JpaRepository<Item,Long>
// JpaRepository<User,Long> ...
}
참고로
[ @Component 어노테이션 ]
개발자가 직접 개발한 클래스를 Bean으로 등록하고자 하는 경우에는 @Component 어노테이션을 활용하면 된다
2) Controller 추상클래스 생성
@Component
// 컨트롤러 추상화
// 추상클래스 이용해 컨트롤러 정의, API컨트롤러에 상속해줌으로써 중복코드사용 줄일수있음
public abstract class CrudController<Req,Res,Entity> implements CrudInterface<Req,Res> {
// 상속받는 클래스만 접근가능
@Autowired(required = false)
protected BaseService<Req,Res,Entity> baseService;
@Override
@PostMapping("")
public Header<Res> create(@RequestBody Header<Req> request) {
return baseService.create(request);
}
@Override
@GetMapping("{id}")
public Header<Res> read(@PathVariable Long id) {
return baseService.read(id);
}
@Override
@PutMapping("")
public Header<Res> update(@RequestBody Header<Req> request) {
return baseService.update(request);
}
@Override
@DeleteMapping("{}")
public Header delete(@PathVariable Long id) {
return baseService.delete(id);
}
}
이러면
crud 에 기본적으로 필요한 준비는 끝!
'웹 개발 > 🍃 SpringBoot' 카테고리의 다른 글
SpringBoot | HTML, CSS, JS 변경 시 실시간 반영 방법 (0) | 2023.05.19 |
---|---|
SpringBoot | application.yml (0) | 2023.04.30 |
SpringBoot | model(repository) with JPA Repository (0) | 2023.04.19 |
SpringBoot | model(entity) with JPA Auditing (0) | 2023.04.19 |
SpringBoot | ModelAndView에 데이터얹기 + Thymeleaf (0) | 2023.04.15 |