개발놀이터

코딩 스탠다드 5 : 강타입 언어라면 강타입 언어의 장점을 적극 활용하자 (HTTP Request) 본문

리팩토링/코딩 스탠다드

코딩 스탠다드 5 : 강타입 언어라면 강타입 언어의 장점을 적극 활용하자 (HTTP Request)

마늘냄새폴폴 2024. 4. 30. 21:55

이번엔 HTTP Request 에서 강타입을 활용해 안정성을 높이는 방법에 대해 포스팅해보고자합니다. 

 

스프링은 백엔드 서버로 주로 사용됩니다. 그래서 수많은 API들을 가지고 있고 그 API마다 프론트에서 원하는 포맷으로 데이터를 쏴주는 역할을 하죠. 

 

보통 프론트엔드와 통신을 할 때 HTTP 통신을 사용하기 때문에 보통 회사에서 RESTful 하게 API를 만들고 Request를 json으로 받곤합니다. 

 

이때! 자바의 강타입을 활용하면 큰 이득을 볼 수 있습니다. 

 

이번 포스팅에선 강타입을 활용해 HTTP Request 를 안정적으로 받는 방법과 어노테이션을 이용해 Validation을 효과적으로 처리하는 방법까지 보너스로 알아보도록하죠!

 

시작은 늘 그렇듯 상황을 가정하고 들어갑니다. 이번엔 특별히 버전을 높여가면서 리팩토링 해볼겁니다. 

 

상황)

기획자가 개발자에게 회원가입은 이름, 나이, 성별, 휴대폰, 이메일, 아이디, 비밀번호 이렇게 일곱가지 필드를 입력받으라고 합니다. 

 

이번엔 흐름도 없이 바로 코드로 바로 보시죠. 

 

Ver.1

    @PostMapping("/member")
    public String saveMember(HttpServletRequest request) {
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        String gender = request.getParameter("gender");
        String phone = request.getParameter("phone");
        String email = request.getParameter("email");
        String loginId = request.getParameter("loginId");
        String loginPw = request.getParameter("loginPw");
    }

 

요즘은 이렇게 코딩하시는 분은 없으시죠 ㅎㅎ?

 

이건 jsp 로 개발할 때나 이렇게 했지 스프링 쓰는 요즘은 이렇게 개발 잘 안하죠. 

 

Ver.2

우린 스프링을 쓸 때 DTO를 두고 @RequestBody 어노테이션을 이용해서 바인딩합니다. 

 

@Data
public class SaveMemberDto {

    private String name;

    private String age;

    private String gender;

    private String phone;

    private String email;

    private String loginId;

    private String loginPw;
}

 

    @PostMapping("/member")
    public String saveMember(SaveMemberDto dto) {
        memberService.save(dto);
    }

 

이 방법에 문제점은 뭘까요? 정말 편하긴 하지만 검증이 안되어있습니다. 비밀번호는 영문자, 숫자, 특수문자를 넣었는지, 휴대폰은 010-XXXX-XXXX 의 포맷을 따르는지, 이메일은 이메일의 포맷을 따르는지 여기서 더 나아가면 나이는 150을 넘지 않는지 등등...

 

다양한 상황을 검증해야합니다. 이제 버전업을 해보죠. 

 

Ver.3

    @PostMapping("/member")
    public String saveMember(SaveMemberDto dto) {
    	String passwordRegx = "비밀번호 정규표현식";
        String emailRegx = "이메일 정규표현식";
        String phoneRegx = "핸드폰 정규표현식";
        
        if (!dto.getLoginPw().matches(passwordRegx)) {
        	return "돌아가";
        }
        
        if (!dto.getEmail().matches(emailRegx)) {
        	return "돌아가";
        }
        
        if (!dto.getPhone().matches(phoneRegx)) {
        	return "돌아가";
        }
        
        memberService.save(dto);
    }

 

지금 우린 빈칸, 공백, null 이런건 신경도 안쓰고 정규표현식만 맞게 개발하고있습니다. 실전에선 빈칸, 공백, null도 막아야겠죠? 그럼 코드가 엄청 길어질겁니다. 

 

물론 이 상황은 회원가입 상황이라 저 로직은 모든 코드를 통틀어 저렇게 한번만 만들면 되지만... 만약 여러번 검증해야 하는 상황이라 중복이 일어나면 어떡하죠? 

 

저 로직을 계속 넣어줘야합니다. 

 

Ver.4

여기서 더 버전 업을 해보죠. 바로 커스텀 어노테이션을 이용한 Validation입니다.

 

커스텀 어노테이션은 조금 난이도가 있을 수 있으니 더 자세한 내용은 구글링을 통해 학습하시는 것을 추천드립니다!

 

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = PasswordCheckService.class)
public @interface Password {

}

 

우선 Password라는 어노테이션을 만들었습니다. 

 

어노테이션은 간단하게 설명드리고 넘어가겠습니다. 

 

  • @Target : 해당 메서드가 필드에 적용되는지, 메서드에 적용되는지, 클래스에 적용되는지 등등을 적어야합니다. 그럴때 사용하는 것이 @Target 어노테이션이죠. 
  • @Retention : 이건 어노테이션이 언제 적용될지 정하는 어노테이션입니다. RUNTIME으로 적용하면 나중에 자바 리플렉션을 이용해서 동적으로 처리할 수도 있습니다. 리플렉션에 대해서는 설명하지 않겠습니다. 
  • @Documented : 이건 저도 정확히 뭔진 모르겠는데... 그냥 커스텀 어노테이션에 다 붙이길래 붙이는게 습관이 됐습니다. 
  • @Constraint : 가장 중요한 어노테이션입니다. validatedBy 에 선언한 PasswordCheckService 에 선언된 로직에 따라 제약을 걸 수 있는 어노테이션입니다. 

 

자 이제 PasswordCheckService를 만들어야겠죠? 

 

public class PasswordCheckService implements ConstraintValidator<Password, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        String passwordPattern = "정규표현식 넣으세요.";
        return !value.matches(passwordPattern);
    }
}

 

PasswordCheckServiceConstraintValidator 를 구현한 클래스입니다. isValid 라는 메서드를 구현하면서 제약조건을 걸 수 있습니다. 

 

정규표현식과 맞지 않으면 false를 반환하여 제약조건에 걸리는 것입니다. 

 

@Data
public class SaveMemberDto {

    @NotBlank
    private String name;

    @NotBlank
    private String age;

    @NotBlank
    private String gender;

    @NotBlank
    private String phone;

    @NotBlank
    private String email;

    @NotBlank
    private String loginId;

    @NotBlank
    @Password	// NEW!!
    private String loginPw;
}

 

여기서 우리가 만든 커스텀 어노테이션을 쓰면 됩니다. 그리고 제가 @NotBlank도 적었죠? 이건 롬복에서 제공해주는 어노테이션입니다. 

 

@NotNull > @NotEmpty > @NotBlank 

 

오른쪽으로 갈수록 더 강력한 제약조건입니다.

 

  • @NotNull 은 null인 경우만 막아줍니다.
  • @NotEmpty은 null과 "" (빈칸) 을 막아줍니다. 
  • @NotBlank는 가장 상위 제약조건이며 null, "" (빈칸), " " (공백) 을 막아줍니다. 

 

그리고 이제 컨트롤러 부분에서 작업해주면 됩니다. 

 

    @PostMapping("/member")
    public String saveMember(@Valid @RequestBody SaveMemberDto dto, BindingResult bindingResult) {
        if (bindingResult.hasError()) {
        	return "돌아가";
        }
        
        memberService.save(dto);
    }

 

이렇게 작업하면 됩니다. 코드가 한결 깔끔해졌습니다. 

 

이제 Password 말고 Email, Phone 이런 어노테이션도 똑같이 만들어서 적용하면 됩니다. 

 

 

마치며

결국 하고싶었던 얘기는 강타입 언어는 강타입의 장점을 살려야한다는 것입니다. HTTP Request를 받을 때 강타입 언어의 장점을 활용하면 애플리케이션을 더 탄탄하게 만들 수 있습니다. 

 

이제 코딩 스탠다드 주제가 다 떨어졌습니다... 어떻게든 쥐어짜내고있는데 일단 제가 알고있는건 다 써먹은거같습니다. 

 

일단 여기까지 하고 마무리지어보겠습니다. 감사합니다!