노션으로 다시 돌아갔습니다 😅

[Spring MVC] 서비스 계층 적용하기 (Prac Project - 3. 서비스 계층 <-> API 계층 연동)

by mignon25
서비스 계층은 API 계층에서 전달받은 클라이언트의 요청 데이터를 기반으로 실질적인 비즈니스 요구사항을 처리하는 계층이다.

 

서비스 계층 적용하기

  • Spring 의 DI를 이용해 API 계층과 비즈니스 계층 연동
  • API 계층에서 전달받은 DTO 객체를 비즈니스 계층의 도메인 엔티티(Entity) 객체로 변환해서 전달

 

DI를 통해 서비스 계층과 API 계층 연동

API 계층에서 구현한 Controller 클래스는 서비스 계층의 Service 클래스와 메서드 호출을 통해 상호작용한다. 

 

Service 란?

Service 계층 - 도메인 업무 영역을 구현하는 비즈니스 로직을 처리하는 계층
  • Service 계층은 대부분 도메인 모델을 포함하고 있다.
  • 도메인 모델은 빈약한 도메인 모델(anemic domain model)과 풍부한 도메인 모델(rich domain model)로 구분
  • 도메인 모델은 DDD(도메인 주도 설계, Domain Driven Design) 와 관련이 깊다.
  • Spring Data JDBC는 이 DDD와 밀접한 관련이 있다. 

 

Service 클래스 작성

현재 MemberController 에 있는 총 다섯 개의 핸들러 메서드가 존재한다.

MemberService 클래스에 이 핸들러 메서드들이 받은 요청을 처리하는 메서드를 작성해준다. 

 

<MemberService와 연동 전 MemberController(V1)>

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;


@RestController
@RequestMapping("/v1/members")
@Validated
public class MemberController {
    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
        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);

        // No need Business logic

        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }

    @GetMapping("/{member-id}")
    public ResponseEntity getMember(@PathVariable("member-id") @Positive long memberId) {
        System.out.println("# memberId: " + memberId);

        // not implementation
        return new ResponseEntity<>(HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity getMembers() {
        System.out.println("# get Members");

        // not implementation

        return new ResponseEntity<>(HttpStatus.OK);
    }

    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(@PathVariable("member-id") @Positive long memberId) {
        System.out.println("# deleted memberId: " + memberId);
        // No need business logic

        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

}

 

<Member 클래스 작성>

도메인 엔티티(Entity) 클래스 - 비즈니스 로직을 처리하기 위해 필요한 데이터를 담는 역할
  • API 계층에서 전달받은 요청 데이터를 기반으로,
  • 서비스 계층에서 비즈니스 로직을 처리하기 위해 필요한 데이터를 전달받고,
  • 비즈니스 로직을 처리한 후에는 결과 값을 다시 API 계층으로 리턴해주는 역할
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {

    private long memberId;
    private String email;
    private String name;
    private String phone;
    
}
  • @NoArgsConstructor : 파라미터가 없는 기본 생성자 자동 생성
  • @AllArgsConstructor : 현재 Member 클래스에 추가된 모든 멤버 변수를 파라미터로 갖는 Member 생성자 자동 생성
  • @Getter, @Setter : lombok 라이브러리에서 제공하는 애너테이션으로, 각 멤버 변수에 대해 getter, setter 자동 생성 

 

< MemberService 기본 구조 작성 (V1) >

import java.util.List;

public class MemberService {

    public Member createMember(Member member) {
        return null;
    }

    public Member updateMember(Member member) {
        return null;
    }

    public Member findMember(long memberId) {
        return null;
    }
    
    public List<Member> findMembers() {
        return null;
    }

    public void deleteMember(long memberId) {
        
    }
}

 

< MemberService 클래스 구현 (V2) >

  • @Service 추가
@Service
public class MemberService {

    public Member createMember(Member member) {
        // TODO should business logic
        
        // TODO member 객체는 나중에 DB에 저장 후, 되돌려 받는 것으로 변경 필요
        Member createdMember = member;
        return createdMember;
    }

    public Member updateMember(Member member) {
        // TODO should business logic
        
        // TODO member 객체는 나중에 DB에 업데이트 후, 되돌려 받는 것으로 변경 필요
        Member updatedMember = member;
        return updatedMember;
    }

    public Member findMember(long memberId) {
        // TODO should business logic

        // TODO member 객체는 나중에 DB 에서 조회하는 것으로 변경 필요
        Member member = new Member(memberId, "aaa@bbb.ccc", "이건이름", "010-1111-1111");
        return member;
    }

    public List<Member> findMembers() {
        // TODO should business logic

        // TODO member 객체는 나중에 DB 에서 조회하는 것으로 변경 필요
        List<Member> members = List.of(
                new Member(1, "aaa@email.com", "ㅁㅁㅁ", "010-1111-1111"),
                new Member(2, "bbb@email.com", "ㄴㄴㄴ", "010-2222-2222")
        );
        return members;
    }

    public void deleteMember(long memberId) {
        // TODO should business logic
    }
}

 

DI 를 적용해 비즈니스 계층과 API 계층 연동

@RestController
@RequestMapping("/v1/members")
@Validated
public class MemberController {

    private final MemberService memberService;

    public MemberController(MemberService memberService) { // 1
        this.memberService = memberService;
    }


    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
        // 2
        Member member = new Member();
        member.setEmail(memberDto.getEmail());
        member.setName(memberDto.getName());
        member.setPhone(memberDto.getPhone());

        // 3
        Member response = memberService.createMember(member);

        return new ResponseEntity<>(response, HttpStatus.CREATED); // 4
    }

    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(@PathVariable("member-id") @Positive long memberId,
                                      @Valid @RequestBody MemberPatchDto memberPatchDto) {

        memberPatchDto.setMemberId(memberId);

        // 5
        Member member = new Member();
        member.setMemberId(memberPatchDto.getMemberId());
        member.setName(memberPatchDto.getName());
        member.setPhone(memberPatchDto.getPhone());

        // 6
        Member response = memberService.updateMember(member);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @GetMapping("/{member-id}")
    public ResponseEntity getMember(@PathVariable("member-id") @Positive long memberId) {
        System.out.println("# memberId: " + memberId);

        // 7
        Member response = memberService.findMember(memberId);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @GetMapping
    public ResponseEntity getMembers() {
        System.out.println("# get Members");

        // 8
        List<Member> response = memberService.findMembers();
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(@PathVariable("member-id") @Positive long memberId) {
        System.out.println("# deleted memberId: " + memberId);

        // 9
        memberService.deleteMember(memberId);

        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
    
}

 

 

현재 상황에서의 문제점

1. Controller 핸들러 메서드의 역할 문제

  • 핸들러 메서드의 역할은 클라이언트로부터 전달받은 요청 데이터를 Service 클래스로 전달하고, 응답데이터를 클라이언트로 다시 전송해주는 단순한 역할만을 하는 것이 좋다. 
  • 그러나 현재 MemberController 에서 핸들러 메서드가 DTO 클래스를 엔티티(Entity) 객체로 변환하는 작업까지 수행하고 있다.

2. Service 계층에서 사용되는 엔티티(Entity) 객체를 클라이언트의 응답으로 전송하고 있다.

  • DTO 클래스는 API 계층에서만 데이터를 처리해야 한다.
  • 엔티티(Entity) 클래스는 서비스 계층에서만 데이터를 처리해야 한다. 
  • 그러나 현재는 Entity 클래스의 객체를 클라이언트의 응답으로 전송하고 있어 계층 간의 역할 분리가 이루어지지 않았다. 

 

 

 

Additional Keywords

 

 

블로그의 정보

Mignon'S Dev Log

mignon25

활동하기