전략 패턴, 개별 컴포넌트 호출

Mugeon Kim·2023년 7월 21일
0

1. 서론



                                             화면 정의서

  • 중복을 요청하는 부분은 총 2곳이 있습니다.
  • 이때 API 요청은 /api/email , /api/name이며 QueryString의 요청이 email과 name입니다.
  • 이것을 검증하는 서비스 로직을 만들면 총 2개의 서비스가 생성이 됩니다.
  • 하지만 2개의 로직은 비슷한 검증을 합니다. 즉. 중복을 체크하는 로직입니다.
  • 이걸 Type에 따른 서비스 호출을 통하여 하나의 인터페이스 2개의 서비스로 만들어 이것을 해결할 수 있습니다.

2. 본론

검증 코드 (이메일, 이름)

  • 2개의 Controller를 보면 쿼리 스트링에 따라서 똑같은 요청을 합니다.
  • 하지만 하나의 인터페이스를 만들어 2개의 서비스에 구현하면 다음과 같이 의존성을 주입을 받아야 합니다.
    private final List<DuplicateService>duplicateServices;

    public DuplicateServiceFinder(List<DuplicateService> duplicateServices) {
        this.duplicateServices = duplicateServices;
    }

Controller

    @GetMapping("/email")
    public DuplicateResponseDto checkEmailDuplication(@RequestParam(value = "email", required = true) String account, HttpServletRequest request) {
        return getVerifyResponseDto(request.getParameterNames().nextElement(), account);
    }

    @GetMapping("/name")
    public DuplicateResponseDto checkNameDuplication(@RequestParam(value = "name", required = true) String account, HttpServletRequest request) {
        return getVerifyResponseDto(request.getParameterNames().nextElement(), account);
    }

    public static DuplicateResponseDto getVerifyResponseDto(String type, String value) {
        DuplicateService duplicateService = duplicateServices.stream()
                .filter(verifyService -> verifyService.getType().name().equals(type))
                .findAny()
                .orElseThrow(RuntimeException::new);

        return duplicateService.signupDivisionDuplicateCheck(type, value);
    }
  • request.getParameterNames().nextElement()을 통하여 쿼리 스트링의 값을 가져올 수 있습니다. 이후 getVerifyResponseDto를 통하여 parameter에 맞는 서비스를 호출할 수 있습니다.

Interface

public interface DuplicateService {
    DuplicateResponseDto signupDivisionDuplicateCheck(String type, String value);
    DuplicateType getType();
}

Service

@Service
public class EmailServiceImpl implements DuplicateService {

    private final MemberRepository memberRepository;

    public EmailServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }


    @Override
    public DuplicateResponseDto signupDivisionDuplicateCheck(String type, String value) {
        return DuplicateResponseDto.of(memberRepository.findByEmail(value)
                .isPresent() ? DuplicateResult.FALSE.getDivisionResult() : DuplicateResult.TRUE.getDivisionResult());
    }

    @Override
    public DuplicateType getType() {
        return DuplicateType.email;
    }
}
@Service
public class NameServiceImpl implements DuplicateService {

    private final MemberRepository memberRepository;

    public NameServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public DuplicateResponseDto signupDivisionDuplicateCheck(String type, String value) {
        return DuplicateResponseDto.of(memberRepository.findByName(value)
                .isPresent() ? DuplicateResult.FALSE.getDivisionResult() : DuplicateResult.TRUE.getDivisionResult());
    }

    @Override
    public DuplicateType getType() {
        return DuplicateType.name;
    }
}
  • 이것을 직접 호출하면 true를 반환을 합니다. ( 중복이 없는 경우 ) 만약에 중복이 되면 false를 반환을 합니다.

  • 정상적으로 검증이 가능합니다.
  • 추가적인 리펙토링이 필요합니다. 이 Type에 따른 컴포넌트 호출을 하는 로직이 2번 필요합니다. (1) 검증하는 부분 (2) 회원가입 이렇게 2번이 필요하기 중복적인 코드가 생깁니다.
    public DuplicateResponseDto getVerifyResponseDto(String type, String value) {
        DuplicateService duplicateService = duplicateServices.stream()
                .filter(verifyService -> verifyService.getType().name().equals(type))
                .findAny()
                .orElseThrow(RuntimeException::new);

        return duplicateService.signupDivisionDuplicateCheck(type, value);
    }

위에 함수를 static으로 바꾸면 안되나요?

  • 위에 함수를 static으로 변경할 수 없습니다. 왜냐하면 의존하는 부분이 스프링 컨테이너에 의해서 빈을 관리하고 있습니다. 하지만 이 함수에서 static을 선언하게 되면 스프링 컨테이너가 관리를 하지 못합니다. 왜냐하면 static은 런타임을 하면 메소드 영역에 할당을 합니다. 그렇게 되면 IOC가 관리를 하지 못하여 DI가 불가능하게 됩니다.

개별 컴포넌트 분리

  • 위에 중복적인 코드의 분리를 위하여 하나의 컴포넌트를 만들어서 의존성을 주입을 받을 수 있게 만들면 됩니다.
@Component
public class DuplicateServiceFinder {

    private final List<DuplicateService>duplicateServices;

    public DuplicateServiceFinder(List<DuplicateService> duplicateServices) {
        this.duplicateServices = duplicateServices;
    }

    public DuplicateResponseDto getVerifyResponseDto(String type, String value) {
        DuplicateService duplicateService = duplicateServices.stream()
                .filter(verifyService -> verifyService.getType().name().equals(type))
                .findAny()
                .orElseThrow(RuntimeException::new);

        return duplicateService.signupDivisionDuplicateCheck(type, value);
    }
}
  • Componet로 빈으로 등록을 하여 회원가입의 로직과 중복 체크하는 로직에서 의존성을 주입받아서 사용하면 중복되는 코드 없이 사용이 가능합니다.

회원가입

    @Override
    @Transactional
    @TimeAnnotation
    public MemberSignupResponse signUp(
            MemberSignupRequest request
    ) {
        checkEmailAndNameDuplication(request);
        Member member = Member.builder()
                .email(request.getEmail())
                .password(passwordEncoder.encode(request.getPassword()))
                .name(request.getName())
                .roles(new HashSet<>())
                .build();

        signupWithRole(member);

        return MemberSignupResponse.of(memberRepository.save(member));
    }
    
    private void checkEmailAndNameDuplication(MemberSignupRequest request) {
        divisionDuplicationAboutNickEmail(request);
        divisionDuplicationAboutName(request);
    }

    private void divisionDuplicationAboutName(MemberSignupRequest request) {
        DuplicateResponseDto name = duplicateServiceFinder.getVerifyResponseDto("name", request.getName());
        Optional.of(name)
                .filter(duplicateResponseDto -> duplicateResponseDto.getVerify().equals(DuplicateResult.FALSE.getDivisionResult()))
                .ifPresent(duplicateResponseDto -> {
                    throw new RuntimeException("중복 이름");
                });
    }

    private void divisionDuplicationAboutNickEmail(MemberSignupRequest request) {
        DuplicateResponseDto email = duplicateServiceFinder.getVerifyResponseDto("email", request.getEmail());
        Optional.of(email)
                .filter(duplicateResponseDto -> duplicateResponseDto.getVerify().equals(DuplicateResult.FALSE.getDivisionResult()))
                .ifPresent(duplicateResponseDto -> {
                    throw new RuntimeException("중복 이메일");
                });
    }
  • 위에 의존성을 주입을 받아서 해당 타입에 따른 컴포넌트 호출하는 함수를 사용을 할 수 있습니다.
profile
빠르게 실패하고 자세하게 학습하기

0개의 댓글