simDev1234
심플하고 차분하게
simDev1234
전체 방문자
오늘
어제
  • 분류 전체보기
    • Computer Science
      • Basic Math
      • Data Structure
      • Algorithm
      • Database
      • OS
    • Language
      • Java
      • Kotlin
      • SQL
    • Framework
      • Spring
      • Orm&Mapper
      • 프로젝트로 스프링 이해하기
      • 스프링 라이브러리
    • Infra
      • Cloud
      • Docker
      • Redis
      • AWS, Azure
      • Device
    • Etc
      • CleanCoding
    • Git,Github

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • JVM메모리구조
  • 자바메모리구조
  • 자바
  • 컨트롤러
  • 참조타입
  • 자바프로그래밍
  • controllerTest
  • 자바프로그램
  • null
  • scanner #next() #nextLine()
  • 참조변수
  • 404
  • 스프링

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
simDev1234

심플하고 차분하게

Framework/Spring

[Validation] 데이터 검증, 비즈니스 로직 검증

2022. 9. 21. 04:41

|  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)

  • @NotNull: to say that a field must not be null.
  • @NotEmpty: to say that a list field must not empty.
  • @NotBlank: to say that a string field must not be the empty string (i.e. it must have at least one character).
  • @Min and @Max: to say that a numerical field is only valid when it’s value is above or below a certain value.
  • @Pattern: to say that a string field is only valid when it matches a certain regular expression.
  • @Email: to say that a string field must be a valid email address.

    [출처] https://reflectoring.io/bean-validation-with-spring-boot/

- 수업에서는 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);
        }
    }
}

 

[ 참고 및 출처 ]

부트캠프 수업 내용

https://reflectoring.io/bean-validation-with-spring-boot/

'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
    'Framework/Spring' 카테고리의 다른 글
    • [HTTP] @RequestParam vs @RequestBody
    • [HTTP] User IP와 Agent(Device) 정보 가져오기
    • [스프링] Entity 객체를 생성 : 영속성의 개념 + 자동 Auditing
    • [스프링] 개발을 시작하기 전에 - 요구 사항 분석, 기본 구조 잡기
    simDev1234
    simDev1234
    TIL용 블로그. * 저작권 이슈가 있는 부분이 있다면 댓글 부탁드립니다.

    티스토리툴바