DI를 통한 서비스 계층 ↔ API 계층 연동과정을 정리해보려 한다.
순서
-Entity클래스인 member 클래스 생성
-memberController 코드구현
-MemberService에 @Service 에너테이션 추가 (Spring Bean으로 만들어주기 위해)
-위 코드의 문제점
먼저 Entity클래스인 member 클래스를 생성해준다.
도메인 엔티티(Entity) 클래스란 서비스 계층에서 데이터 액세스 계층과 연동하면서 비즈니스 로직을 처리하기 위해 필요한 데이터를 담는 역할을 하는 클래스
DTO와 비슷한 역할을 하지만
DTO는 클라이언트와 API 계층 사이에서 데이터를 담아 전달하고,
도메인 엔티티 클래스는 API계층과 서비스 계층 사이에서 데이터를 전달한다.
📝member 클래스 생성
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/*DTO의 역할
클라이언트 <---DTO객체(데이터)---> API 계층
Entitiy class(여기서는 Member class)의 역할
API 계층 <---Entitiy class(데이터)---> 서비스 계층(비즈니스 로직 처리)
*/
@Getter
@Setter
@NoArgsConstructor //현재 Member 클래스에 추가된 모든 멤버 변수를 파라미터로 갖는 Member 생성자를 자동으로 생성
@AllArgsConstructor//파라미터가 없는 기본 생성자를 자동으로 생성
public class Member {
private long memberId;
private String email;
private String name;
private String phone;
}
📝memberController 코드구현
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Positive;
import java.util.List;
@RestController
@RequestMapping("/v1/members")
@Validated
public class MemberController {
//final을 하면 꼭 생성자를 만들어줘야함
//final을 사용한 이유. memberRepository의 값이 필수로 들어와야만 오류가 안남.
private final MemberService memberService; //멤버서비스를 사용하기 위해 선언
//DI적용 X
// public MemberController(){ //멤버 컨트롤러 생성자 안에 멤버서비스 객체를 생성해서 memberSevice에 참조변수로 넘겨주었다.
// this.memberService = new MemberService(); /*1*/
//DI 적용
// Spring이 애플리케이션 로드시, ApplicationContext에 있는 MemberService 객체를 주입 해줌.
//주입을 받는 클래스와 주입 대상 클래스 모두 Spring Bean이어야 한다.
//@RestController 애너테이션이 추가되어있으므로 Spring Bean이다.
//MemberService 클래스에 @Service 애너테이션을 추가(Spring Bean)
public MemberController(MemberService memberService){
this.memberService = memberService;
}
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
// (2)
Member member = new Member(); //멤버타입 새 객체 생성
member.setEmail(memberDto.getEmail()); //Dto에서 이메일 꺼내와 member에 넣어줌
member.setName(memberDto.getName());
member.setPhone(memberDto.getPhone());
// (3)
Member response = memberService.createMember(member);
return new ResponseEntity<>(memberDto, HttpStatus.CREATED);
}
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(@PathVariable("member-id") @Positive long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// (4)
Member member = new Member(); //멤버객체 생성
member.setMemberId(memberPatchDto.getMemberId()); //DTO에서 ID꺼내와 Member클라스 정보 수정
member.setName(memberPatchDto.getName());
member.setPhone(memberPatchDto.getPhone());
// (5)
Member response = memberService.updateMember(member); //member타입의 response객체 생성, 업뎃합
return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
}
@GetMapping("/{member-id}")
public ResponseEntity getMember(@PathVariable("member-id") @Positive long memberId) {
System.out.println("# memberId: " + memberId);
// (6)
Member response = memberService.findMember(memberId);
//멤버타입의 응답 객체로 멤버서비스의 findMember메서드 불러와 리턴
return new ResponseEntity<>(HttpStatus.OK);
}
@GetMapping
public ResponseEntity getMembers() {
System.out.println("# get Members");
// (7)
List<Member> response = memberService.findMembers();
return new ResponseEntity<>(HttpStatus.OK);
}
@DeleteMapping("/{member-id}")
public ResponseEntity deleteMember(@PathVariable("member-id") @Positive long memberId) {
System.out.println("# deleted memberId: " + memberId);
// (8)
memberService.deleteMember(memberId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
📝MemberService에 @Service 에너테이션 추가 (Spring Bean으로 만들어주기 위해)
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MemberService {
public Member createMember(Member member) {
//TODO 비즈니스 로직 작성
//TODO member객체는 나중에 DB에 저장 후, 되돌려 받는 것으로 변경 필요
//파라미터로 전달 받은 Member 객체를 그대로 리턴
Member createdMember = member;
return createdMember;
}
public Member updateMember(Member member) {
// TODO should business logic
// member 객체는 나중에 DB에 업데이트 후, 되돌려 받는 것으로 변경 필요.
//파라미터로 전달 받은 Member 객체를 그대로 리턴
Member updatedMember = member;
return updatedMember;
}
public Member findMember(long memberId) {
// TODO should business logic
// TODO member 객체는 나중에 DB에서 조회 하는 것으로 변경 필요.
//Stub 데이터를 넘겨주도록 했다
Member member =
new Member(memberId, "hgd@gmail.com", "홍길동", "010-1234-5678");
return member;
}
public List<Member> findMembers() {
// TODO should business logic
// TODO member 객체는 나중에 DB에서 조회하는 것으로 변경 필요.
//Stub 데이터를 넘겨주도록 했다
List<Member> members = List.of(
new Member(1, "hgd@gmail.com", "홍길동", "010-1234-5678"),
new Member(2, "lml@gmail.com", "이몽룡", "010-1111-2222")
);
return members;
}
public void deleteMember(long memberId) {
// TODO should business logic
}
}
📌위 코드의 문제점
✔Controller 핸들러 메서드의 책임과 역할
핸들러 메서드의 역할은 클라이언트로부터 전달 받은 요청 데이터를 Service 클래스로 전달하고, 응답 데이터를 클라이언트로 다시 전송해주는 단순한 역할만을 하는 것이 좋다.
그런데, 현재의 MemberController에서는 핸들러 메서드가 DTO 클래스를 엔티티(Entity) 객체로 변환하는 작업까지 도맡아서 하고 있다.
✔Service 계층에서 사용되는 엔티티(Entity) 객체를 클라이언트의 응답으로 전송하고 있음
DTO 클래스는 API 계층에서만
엔티티(Entity) 클래스는 서비스 계층에서만
데이터를 처리하는 역할을 해야 하는데
엔티티(Entity) 클래스의 객체를 클라이언트의 응답으로 전송함으로써 계층 간의 역할 분리가 이루어지지 않았다.
결국 DTO 클래스와 엔티티(Entity) 클래스를 서로 변환해주는 누군가 즉, 매퍼(Mapper)가 필요
매퍼는 다음 글에 정리해보겠다!
'Spring' 카테고리의 다른 글
Unparseable date /date <-> string 변환하기 (0) | 2023.01.11 |
---|---|
Spring - 엔티티 간의 연간관계 매핑(일대다,다대일,다대다,일대일) (0) | 2022.11.05 |
Spring - JDBC기반 Domain Entity와 테이블 설계 (0) | 2022.11.02 |
Spring - 유효성 검증 (0) | 2022.10.30 |
Spring - Mapper 클래스의 구현, MapStruct (0) | 2022.10.30 |