인프런 수업 강의를 듣고 정리한 내용입니다.
📣 수업을 시작하기 전에
API 개발과 성능 최적화✔️ REST API 개발
- 등록, 수정, 조회 REST API 개발
- API 개발 실무 노하우
무엇보다 성능 최적화가 중요하다!
좋지 않은 성능을 써보다가 → 좋은 성능으로 변경한다.✔️ 학습 방법
스프링와 JPA 공부한 상태여야 한다.
프로젝트에서 디렉터리로 공통처리를 많이 한다.
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/**
* 등록 V1: 요청 값으로 Member 엔티티를 직접 받는다.
*/
@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;
}
@Data
static class CreateMemberResponse{
private Long id;
public CreateMemberResponse(Long id) {
this.id = id;
}
}
}
@RestController
:@ResponseBody
+@Controller
상속
@ResponseBody
: 데이터 자체를 json이나 xml 보낼 때 주로 사용하는 애노테이션
✔️ 문제점
현재 Member 테이블에 아무런 입력 값 없이, PostMapping을 하여도 실행이 된다.
이때,@NotEmpty
를 추가한 후, 아무 입력 값이 전송할 경우 오류가 발생한다. (400번 오류)
다만,
@NotEmpty
등등)
✔️ 결론
/**
* 등록 V2: 요청 값으로 Member 엔티티 대신에 별도의 DTO를 받는다.
*/
@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);
}
@Data
static class CreateMemberRequest {
private String name;
}
@Data
static class CreateMemberResponse{
private Long id;
public CreateMemberResponse(Long id) {
this.id = id;
}
}
CreateMemberResquest
를 Member
엔티티 대신에 RequestBody
와 매핑한다.
📣 api 과정에서
(1) 필드 값이 변경되었을 때
- V1번은 변경되었는지 알 수 없다.
- V2번은 오류로 알 수 있다. (서비스를 안정적으로 유지할 수 있다.)
(2) 필드 값을 알고 싶을 때
- V1은 어떤 것이 필드 데이터 입력되었는지 알 수 없다.
- V2는 어떤 것이 필드 데이터로 입력되었는지 알 수 있다.
실무에서는
@RequestBody @Valid Member member
보다는@RequestBody @Valid CreateMemberRequest request
와 같이 많이 사용한다!
💡 참고
실무에서는 엔티티를 API 스펙에 노출하면 안된다!
DTO로 가는 객체들을 사용해서 등록이나 응답을 받는다!
✔️ DTO란?
DTO(Data Transfer Object)
: 계층 간 데이터 교환을 하기 위해 사용하는 객체로, DTO는 로직을 가지지 않는 순수한 데이터 객체이다.(getter
& setter
만 가진 클래스)
✔️ DAO
DAO(Data Access Object)
: 데이터베이스의 data에 접근하기 위한 객체이다. DataBase에 접근하기 위한 로직 & 비즈니스 로직을 분리하기 위해 사용한다.
✔️ VO
VO(Value Object)
값 오브젝트로써 값을 위해 쓰인다. read-Only 특징(사용하는 도중에 변경 불가능하며 오직 읽기만 가능)을 가진다.DTO
와 유사하지만, DTO
는 setter
를 가지고 있어 값이 변경할 수 있다.VO
읽기만 가능하여, setter
를 가지고 있지 않아 값을 변경할 수 없다.
MemberApiController
/**
* 수정 API
*/
@PatchMapping("/api/v2/members/{id}")
public UpdateMemberResponse updateMemberV2(@PathVariable("id") Long 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
에서는 Lombok 애노테이션 많이 사용한다. memberService.update(id, request.getName()); // 트랜잭션 종료된다.
Member findMember = memberService.findOne(id); // query를 통해 가져와서, 반환한다.
command
와 query
를 소스상에서 분리하는 것이 좋다.
MemberService
public class MemberService {
private final MemberRepository memberRepository;
/*
* 회원 수정
*/
@Transactional
public void update(Long id, String name) {
Member member = memberRepository.findOne(id);
member.setName(name);
}
실행 결과
hello → new
💡 참고
PUT
: 전체 업데이트할 때 사용한다.
PATCH
: 부분 업데이트할 때 사용한다.
💡 참고
org.springframework.http.converter.HttpMessageNotReadableException
와 같은 예외가 발생했을 경우, HTTP 요청의 body 형식을 Text로 보냈는지 확인한다.
body 형식을 JSON으로 변경하여 해결할 수 있다.
참고
none
으로 바꿀시table drop
이 일어나지 않는다.- 한 번 데이터 넣을 시, 재실행해도 계속 쓸 수 있다.
public class MemberApiController{
private final MemberService memberService;
/**
* 조회 V1 : 응답 값으로 엔티티를 직접 외부에 노출한다.
*/
@GetMapping("/api/v1/members")
public List<Member> membersV1() {
return memberService.findMembers();
}
}
실행 결과
✔️ 문제점
@JsonIgnore
, 별도의 뷰 로직 등등)외부에 노출되는 것을 막는다.
api가 회원 데이터만 출력하고 싶을 때, Order를 제외한다.
(다만, 다른 api에서는 Order를 사용할 수도 있어 @JsonIgnore
을 붙여주는 것은 좋지 않다.)
모든 상황에 대응할 수 없다.
Result
클래스 생성으로 해결)
✔️ 결론
💡 참고
엔티티를 외부에 노출하지 말자!
실무에서는member
엔티티의 데이터가 필요한 API가 계속 증가하게 된다.
어떤 API는name
필드가 필요하지만, 어떤 API는name
필드가 필요없을 수 있다. 결론적으로 엔티티 대신에 API 스펙에 맞는 별도의 DTO를 노출해야 한다.
/**
* 조회 V2: 응답 값으로 엔티티가 아닌 별도의 DTO를 반환한다.
*/
@GetMapping("/api/v2/members")
public Result membersV2() {
List<Member> findMembers = memberService.findMembers();
//엔티티 -> DTO 변환
List<MemberDto> collect = findMembers.stream()
.map(m -> new MemberDto(m.getName()))
.collect(Collectors.toList());
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
로 변환해서 반환한다.Result 클래스
로 컬렉션을 감싸서 향후 필요한 필드를 추가할 수 있다.Result에 count 필드 추가
💡 참고
api
만들 때는 엔티티를 노출하거나 받지 말라!api
에 맞는Dto
를 만들어 활용하라!