실무에서는 기능개발
문제를 넘어서 기술 개발
문제가 더 중요해진다.
처리할 데이터가 많은 실무에서는 성능, 유지 보수를 만족하는 기술 개발
을 하는 것이 어렵다!!
이번 강의에서는 API 개발
과 성능 최적화
이 두가지에 초점을 맞춘 강의가 될 것 같다.
- REST API 개발
- 성능 최적화
먼저 API에 대해 알아보자
API 란?
간단하게 말하자면 API는 두 소프트웨어가 서로 통신
할 수 있게 하는 메커니즘이다.
API를 일종의 함수라고 보면 서로 다른 두 소프트웨어(클라이언트, 서버)가 통신
을 하기 위해 API라는 함수를 이용하는 것이다!
지금까지는 템플릿 엔진(thymleaf)를 이용하여 서버에서 이것 저것 렌더링 해주는 방식을 사용했다.
API를 이용하면 클라이언트가 서버에 Request
를 보내면 서버가 클라이언트에 Response
해주는 방식이 사용된다.
API에 대한 자세한 설명은 여기에 들어가보자
JPA는 엔티티 개념이 있기 때문에 지금까지 해왔던 API와 다른점이 있다고 하니 주의하며 공부하자!!
REST API 란?
API의 작동 방식 중 하나로
클라이언트와 서버는 HTTP를 사용하여 데이터를 교환하는 방식을 사용한다.
HTTP의 편리성을 최대한 이용한 API 방식이라고 보면 될 것 같다.
REST는 GET, PUT, DELETE 등의 함수 집합을 정의한다.
REST API에 대한 자세한 설명은 여기에 들어가보자
API와 템플릿 엔진을 이용한 VIEW는 공통적으로 처리하는게 서로 다르기 때문에
API를 개발할 때는 템플릿 엔진을 사용하여 렌더링하는 컨트롤러와 API 스타일 컨트롤러를 분리하는게 좋다.
@RestController // @Controller + @ResponseBody
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/** 여기에 클래스 및 함수 작성 **/
}
@RestController
는 @Controller
와 @ResponseBody
의 기능을 둘다 가지고 있는 친구이다
version_1(v1) 과 version_2(v2) 두가지 방법을 사용해서 회원 등록을 처리하는 API를 구현할 것이다
v1의 방식으로 하면 안되니까 v2까지 만들었겠지??
private final MemberService memberService;
/**
* 회원 등록 API v1 : 엔티티를 파라미터로 받아서 사용
* 결론 : 이렇게 쓰면안됨
*/
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
@Data
static class CreateMemberRequest {
private String name;
}
회원 등록 요청과 응답을 위한 CreateMemberRequest()
& CreateMemberResponse()
클래스를 만들고 함수를 정의
@RequestBody
: json 데이터를 처리해주는 친구. json으로 온 body를 그대로 매핑해서 넣어준다.
@Valid
: 데이터가 유효한지 검증해주는 친구. javax.validation을 검증한다
이게 왜 문제냐?!
API에 맞추기 위해 엔티티를 수정하면 API 스펙 자체가 변한다.
엔티티는 여러곳에서 쓰이기 때문에 바뀔 확률이 높은데
API가 엔티티를 그대로 받아서 사용하면 엔티티가 바뀔 때 마다 API 스펙이 바뀌어 버려서 오류가난다.
API 스펙이 바뀌어?? 무슨소리지
예를 들어줄게
v1 버전을 통해 회원 가입을 할 때 이름에 아무 값도 안넣고(NULL) 회원가입을 하면 성공적으로 회원가입이 완료된다.
이름이 null인게 마음에 안들어!
회원가입시 이름 입력은 필수로 하기 위해서 Member 엔티티에 들어가서 name 값에 '@NotEmpty'를 붙여주면
'@Valid'가 검증을 해주기 때문에 이름이 null이면 회원 가입 에러!!
이러면 API를 위해서 엔티티가 수정 돼버린상황..
어떤 API는 @NonEmpty가 필요할수도 있지만 다른 API는 필요하지 않을수도 있다
그럼 어떡해??
API 스펙을 위한 별도의 DTO(Data Transfer Object)를 만들어주자!!!
--> API 스펙에 맞춰 별도의 파라미터를 생성해야한다.
이 방식을 사용한게 바로 v2.
DTO를 만들기 위해 @Data
를 붙여주고 API 스펙에 맞는 클래스 생성!!
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
Member member = new Member();
member.setName(request.getName());
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
// DTO 생성
@Data
static class CreateMemberRequest {
private String name;
}
@Data
static class CreateMemberResponse {
public CreateMemberResponse(Long id) {
this.id = id;
}
private Long id;
}
회원 등록과 마찬가지로 version_1(v1) 과 version_2(v2) 두가지 방법을 사용해서 회원 조회를 처리하는 API를 구현할 것이다
v1의 방식으로 하면 안되니까 v2까지 만들었다.
@GetMapping("/api/v1/members")
public List<Member> MemberV1() { return memberService.findMembers(); }
와.. 간단 그 자체..!!
이지만, 회원 등록에서와 같이 회원 조회에서도 엔티티를 직접 쓰면 안된다.
엔티티를 외부에 노출해선 안된다고 표현하는데, 회원 정보를 가져오는데 엔티티를 직접 노출해서 가져오면 문제가 생긴다.
어떤문제??
관련된 모든 정보를 가져오는 문제!! join된 모든 정보를 가져와버린다
이게 왜 문제야??
예를 들어줄게
회원 정보만 조회 할건데 엔티티와 관련된 모든 정보를 가져와
쓸데없이 많은 데이터를 가져오는 상황이 발생하는거지
또 노출되면 안되는 정보까지 가져와버린다(패스워드, 개인정보, 등...)
회원 등록때와 마찬가지로 이걸 막기 위해서 Member 엔티티에서 join된 애한테 @JsonIgnore를 붙여주면 해결 되긴해
근데 이러면 위에서와 마찬가지로 엔티티를 수정하는 것이기 때문에 스펙 자체가 바뀌게된다
이러면 다른 API를 만들때 문제가 된다~~ 이말이지.
또 @JsonIgnore은 화면에 표시하기위한 로직이잖아. 이런 로직은 지양한다.
그럼 어떡해??
API 스펙을 위한 별도의 DTO(Data Transfer Object)를 만들어주자!!!
--> API 스펙에 맞춰 별도의 파라미터를 생성해야한다.
이 방식을 사용한게 바로 v2.
DTO를 만들기 위해 @Data
를 붙여주고 API 스펙에 맞는 클래스 생성!!
@GetMapping("/api/v2/members")
public Result memberV2() {
List<Member> findMembers = memberService.findMembers();
List<MemberDto> collect = findMembers.stream()
.map(m -> new MemberDto(m.getName()))
.collect(Collectors.toList());
return new Result(collect.size(), collect);
//return new Result(collect);
}
@Data
@AllArgsConstructor
static class Result<T> {
private int count;
private T data;
}
@Data
@AllArgsConstructor
static class MemberDto {
private String name;
}
회원 등록과 마찬가지로 DTO를 만들어서 엔티티가 노출되지 않게 한다.
저번 포스트에서 수정의 방법에는 변경 감지
와 병합(merge)
이 있다는 것을 공부했고 우리는 변경 감지
를 사용하기로 했다!!
@PutMapping("/api/v2/members/{id}")
public UpdateMemberResponse updateMemberV2(
@PathVariable("id") Long id, // url에 {id}부분 값을 넘겨주는 역할
@RequestBody @Valid UpdateMemberRequest request) {
// 수정할 떈 변경감지!
// 커맨드와 쿼리를 분리해서 사용
memberService.update(id, request.getName());
Member findMember = memberService.findOne(id);
return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}
@Data
static class UpdateMemberRequest {
private String name;
}
@Data
@AllArgsConstructor
static class UpdateMemberResponse {
private Long id;
private String name;
}
수정도 등록,조회와 마찬가지로 DTO를 생성해서 파라미터에 매핑해야 한다.
한가지 더 짚고 넘어갈 만한 것은 url에 {id}
를 넣고 @PathVariable("id")
을 이용해 id값을 넘겨받는것 정도??
POST
/ PUT
/ PATCH
가 있는데POST
, PATCH
는 부분 수정.PUT
은 전체 수정이다.API를 만들때 엔티티를 파라미터로 받지도 말고 엔티티를 외부에 노출해서도 안된다
API에서 요청/응답 할 때 절대 엔티티를 사용하지 않고 DTO를 생성해서 요청/응답 해야한다.
API를 만들때는 엔티티를 DTO 로 변환하는 작업을 추가하자
➡️ 엔티티가 변경돼도 API 스펙이 변하지 않는다
➡️ 한번 감싸서 반환했기 때문에 유연성이 좋아진다