| Validation
- 지난 번 스프링을 처음 배울 당시 아래와 같이 Validation에 대한 개념을 배웠었다.
✅ Validation은 유효성 검증을 말하며, 주로 HTTP Request에서 잘못된 내용을 검증할 때 사용한다. - 데이터 검증 : 필수 데이터 / 문자열 길이 및 숫자형 데이터 범위 / 이메일 및 신용카드 번호 등 형식 확인 - 비즈니스 검증 : 서비스 정책에 따라 데이터 검증 ✅ Validation 방식 1) Java Bean Validation : dto클래스 맴버에 Annotaion(ex. @NotBlank, @Size, @Email...)을 붙이는 방식 2) Spring validator 인터페이스 구현을 통한 validation ✅ Validation 주의사항 : Validation을 멀리 두면 테스트 및 유지보수성이 떨어지므로 가능한 Annotaion방식을 쓰는 게 좋다. * 수업에서의 제안 : 1차로 dto에 Annotation방식 사용, 2차로 비즈니스 검증 후 실패 시 Custom Exception throw |
- 오늘은 계좌 프로그램의 계좌 생성 기능에 대한 단위 테스트를 하면서 위 데이터를 검증하고, 비즈니스 로직을 검정하는 실습해보았다.
- 이것을 연습 삼아서 동물 병원 차트를 생성하는 걸로 바꿔서 해보았다.
1. 데이터 검증하기 - Common Validation Annotations(Java Bean Validation)
|
- 수업에서는 HTTP 요청/응답 데이터를 검증할 때, DTO객체에서 Java Bean Validation을 썼다.
- 연습 삼아 동물 병원 차트를 통해 정리해보았다.
- 아래는 동물 병원 차트 도메인 객체
// import 생략
// Lombok getter/setter/생성자 생략
@Entity
@EntityListener(AuditingEntityListener.class)
public class Chart{
@Id
@GeneratedValue // sequence (auto-increment)
private Long id;
private String chartNo; // 차트번호 (ex. AA20221211 - 종분류+생성일자)
@ManyToOne
private Owner owner; // 주인
private Patient patient; // 환자 기본 정보 (이름, 나이, 성별 등)
private VaccineRecord vaccineRecord; // 백신 여부
private String memo; // 증상 메모
private String diagnose; // 진단명
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
[1] DTO : Request/Response Data-binding용 객체에 제약조건 걸기
[ 예시 ] 동물 병원 차트 생성 DTO - Request와 Response 시 Data-binding용 - 요청시 : 주인 id와 동물 id는 반드시 받아 존재하는지 확인한다. / 차트에 메모와 진단이 있을 수도 있고 없을 수도 있다. - 응답시 : 생성된 차트의 차트번호 반환 |
// import 생략
public class CreateChart{
// lombok 게터/세터/생성자 생략
public static class Request{
@NotNull
@Min(1)
private Long ownerId;
@NotNull
@Min(1)
private Long patientId;
private String memo;
private String diagonose;
}
// lombok 게터/세터/생성자 생략
public static class Response{
@NotNull
private String chartNo;
}
public static Response from(ChartDto chartDto){
return Response.builder()
.ownerId(chartDto.getOwnerId())
.patientId(chartDto.getPatientId())
.memo(chartDto.getMemo())
.diagonose(chartDto.getDiagnose())
.build();
}
}
[2] DTO : 도메인에서 필요한 데이터만 넘겨주는 객체 생성
* 지연 로딩을 위해서 필요한 객체이다. (지연 로딩이란? JPA에서 프록시 객체 - 참조값을 가진다 - 로 Entity간 연관관계를 주입할 때, 한 번에 모든 데이터를 올리는 것이 아니라 필요에 따라 지연하여 데이터를 로딩하는 방식)
* 지연 로딩을 하게 되면 사용자의 요청사항에 유연하게 반응할 수 있다.
// import 생략
// lombok 관련 annotation 생략
public class ChartDto {
private String chartNo;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public static ChartDto fromEntity(Chart chart){
return ChartDto.builder()
.chartNo(chart.getChartNo())
.createdAt(chart.getCreatedAt())
.updatedAt(chart.getUpdatedAt())
.build();
}
}
[3] Controller에서 @Valid로 요청 시 데이터 제약사항 검증하기
[ 예시 ] 동물 병원 차트 생성 컨트롤러 - REST 방식의 컨트롤러 POST http://ip주소:port번호/chart content-type: application/json { "ownerId": 1, "name": "여름이" } |
// import 생략
@RestController
@RequiredArgsConstructor
public class ChartController{
private final ChartService chartService;
@PostMapping("/chart")
public CreateChart.Response createChart(@RequestBody @Valid CreateChart.Request request){
return CreateChart.Response.from(
chartService.createChart(
request.getOwnerId(),
request.getPatientId(),
)
);
}
}
2. 비즈니스 로직 검증하기 - Custom Exception
[1] exception : 예외사항에 대한 Custom Exception을 만든다.
(1) 먼저 Enum으로 예외 코드를 만든다. (2) 해당 Enum을 담은 커스텀 Exception을 만든다. |
// import 생략
@Getter
@AllArgsConstructor
public Enum ErrorCode {
OWNER_NOT_FOUND("주인이 없습니다."),
PATIENT_NOT_FOUND("환자가 없습니다."),
CHART_NO_NOT_FOUND("차트번호를 찾을 수 없습니다.")
private String description;
}
// import 생략
// lombok 생략
public class ChartException extends RuntimeException {
ErrorCode errorCode;
String errorMessage;
public ChartException(ErrorCode errorCode) {
this.errorCode = errorCode;
this.errorMessage = errorCode.getDescription();
}
}
[2] service : 비즈니스 로직에 따라 서비스 객체 생성하기
[ 예시 ] 동물 병원 차트 생성 서비스 - 비즈니스 로직 검증 - 주인이 있는지 조회하기 - 동물이 있는지 조회하기 - 동물의 차트가 100개를 넘어가는지 확인 (*validate를 위해 임의로 만든 것) - 최신 차트 번호 생성 - 차트 저장 후, 차트 정보를 넘긴다. |
// import 생략
@Service
@RequiredArgsConstructor
public class ChartService {
private final ChartRepository chartRepository;
private final OwnerRepository OwnerRepository;
private final PatientRepository PatientRepository;
@Transactional
public ChartDto createChart(Long ownerId, Long patinetId){
Onwer owner = ownerRepositroy.findById(ownerId)
.orElseThrow(() -> new ChartException(ErrorCode.OWNER_NOT_FOUND));
Patient patient = patientRepository.findById(patientId)
.orElseThrow(() -> new ChartException(ErrorCode.PATIENT_NOT_FOUND));
// 차트 100개 넘는지 별도로 validate 처리
// -- [ctrl]+[alt]+[M]으로 메소드를 extract
validate(patient);
String chartNo = chartRepository.findFirstByOrderByIdDesc()
.map(chart -> (Integer.parseInt(chart.getChartNo().substring(2) + 1)) + "")
.orElseThrow(() -> new ChartException(ErrorCode.CHART_NO_NOT_FOUND));
return ChartDto.fromEntity(
chartRepository.save(
Chart.builder()
//생략
.build()
)
);
}
public void validate (Patient patient) {
if (chartRepository.CountByPatient(patient) == 100) {
throw new ChartException(ErrorCode.MAX_CHART_COUNT_100);
}
}
}
[ 참고 및 출처 ]
부트캠프 수업 내용
'Framework > Spring' 카테고리의 다른 글
[HTTP] @RequestParam vs @RequestBody (0) | 2022.10.19 |
---|---|
[HTTP] User IP와 Agent(Device) 정보 가져오기 (0) | 2022.10.15 |
[스프링] Entity 객체를 생성 : 영속성의 개념 + 자동 Auditing (0) | 2022.09.15 |
[스프링] 개발을 시작하기 전에 - 요구 사항 분석, 기본 구조 잡기 (1) | 2022.09.15 |
[스프링] 프로젝트 전 꼭 알아두면 좋은 것들 (0) | 2022.09.13 |