일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 자료구조
- Spring
- REST HTTP API
- 코드스테이츠
- O(log n)
- char to int
- Java
- root passwd
- 스키마 디자인
- Spring MVC
- 스키마 설계
- RestControllerAdvice
- AOP
- 리눅스 사용권한
- 배열 탐색
- git workflow
- 함수형 인터페이스
- set-version
- ubuntu 패스워드
- mapstruct
- file i/o
- custom exception
- JAVA 재귀함수
- git 설정
- ubuntu
- Spring 예외처리
- 탐욕 알고리즘
- http 응답코드
- N:N
- ubuntu passwd
Archives
- Today
- Total
개발소설
DI를 통한 서비스 계층 ↔ API 계층 연동, 매퍼(Mapper), 엔티티(Entity) 본문
서비스(Service) 계층
- 애플리케이션에 있어 Service는 도메인 업무 영역을 구현하는 비즈니스 로직을 처리하는 것을 의미한다.
- Controller 클래스에 @RestController 애너테이션을 추가하면 Spring Bean으로 등록된다.
- Service 클래스에 @Service 애너테이션을 추가하면 Spring Bean으로 등록된다.
- 생성자 방식의 DI는 생성자가 하나일 경우에는 @Autowired 애너테이션을 추가하지 않아도 DI가 적용된다.
- 서비스 계층에서 데이터 액세스 계층과 연동하면서 비즈니스 로직을 처리하기 위해 필요한 데이터를 담는 역할을 하는 클래스를 도메인 엔티티(Entity) 클래스
Mapper
- DTO ↔ Entity 클래스를 서로 변환해주는 변환자
- Mapper를 사용해서 DTO 클래스와 Entity 클래스 간의 관심사를 분리할 수 있다.
- Mapper를 개발자가 직접 구현하기보다는 MapStruct 같은 매핑 라이브러리를 사용하는 것이 생산성 측면에서 더 나은 선택이다.
- MapStruct는 DTO 클래스처럼 Java Bean 규약을 지키는 객체들 간의 변환 기능을 제공하는 매퍼(Mapper) 구현 클래스를 자동으로 생성해 주는 코드 자동 생성기
- 매핑 우선 순위 조건 Builder > Constructor > Setter
DTO 클래스와 Entity 클래스의 역할 분리가 필요한 이유
- 계층별 관심사의 분리
- DTO : client의 요청 데이터를 하나의 객체로 받기 위해 (API 계층)
- Entity : 비즈니스 로직을 처리하는데 사용하는 클래스 이후 데이터 액세스 계층에서 DB와 관련됨 (서비스 계층)
- 코드 구성의 단순화
- DTO에서는 유효성검사 애너테이션, Entity에서는 JPA 애너테이션을 사용하는데 두개가 섞이면 코드가 복잡해진다.
- REST API 스펙의 독립성 확보
- 필요한 데이터만 Response Body로 전달하기 용이
- 비밀번호같은 민감 데이터 노출 방지
- ResponseDto에는 비밀번호등의 필드를 뺀다.
- 필요한 데이터만 Response Body로 전달하기 용이
Service 클래스 만들기 (서비스 계층)
import com.codestates.coffee.entity.Coffee;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CoffeeService {
public Coffee createCoffee(Coffee coffee){
// DB에서 생성(create)하는 코드 필요 (데이터 액세스)
Coffee createCoffee = coffee;
return createCoffee;
}
public Coffee updateCoffee(Coffee coffee){
// DB에서 수정(update)하는 코드 필요
Coffee updateCoffee = coffee;
return updateCoffee;
}
public Coffee findCoffee(long coffeeId){
// DB에서 조회(select)하는 코드 필요
Coffee coffee = new Coffee(coffeeId, "아메리카노", "Americano", 2500);
return coffee;
}
public List<Coffee> findCoffees(){
// DB에서 전체 조회(select)하는 코드 필요
List<Coffee> coffees = List.of(
new Coffee(1L, "아메리카노", "Americano", 2500),
new Coffee(2L, "카라멜 라떼", "Caramel Latte", 5000 )
);
return coffees;
}
public void deleteCoffee(long coffeeId){
// DB에서 삭제 (delete)하는 코드 필요
}
}
- 데이터 액세스 계층으로의 연동은 아직 미구현 (stub 데이터 사용)
- @Service : SpringBean으로 생성하기 위한 애너테이션
- 생성자가 하나일 경우 자동으로 DI 적용
- 생성자가 하나 이상일 경우, DI를 적용하기 위한 생성자에 반드시 @Autowired을 사용
- Controller 클래스의 핸들러 메서드와 1대1로 매치하여 생성
Entity 클래스 만들기 - lombok 이용
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Coffee {
private long coffeeId;
private String korName;
private String engName;
private int price;
}
lombok이라는 라이브러리에서 제공하는 애너테이션으로 엔티티클래스 생성
- @Getter, @Setter : getter/setter 메서드를 자동으로 생성
- @AllArgsConstructor : 현재 클래스에 추가된 모든 멤버 변수를 파라미터로 갖는 생성자를 자동으로 생성
- @NoArgsConstructor는 파라미터가 없는 기본 생성자를 자동으로 생성
mapper 생성 - MapStruct 사용
//build.gradle
dependencies {
...
...
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
}
- build.gradle에 MapStruct 의존 라이브러리 설정
// mapper 인터페이스
package com.codestates.coffee.mapper;
import com.codestates.coffee.dto.CoffeePatchDto;
import com.codestates.coffee.dto.CoffeePostDto;
import com.codestates.coffee.dto.CoffeeResponseDto;
import com.codestates.coffee.entity.Coffee;
import org.mapstruct.Mapper;
import java.util.List;
@Mapper(componentModel = "spring")
public interface CoffeeMapper {
Coffee coffeePostDtoToCoffee(CoffeePostDto coffeePostDto);
Coffee coffeePatchDtoToCoffee(CoffeePatchDto coffeePatchDto);
CoffeeResponseDto coffeeToCoffeeResponseDto(Coffee coffee);
List<CoffeeResponseDto> coffeesToCoffeeResponseDtos(List<Coffee> coffees);
}
- MapStruct 기반의 매퍼(Mapper) 인터페이스 정의
- @Mapper 애너테이션을 추가함으로써 해당 인터페이스는 MapStruct의 매퍼 인터페이스로 정의
- @Mapper 애너테이션의 애트리뷰트로 componentModel = "spring"을 지정해 주면 Spring의 Bean으로 등록
- 메서드 작성
- Dto를 Entitiy로 변경하는 메서드 작성 (coffeePostDtoToCoffee)
- Entitiy를 Dto로 변경하는 메서드 작성 (coffeeToCoffeeResponseDto)
- 메서드 시그니처를 현재데이터to변경할데이터로 명확히
- List등의 자료구조도 사용 가능 (다중 데이터일 경우)
- List<CoffeeResponseDto> coffeesToCoffeeResponseDtos(List<Coffee> coffees);
- 사용하지 않으면 Controller에서 다중 처리로직을 Stream등으로 해야함 (밑에있는 Controller 코드내 getCoffes확인)
// 자동생성 mapper 구현클래스
import com.codestates.coffee.dto.CoffeePatchDto;
import com.codestates.coffee.dto.CoffeePostDto;
import com.codestates.coffee.dto.CoffeeResponseDto;
import com.codestates.coffee.entity.Coffee;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.stereotype.Component;
@Component
public class CoffeeMapperImpl implements CoffeeMapper {
public CoffeeMapperImpl() {
}
public Coffee coffeePostDtoToCoffee(CoffeePostDto coffeePostDto) {
if (coffeePostDto == null) {
return null;
} else {
Coffee coffee = new Coffee();
coffee.setKorName(coffeePostDto.getKorName());
coffee.setEngName(coffeePostDto.getEngName());
coffee.setPrice(coffeePostDto.getPrice());
return coffee;
}
}
public Coffee coffeePatchDtoToCoffee(CoffeePatchDto coffeePatchDto) {
if (coffeePatchDto == null) {
return null;
} else {
Coffee coffee = new Coffee();
coffee.setCoffeeId(coffeePatchDto.getCoffeeId());
coffee.setKorName(coffeePatchDto.getKorName());
coffee.setEngName(coffeePatchDto.getEngName());
if (coffeePatchDto.getPrice() != null) {
coffee.setPrice(coffeePatchDto.getPrice());
}
return coffee;
}
}
public CoffeeResponseDto coffeeToCoffeeResponseDto(Coffee coffee) {
if (coffee == null) {
return null;
} else {
long coffeeId = 0L;
String korName = null;
String engName = null;
Integer price = null;
coffeeId = coffee.getCoffeeId();
korName = coffee.getKorName();
engName = coffee.getEngName();
price = coffee.getPrice();
CoffeeResponseDto coffeeResponseDto = new CoffeeResponseDto(coffeeId, korName, engName, price);
return coffeeResponseDto;
}
}
public List<CoffeeResponseDto> coffeesToCoffeeResponseDtos(List<Coffee> coffees) {
if (coffees == null) {
return null;
} else {
List<CoffeeResponseDto> list = new ArrayList(coffees.size());
Iterator var3 = coffees.iterator();
while(var3.hasNext()) {
Coffee coffee = (Coffee)var3.next();
list.add(this.coffeeToCoffeeResponseDto(coffee));
}
return list;
}
}
}
- 자동 생성된 Mapper 인터페이스 구현 클래스 확인 (CoffeeMapperImpl)
- Gradle의 build task를 실행하면 자동으로 생성 -> 버전에 따라 그냥 build인 경우도 있다.
Controller의 핸들러 메서드에서 서비스 계층 연동 및 mapper 적용
import com.codestates.coffee.dto.CoffeeResponseDto;
import com.codestates.coffee.entity.Coffee;
import com.codestates.coffee.mapper.CoffeeMapper;
import com.codestates.coffee.dto.CoffeePatchDto;
import com.codestates.coffee.dto.CoffeePostDto;
import com.codestates.coffee.service.CoffeeService;
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;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/v5/coffees")
@Validated
public class CoffeeController {
//(1)
private final CoffeeService coffeeService;
private final CoffeeMapper mapper;
//(2)
public CoffeeController(CoffeeService coffeeService, CoffeeMapper mapper) {
this.coffeeService = coffeeService;
this.mapper = mapper;
}
@PostMapping
public ResponseEntity postCoffee(@Valid @RequestBody CoffeePostDto coffeePostDto) {
Coffee coffee = mapper.coffeePostDtoToCoffee(coffeePostDto); //(3)
Coffee response = coffeeService.createCoffee(coffee); //(4)
//(5)
return new ResponseEntity<>(mapper.coffeeToCoffeeResponseDto(response), HttpStatus.CREATED);
}
@PatchMapping("/{coffee-id}")
public ResponseEntity patchCoffee(@PathVariable("coffee-id") @Positive long coffeeId,
@Valid @RequestBody CoffeePatchDto coffeePatchDto) {
coffeePatchDto.setCoffeeId(coffeeId);
Coffee coffee = mapper.coffeePatchDtoToCoffee(coffeePatchDto);
Coffee response = coffeeService.updateCoffee(coffee);
return new ResponseEntity<>(mapper.coffeeToCoffeeResponseDto(response), HttpStatus.OK);
}
@GetMapping("/{coffee-id}")
public ResponseEntity getCoffee(@PathVariable("coffee-id") long coffeeId) {
Coffee response = coffeeService.findCoffee(coffeeId);
return new ResponseEntity<>(mapper.coffeeToCoffeeResponseDto(response), HttpStatus.OK);
}
@GetMapping
public ResponseEntity getCoffees() {
List<Coffee> coffees = coffeeService.findCoffees();
//list 처리를 stream으로
/*
List<CoffeeResponseDto> response = coffees.stream()
.map(coffee -> mapper.coffeeToCoffeeResponseDto(coffee))
.collect(Collectors.toList());
*/
// Mapper 인터페이스내 list처리 메서드 만들어서
List<CoffeeResponseDto> response = mapper.coffeesToCoffeeResponseDtos(coffees);
return new ResponseEntity<>(response, HttpStatus.OK);
}
@DeleteMapping("/{coffee-id}")
public ResponseEntity deleteCoffee(@PathVariable("coffee-id") long coffeeId) {
coffeeService.deleteCoffee(coffeeId);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
- (1) : service 클래스와 mapper 인터페이스를 멤버변수로 생성
- (2) : 생성자를 통한 의존성 주입(DI) 받기 - Spring Bean
- (3) : mapper를 통해 Dto를 entity로 변환
- (4) : Service를 통해 서비스 계층으로 entity데이터 전달하여 응답(Response)entity 데이터로 저장 (서비스 계층 연동)
- (5) : mapper를 통해 ResponseEntity를 ResponseDto로 변환
- 나머지 핸들러 메서드에도 위와 같이 서비스계층 연동과 mapper를 통한 데이터 변환 적용
'Spring Framework' 카테고리의 다른 글
비즈니스 로직에 대한 예외 처리, 사용자 정의 예외(Custom Exception) (0) | 2023.04.18 |
---|---|
Spring MVC 예외처리 (0) | 2023.04.16 |
DTO(Data Transfer Object) (0) | 2023.04.13 |
SpringMVC - Controller (0) | 2023.04.12 |
Spring MVC (0) | 2023.04.12 |
Comments