Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4기 안재영] Shorten URL 과제 PR입니다. #54

Open
wants to merge 17 commits into
base: JaeyoungAhn
Choose a base branch
from

Conversation

JaeyoungAhn
Copy link
Member

요구사항

각 요구사항을 모두 충족할 수 있도록 노력해봅시다.

  • URL 입력폼 제공 및 결과 출력
  • URL Shortening Key는 8 Character 이내로 생성
  • 단축된 URL 요청시 원래 URL로 리다이렉트
  • 단축된 URL에 대한 요청 수 정보저장 (optional)
  • Shortening Key를 생성하는 알고리즘 2개 이상 제공하며 애플리케이션 실행중 동적으로 변경 가능 (optional)

✅ API 명세

POST: /v1/url => 링크 줄이기
GET: /v1/url/{hash} => 단축 링크를 통해 접속하기

✅ View 명세

/url-shortner => 링크 줄이는 화면으로 이동
/display-result => 링크를 줄인 결과 페이지로 이동

👩‍💻 시연

1 2 3

궁금한 점

리다이렉션 성공시 조회수를 1 증가 시키는 경우, 최초 시도 시에는 1이 증가하지만,
이후에 다시 시도할 경우 조회수가 오르지 않는 것을 목격하였습니다. (Postman이 아닌 브라우저)
캐시랑 관련이 있다고 생각이 들지만, 리다이렉션을 위해 서버에 접근해야하는 과정이 반드시 포함되어있음에도
받아오는 과정을 생략하게 되는건지 잘 모르겠습니다. 어떤 부분을 학습해보면 좋을까요?

Copy link

@WooSungHwan WooSungHwan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코멘트 몇자 남겼습니다 ㅎ 고민의 흔적이 많이 보이네요ㅎㅎ 수고하셨어요!

Comment on lines +14 to +15
private final UrlStore store;
private final UrlReader reader;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오우 굳

}

@Transactional
public OriginalUrl redirectUrl(String hash) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hash라는 변수명보단 key가 맞을듯 합니다. key역할이 될수 있는게 hash인데 파라미터로 받는 순간 이건 hash라기 보다는 key로 넘어왔다고 생각해야 덜 헷갈릴 것 같네요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신대로 hash는 너무 구체적인 것 같다고 생각합니다. key로 바꿔보겠습니다!

import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단순한 기능 exception 예외처리뿐만 아니라 스프링을 사용하는 환경에서 발생할 수 있는 부분의 특징도 고려해서 정의해보도록 하세요.

예를 들어, 405가 발생하는 exception이 터졌는데 그럼 @ExceptionHandler(Exception.class) 여기 탈거고 500 으로 리턴될텐데 이런 부분은 고려가 안됐다고 생각이 들게되는 것 같아요.

귀찮아도 이런게 쌓이고 쌓여야 기본기가 될겁니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스프링 환경 내에 발생하는 더 많은 익셉션들을 처리해보는 습관을 들이도록 하겠습니다!!

this.repository = repository;
}

public UrlMapping retrieveUrlMapping(String hash) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retrive 좋네요 명칭

Comment on lines +23 to +24
@Column(name = "hash")
private String hash;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얘는 그냥 이렇게 두면 안될것같은데요? unique 조건은 걸어줘야하지 않을까요?
-> 해쉬충돌

Comment on lines +36 to +39
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new UrlShortenerException(BLANK_ORIGINAL_URL);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런데서 BindingResult 예외처리는 빼봅시다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커스텀 익셉션를 new할 때 BLANK_ORIGINAL_URL과 같은 Enum을 통해 별도의 정형화된 에러 출력 코드라든지 메세지를 조작할 수 있었지만 @Valid를 통해 생기는 validation 관련 익셉션은 커스텀 익셉션이 아니다보니 정형화된 처리가 어려울 것 같아 이런 방법을 생각했던 것 같습니다. 조금 더 좋은 방법은 없을지 고민해보겠습니다.

import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class UrlThymeleafController {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redirect 해주는 로직은 없네요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

주소창으로 입력하는 것만 떠올랐었는데 thymeleaf로도 리다이렉션해줄 수 있을 것 같습니다.

import jakarta.validation.constraints.Pattern;

@Embeddable
public class OriginalUrl {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 클래스는 요청 파라미터 바인딩도 하는거같은데 embeddable이기도 하네요? ㅎ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

작은 프로그램의 특성상 별도의 DTO를 사용을 하지 않으려고 해당 클래스를 통해 파라미터 바인딩을 하는 방법을 택했지만 다른 측면으로 생각해보니 @embeddable이라는 어노테이션 자체가 JPA에 관련된 코드기도 해서 책임의 분리가 일어나지 않은 것 같습니다. 서비스 클래스 안쪽에서 해당 클래스를 사용하는 것이 바람직할 것 같습니다.

ErrorCode errorCode = exception.getErrorCode();
ErrorResponse errorResponse = ErrorResponse.of(errorCode);

logger.error("UrlShortenerException: {}", errorResponse);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 로그의 레벨이 error가 맞는지 고민해보면 좋을것 같아요.
각 로그 레벨을 어떤 기준으로 나누고 찍을지 정해보시지요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

관련해서 검색을 해보니 이미 개발자가 인지하고있는 구체적인 커스텀 익셉션인 경우 로그 레벨이 error인 것보다, info 수준에서 다뤄주는게 좋을 것 같아서 이 경우 info를 사용하고, Exception 같이 상위 레벨이기에 구체적으로 어떤 익셉션인지 개발자가 인지하지 못하고 있을 수도 있는 경우에는 error를 사용하는 방식으로 수정해보겠습니다.

Comment on lines +47 to +56

@GetMapping("/{hash}")
public ResponseEntity<Void> redirectURL(@PathVariable String hash) {
OriginalUrl originalUrl = service.redirectUrl(hash);

return ResponseEntity
.status(HttpStatus.MOVED_PERMANENTLY)
.header(HttpHeaders.LOCATION, originalUrl.getUrl())
.build();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이게 hash가 shorturl에서 여러 구현 방법중에 하나인데
이걸 path값과 paratmer로까지 보여줘야하는지 의문이 드네요.

hash가 아닌 다른 방식으로 사용하거나, 알고리즘이 바뀐다면 모두 바꿔야 되지 않을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신 내용이 맞는 것 같습니다. 좀 더 일반화된 방법을 고안해보겠습니다.

Comment on lines +8 to +9
return new ShortenUrlResponse(urlMapping.getHash(), urlMapping.getOriginalUrl().getUrl());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get.get 디미터의 법칙에 대해 알아보세요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 엔티티의 내부 사정을 너무 구체적으로 알지 못하도록 연속되는 get은 해당 엔티티 안에서 해결할 수 있도록 하겠습니다.

static final char[] BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray();

private Base62Encoder() {
// on purpose
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// on purpose 라는 주석은 코드의 의도를 완전히 설명하지 못하는거 같은데요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의도가 있어서 지우지 않았다면 어떤 의도였는지 구체적으로 주석을 남겨보겠습니다.


public class Base62Encoder {

static final char[] BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상수 명명 규칙은 대문자 + 언더바입니다 BASE_62

@ParameterizedTest

@MethodSource("provideFailOriginalUrl")
void OriginalUrl_FailCreation(String url) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 메소드 네이밍을 이렇게 한 의도가 있을까요?
없다면 표준을 지켜주세요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엔티티의 타입에 대한 테스트여서 표준을 간과했던 것 같습니다. 수정하겠습니다.

Comment on lines +24 to +34

@Autowired
MockMvc mockMvc;

@Autowired
UrlService service;

ObjectMapper objectMapper = new ObjectMapper();

private static final String BASE_URL = "/v1/url";
private static final String TEST_URL = "https://www.google.com";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

누군 private 누군 default.

이유가 없다면 통일성을 맞춰주는게 어떨까요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

부주의 했던 것 같습니다. private으로 통일하겠습니다.

Comment on lines +64 to +68

}



Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??


@ParameterizedTest
@MethodSource("provideEncodeTestData")
void Encode_SuccessfullyConverted(Long seq, String hash) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메소드 네이밍 규칙을 지켜주세요

Comment on lines +22 to +26

public void setUrl(String url) {
this.url = url;
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자로 초기화 시키는데 set이 필요한 이유가 있을까요??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필요하지 않은 게터나 세터는 필요할때만 만드는 습관을 들이겠습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants