본문 바로가기

프로젝트/고민

단축 url 프로젝트 회고

간단한 url 단축 프로젝트를 만들었다. 요구사항은 다음과 같다.

  1. URL 단축 서비스를 만들어야함
  2. 단축 URL의 키는 8글자로 생성 - www.abc.com/{단축 URL 키}
  3. 키 생성 알고리즘은 자유롭게
  4. 단축 URL은 원래 URL로 리다이렉트 되어야함
  5. 원래 URL로 다시 단축 URL 생성해도 항상 새로운 단축 URL 생성되어야함
    • 기존 URL은 계속 동작 해야함
  6. 단축 URL에서 리다이렉트 될 때 카운트 증가
  7. 이러한 정보(단축 URL, 카운트, 원래 URL)를 확인할 수 있는 기능
  8. DB 없이 컬렉션으로 데이터 저장
  9. 위 기능을 확인할 수 있는 테스트 코드
  10. 해당 서비스를 사용할 수 있는 UI 페이지

위의 요구사항을 보고 url 리다이렉션을 이용해서 만들면 간단하게 만들 수 있을거라고 생각했다.

 

하지만 만들고 보니 SOLID의 원칙을 최대한 지키면서 코딩을 하려 했음에도 불구하고 많은 부분에서 지켜지지 못했다. 또한 와일드 카드의 사용법에 대해서도 제대로 이해하지 못했고, 컨트롤러에서 예외를 처리하는 방법에 대해서도 미숙했다. 또한 HTTP 상태 코드에 대해서 제대로 모르고 있었고, DDD에 대해서도 잘 모르고 있었고, 여러개의 프로필을 사용하는 방법에 대해서도 모르고 있었다.  하나씩 짚고 넘어가자면,

 

다음은 url 객체를 map에 저장하는 메소드 인데, 단축 url을 key(String) 원본 Url 객체를 value로 하여 저장을 하였다. 하지만 저장을 해야하는 메소드에 단축 url을 만드는 로직까지 넣어 SRP에 어긋난 코드를 짜게 됐다. 이런 로직은 따로 빼줘서 관리해주는게 좋을것 같다.

while (isExistByShortUrl(shortUrl)) {
            shortUrl = Sha256.encode(originalUrl, ++position);
        }
        Url saveUrl = Url.builder()

 

다음은 단축 url을 만드는 코드인데, 와일드 카드를 사용한 이유는 예외를 던지기 때문에 컴파일 타임에 타입을 정할 수 없다고 생각하여 와일드 카드를 사용했는데 잘못된 생각이었다. 그래서 컨트롤러에서 예외처리하는 법을 찾아보았는데 간다힌 말하자면 다음과 같다.

 

스프링 부트는 예외가 발생하면 기본적으로 /error로 에러 요청을 다시 전달하도록 WAS 설정을 해둔다. 기본적인 에러 처리 방식은 결국 에러 컨트롤러를 한번 더 호출하는 방식으로 작동해서 에러를 제어하려면 @ExceptionHandler를 사용해 에러를 처리한다.

 

위의 내용을 바탕으로 아래아래의 코드와 같이 예외 처리를 하는 코드를 작성하고, 와일드 카드를 사용했다. 또한 @Value라는 어노테이션을 통해 yml이나 properties파일의 값을 가져올 수 있는데, domain인 경우 실행 환경에 따라 달라지기 때문에 프로필별로 관리를 하게 수정했다. 또한 처음에는 lombok을 이용해 편리하게 개발을 했지만, lombok없이도 개발할 수 있어야 한다고 생각이 들어 build.gradle파일의 lombok 설정을 주석처리한 후 개발을 했다.

@PostMapping
    public ResponseEntity<?> createShortUrl(@Valid @RequestBody UrlDto url, @Value("${domain}") String domain) throws URISyntaxException {
        if (UrlValidators.isValid(url.url())) {
            String requestUrl = urlService.save(url.url());
            URI uri = new URI("/" + requestUrl);
            String shortenUrl = domain + requestUrl;
            UrlResponseDto responseUrl = new UrlResponseDto(shortenUrl, urlService.getRequestCount(requestUrl));
            return ResponseEntity.created(uri).body(responseUrl);
        }
        throw new UrlValidException();
    }
@RestControllerAdvice
//@Slf4j
public class ExceptionHandlerController {

    @ExceptionHandler(UrlShortenException.class)
    private ResponseEntity<ExceptionDto> handleUrlShortenException(UrlShortenException urlShortenException) {
        ExceptionDto response = ExceptionDto.getExceptionInformation(urlShortenException);
        errorLogging(urlShortenException);
        HttpStatus httpStatus = urlShortenException.getHttpStatus();
        return new ResponseEntity<>(response, httpStatus);
    }

    private void errorLogging(Exception exception) {
        //log.error("Exception: {} , message: {}", exception.getClass().getSimpleName(), exception.getMessage());
        System.out.println("Exception: " + exception.getClass().getSimpleName() + " message: " + exception.getMessage());
    }
}

이번 프로젝트를 통해 HTTP 상태코드에 대해서도 공부를 했는데 이는 다음 글에 작성하도록 하겠다.

 

또한 처음에 Service계층에서 domain을 반환했는데 이는 DDD와 관련된 내용으로 간다하게 말하면 서비스계층에서 도메인을 반환해주면 결국 도메인 자체의 데이터를 다 노출 시켜줘야한다 그래서 필요한 데이터만 dto로 만들어서 반환한다. 자세한 내용은 DDD에 대해 공부할 때 작성하도록 하겠다.

 

테스트

테스트 코드를 작성하면서 단위테스트를 하는 것의 중요성을 많이 느꼈다. 

우선 기능 추가나 수정사항이 생길 경우 매번 실행하는 것보다 테스트 코드를 실행하는 것이 훨씬 빨랐고

테스트를 진행하며 예상하지 못한 오류에 대해서 잡고 수정할 수 있었다. 단위테스트를 하며 코드의 문제 여부를 빠르게 잡아 개발에 대한 비용을 절감 할 수 있었다.

 

다음으로는 목 테스트로 컨트롤러에 대한 테스트 코드를 작성했다. 이를 작성하면서 목 테스트에 대해서 공부를 하게 됐는데, 다음과 같은 장단점이 존재하는것을 알게 되었다.

 

목 객체를 통해 시나리오를 구성하여 테스트를 할 수 있다는 장점도 있지만, 실제 구현체는 목객체와 다르게 작동할 수 있다는 단점을 가져 테스트에 대한 신뢰도가 떨어진다는 점과, 테스트 코드에 사용되는 협력객체들이 전부 명시해 줘야 해서 테스트 코드를 이해하기 어렵고, 협력하는 객체가 조금만 변하더라도 코드를 전부 수정해야 한다는 단점이 있다.

 

이 밖에도 매직넘버를 상수로 만들지 않는 실수, 들여쓰기에 관한 실수도 하였다. 이런 부분에 대해서 항상 생각하며 습관을 들이는게 중요한것 같다. 

 

프로젝트를 하면서 처음부터 최대한 배운 내용을 바탕으로 객체지향적인 코드를 작성하려고 했지만, 쉽지 않았고 부족한 부분을 많이 발견하며 공부를 했다. 다음 프로젝트때는 좀 더 나은 코드를 작성할 수 있기를 바란다.