주말이 지나고 월요일이 왔다.















주말동안 정규식 복습과 블로그 개설 준비를했다.
오늘 부터는 GitHub Page로 만든 블로그로 포스팅을 할려고한다.
하려고하려고 했는데, 드디어 개설할 수 있어서
포스팅하는 맛이 날 것 같다 ㅎㅎ


Service

저번주에 API 계층에 대해서 공부 했었다.
Controller의 핸들러 메서드를 통해서
클라이언트의 요청을 어떻게 전달 받을 수 있는지?까지 파악해보았고

이번 시간부터는 Service 계층을 공부해볼 시간이다.

  1. DI를 사용해 API 계층과 Service 계층을 연동하기
  2. API 계층의 DTO클래스와 Service 계층의 Entity 클래스를 매핑하기

Service 클래스는 애플리케이션에 있어
도메인 업무 영역을 구현하는 비지니스 로직과 관련이 있다.


lombok

시작에 앞서 lombok에서 지원하는 어노테이션
5가지정도 간단하게 알아보자

1). @NoArgsConstructor : 파라미터가 없는 기본 생성자로 만들어줌

2). @RequiredArgsConstructor : final, @NonNull로 붙은 멤버 변수를 파라미터로 가지는 생성자를 생성해줌
-> 생성자를 만들어주기때문에 생성자가 하나일경우 자동으로 @Autowired가 된다.

3). @AllArgsConstructor :해당 애노테이션이 붙은 클레스에 추가된 모든 멤버 변수를 파라미터로 가지는 생성자를 생성해줌

4). @Getter : 멤버 변수의 Getter 메서드를 만들어줌

5).@Setter : 멤버 변수의 Setter 메서드를 만들어줌

말로 들으면 어려우니 코드로 확인해보자

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    private long memberId;
    private String email;
    private String name;
}

위의 코드가 lombok 어노테이션을 적용한 코드이다.

public class Member {
    private long memberId;
    private String email;
    private String name;

    public long getMemberId() {
        return this.memberId;
    }

    public String getEmail() {
        return this.email;
    }

    public String getName() {
        return this.name;
    }
    
    public void setMemberId(final long memberId) {
        this.memberId = memberId;
    }

    public void setEmail(final String email) {
        this.email = email;
    }

    public void setName(final String name) {
        this.name = name;
    }
    //NoArgsConstructor 애노테이션으로 만들어진 생성자
    public Member() {
    }
    //AllArgsConstructor 애노테이션으로 만들어진 생성자
    public Member(final long memberId, final String email, final String name, final String phone) {
        this.memberId = memberId;
        this.email = email;
        this.name = name;
        this.phone = phone;
    }
}

해당 lombok 어노테이션을 build하면 위와 같이 코드가
자동으로 생성되서 빌드되게 되어진다.

말그대로 우리가 코드를 직접 다 입력하지않아도
lomobk의 기능을 이용하면 어노테이션 하나로
어떤기능이 있는지 쉽게 유추가 가능하고 코드도 간결해진다.

Service 계층 - API 계층 / DTO - Entity 맵핑

저번주에 공부했던 API 계층의 데이터를 받기위해
핸들러 메서드를 통해 JSON 객체 값을 DTO로 받아오는 것 까지 공부를 했었다.
이제 이렇게 받아온 데이터를 Service 계층과 어떻게
연동 시키고 코드를 작성해야 유연한지 공부해보자.

우선 기본로직을 작성해보자.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Coffee {
    private long coffeeId;
    private String korName;
    private String engName;
    private int price;
}

Entity 클래스인 Coffee 클래스이다.
lombok을 이용해 Getter,Setter 메서드를 사용할 수 있게 해주고
생성자는 매개변수가 없는 생성자 1개 (NoArgsConstructor)
매개변수가 4개인 생성자 1개 (AllArgsConstructor)
해서 총 2개의 생성자가 만들어졌다.

@Getter
@Setter
public class CoffeePostDto {
    private String korName;
    private String engName;
    private int price;
}

그리고 API 계층에서 보내줄 데이터
3개를 멤버변수로 만들었다.


역할과 기능을 나누기위해서
Controller에는 API 데이터를 가져오고 반환해주는 역할만 두고
Service 클래스를 만들어 비지니스로직을 만들고
Mapper를 이용해 Dto와 Entity클래스를 매핑하여 연동해보자

@Service
public class CoffeeService {
    public Coffee createCoffee(Coffee coffee){
        
        // coffee 객체는 추후 DB연결후 리턴받는 걸로 변경 필요.
        Coffee toss = coffee;

        return toss;
    }
}

우선 Service 클래스를 Bean으로 등록해놓고
의존성 주입을 통행 createCoffee(); 메서드를
다른 클래스에서 사용할 수 있게 된다.

@RestController
@RequestMapping("/api/coffees")
public class CoffeeController {
   private final CoffeeService coffeeService;
   private final CoffeeMapper coffeeMapper;
   
   @Autowired // 의존성 주입, 생성자가 1개라 생략가능함
   public CoffeeController(CoffeeService coffeeService, CoffeeMapper coffeeMapper) {
      this.coffeeService = coffeeService;
      this.coffeeMapper = coffeeMapper;
   }

   @PostMapping
   public ResponseEntity postCoffee(@Valid @RequestBody CoffeePostDto coffeePostDto) {

      Coffee coffee = coffeeMapper.coffeePostDtoToCoffee(coffeePostDto); // DTO <-> Entity 변환
      Coffee response = coffeeService.createCoffee(coffee); // Coffee 생성

      return new ResponseEntity<>(coffeeMapper.coffeeToCoffeeResponseDto(response), HttpStatus.CREATED);
   }
}

Controller 클래스에서는 CoffeeService,CoffeeMapper 를 의존성 주입해놓고
Mapper 클래스인 coffeeMapper를 만들어 coffeePostDto를 <-> entity의 맵핑을 시켜주는
방법으로 프로그램을 작성했다. 최종적으로 맵핑시켜 받은 객체를
Service로직의 createCoffee로 객체를 만들어 반환받은 Coffee 클래스 참조 변수를
coffeeToCoffeeResponseDto라는 맵퍼를 이용해 Json 객체를 통해 반환해주는 모습이다.


이제 @Mapper라는 어노테이션을 이용해
추상화 시켜 Mapper 클래스를 작성해보자.

implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'

우선 gradle dependencies에 위의 코드를 추가해줘야 Mapper를 사용가능하다.

@Mapper(componentModel = "spring")
public interface CoffeeMapper {
    Coffee coffeePostDtoToCoffee(CoffeePostDto coffeePostDto);
    CoffeeResponseDto coffeeToCoffeeResponseDto(Coffee coffee);
}

이렇게 인터페이스를 통해 메서드를 추상화 시켜놓으면
Mapper 클래스가 자동으로 빌드과정에서 구현체를 만들어준다.
해당 구현체의 내용은 첫줄부터 보자면

Coffee coffeePostDtoToCoffee(CoffeePostDto coffeePostDto);
coffeePostDto로 전달된 객체를 Coffee 타입의 변수로 값을 넣어준다.
넣을때는 생성자와 Setter로 넣어준다. 말그대로 @Mapper하나를
달아놓음으로서 자동으로 코드가 주입되는 것이다.
실제로 빌드해놓은 코드를 보면 아래와 같이 나온다.

<@Mapper를 통해 자동으로 Build된 코드>

public Coffee coffeePostDtoToCoffee(CoffeePostDto coffeePostDtoo) {
  if (coffeePostDtoo == null) {
      return null;
  } else {
      Coffee coffee = new Coffee();
      coffee.setKorName(coffeePostDtoo.getKorName());
      coffee.setEngName(coffeePostDtoo.getEngName());
      coffee.setPrice(coffeePostDtoo.getPrice());
      return coffee;
}

다른 부분들은 생략하고 coffeePostDto 부분만 보면
위처럼 coffeePostDto를 매개변수로 전달해
참조변수에 값이 있으면, Coffee 객체를 생성자를 통해 새로만들어
setter로 필드변수에 값을 넣어주는 모습이다.

실제로 Coffee 클래스에는 @NoArgsConstructor가 있기 때문에
매개변수가 없는 생성자를 통해 객체가 만들어지고
@Sette가 있기에 필드변수에 값을 넣어 줄 수 있게되어진다.
실제로 @Setter를 지우게되면 다른 방법으로 값을 넣을 방법을 찾는다.

<Coffee 클래스의 @Setter를 지운고 @Mapper를 통해 자동으로 Build한 코드 >

public Coffee coffeePostDtoToCoffee(CoffeePostDto coffeePostDtoo) {
  if (coffeePostDtoo == null) {
      return null;
  } else {
      Coffee coffee = new Coffee();
      return coffee;
  }
}

@Setter를 지웠을 경우에 위처럼 빌드과정에서 Mapper를 통해
값을 전달해주는 코드가 달라지게된다. 이렇게 되면
Setter가 없기때문에 객체만 만들어져서 전달될 뿐 Dto로 전달된 값이
새로운 객체로 저장되어지지 않으니 주의해야한다.


마지막으로 응답을할 때 전달해준 Dto를 만들었다.

@Getter
@Setter
@AllArgsConstructor
public class CoffeeResponseDto {
    private long coffeeId;
    private String korName;
    private String engName;
    private int price;
}

해당 클래스도 생성자와, Getter,Setter를 만들어주고
Controller에서 return하여 값을 응답할 때
Coffee객체를 CoffeeMapper로 맵핑하여 다시 리턴하게될 수 있게
만들어준 클래스이다.


이렇게 오늘 프로그램해본 내용을 정리해보자면

  1. @Mapper 기능을 통해 핸들러메서드로 받은 값 DTO를 -> Entity 객체생성 및 값을 맵핑함
    ex) coffeePostDto -> Coffee 필드변수의 값을 저장.
  2. coffeeMapper 클래스를 통해 반환 받은 객체를 저장
  3. 2번에서 반환받은 Coffee객체를 Service 로직의 활용
  4. Service 로직에서 반환 받은 객체를 coffeeMapper로 전달
  5. 전달받은 coffeeMapper에서 응답에 해당하는 객체를 ResponEntity 객체와 함께 반환
    하여 Json 객체로 클라이언트에 전달

오늘은 이렇게 @Mapper 기능을 이용해
자동으로 맵핑해주고 사용한는 방법을 배워 보았고
아직까지 어색하긴하지만 대략적으로 어떻게
전달되고 묶여지는지 감이 잡히기 시작하고 있다.

아직 DataBase가 연결이안되 이해가 안가는 부분이 사실 있지만
차근차근 Step by Step으로 알아가보자

오늘공부는 여기서 끝!!


오늘의 커피량: ☕️ ☕️ ☕️ ☕️
오늘의 점심: 간짜장, 공기밥