- API와 일반 화면은 공통 처리하는 부분이 많이 다름
- 일반 화면은 에러 발생시 공통 에러 HTML이 나와야 함
- 반면에 API는 공통 에러용 JSON API 스펙이 나와야 함
- 따라서 API 패키지를 따로 분리하여 작성
@RestController@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* @since 4.0.1
*/
@AliasFor(annotation = Controller.class)
String value() default "";
}
@ResponseBody : 데이터 자체를 바로 json이나 xml로 보낼때 사용하는 annotationMember 엔티티를 직접 받음package jpabook.jpashop.api;
import jakarta.validation.Valid;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.service.MemberService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class MemberApiController {
private final MemberService memberService;
/**
* 등록 V1
* @param member
* @return
*/
@PostMapping("/api/v1/members")
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
@Data
static class CreateMemberResponse {
private Long id;
public CreateMemberResponse(Long id){
this.id = id;
}
}
}
Post 수행 시, 회원 id 생성
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@NotEmpty
private String name;
@Embedded
private Address address;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
Member entity의 name에 @NotEmpty를 설정하면 비어있는 값을 전달할 경우 아래와 같이 validation이 자동으로 수행됨
@NotEmpty 등등)Member 엔티티 대신에 별도의 DTO를 받음public class MemberApiController {
private final MemberService memberService;
@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;
}
}
}
CreateMemberRequest를 Member 엔티티 대신에 RequestBody와 매핑함
엔티티와 프레젠테이션 계층을 위한 로직을 분리할 수 있음
엔티티와 API 스펙을 명확하게 분리할 수 있음
엔티티가 변해도 API 스펙은 변하지 않음
만약 name에서 userName으로 바뀌어도 컴파일 시점에서 오류가 발생하여 확인이 가능함
실무에서는 엔티티를 API 스펙에 노출하면 안됨
API 스펙 문서를 까보지 않아도 DTO를 받으면 API 스펙이 어떻게 되어있는지 알 수 있음
@NotEmpty를 걸어주면 검증 유무 확인도 용이함테스트 완료
