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입니다. #51

Open
wants to merge 31 commits into
base: wonu606
Choose a base branch
from

Conversation

wonu606
Copy link

@wonu606 wonu606 commented Oct 9, 2023

📌 과제 설명

Local Memory에 저장하는 URL Shortener

2023-10-10.5.09.52.mov

👩‍💻 요구 사항과 구현 내용

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

✅ PR 포인트 & 궁금한 점

저는 Url이 이미 hash값으로 생성되어 있다면 재사용하고 생성되지 않았다면 생성하려고 하였습니다.
그러다 보니
UrlShortenerApiController.findOrCreateShortenedUrl
DefaultUrlShortenerService .findOrCreateShortenUrlHash 같이 2가지 기능을 하는 로직이 생겨났습니다.
이 로직들을 어떻게 분리하면 좋을지 궁금합니다.

- SpringBoot 프로젝트 생성
- .gitignore 설정
- 테스트를 통과하지만 서비스 추가시 실패 예정
- 서비스 동작을 통해 성공하도록 구현
- request에서 간략한 검증하도록 수정
- response 반환시 json으로만 받을 수 있게 강제
- DefaultUrlShortenerService에 UrlLink를 생성하는 로직 구현
- UrlLink 도메인은 원본 Url과 Hash 값을 저장
- 잘못된 id 값이 들어올 경우 Exception 발생하도록 수정
- Hash 값을 가진다는 것을 명확하게 표현하기 위해 이름 변경
- Url에 대한 Hash를 생성하고 Repository를 통해 존재 여부를 확인하여 유일성 보장
- yaml 형식으로 작성된 내용을 properties 형식으로 수정
- `LocalUrlLinkRepository` 클래스를 통해 `UrlLink` 객체를 Local Memory에서 저장하고 관리
- `UrlLink` 객체를 관리하는 클래스명을 명확하게 표현하도록 수정
- UrlShortenerApiController와 UrlShortenerService에서 수행하는 작업이 유사하나,
함수명이 다르므로 동일한 작업을 수행하는 것처럼 보이지 않았음
두 클래스에서의 작업이 유사함을 명확히 나타내기 위해 create 함수명을 통일함
- URL 단축 생성 로직에서, 이미 단축된 URL이 존재하는 경우 해당 URL을 반환하도록 로직 수정
- 이 변경으로 인해 불필요한 리소스 사용을 줄이고, 동일한 원본 URL에 대해 항상 동일한 단축 URL을 제공함
- 중복된 테스트 클래스 제거(UrlShortenerApiControllerMockTest)
- 함수명 given_when_then 사용
- private으로 선언되지 않은 필드 private 선언
- get은 못 찾을 시 에러가 발생해야 한다고 생각하지만 현재 로직은 발생하지 않는다.
find가 존재하지 않더라도 오류가 발생하지 않는다고 생각하여 변경
- ShortUrl을 받아 원본 URL로 리다이렉트하는 로직 구현
- 사용자가 ShortUrl을 접근할 때, 해당 ShortUrl에 매핑된 원본 URL로 브라우저가 리다이렉트됨
- `UrlLinkRepository`에서 `UrlHash`를 사용하여 `UrlLink`를 조회한 후, 원본 URL을 반환하도록 로직 수정(이전에는 `UrlHash` 값을 반환하였으나, 이제는 원본 Url 값을 반환하도록 변경)
Comment on lines +7 to +10

private static final UrlValidator URL_VALIDATOR = new UrlValidator();
public static final int URL_MAX_LENGTH = 2_000;

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 +11 to +20
public Url {
if (!URL_VALIDATOR.isValid(value)) {
throw new IllegalArgumentException("잘못된 URL 주소입니다. Current URL: " + value);
}

if (StringUtils.length(value) > URL_MAX_LENGTH) {
throw new IllegalArgumentException(
String.format("URL의 길이는 %d를 넘어갈 수 없습니다 Current Length: %d", URL_MAX_LENGTH, value.length()));
}
}
Copy link

Choose a reason for hiding this comment

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

정적 팩토리 메소드로 URL_VALIDATOR를 주입받아 검증하면서 생성한다면 다양한 검증 시나리오 테스트에 용이해질것 같아요

Comment on lines +18 to +19
}
if (StringUtils.length(value) > MAX_LENGTH) {
Copy link

Choose a reason for hiding this comment

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

개행은 가독성을 증가시키는 효과를 가질 수 있어요.

throw new IllegalArgumentException("ID는 0보다 크고 null이 아니어야 합니다. Input ID: " + newId);
}
if (this.id != null) {
throw new IllegalStateException("ID가 이미 할당되어 있습니다. Current ID: " + newId);
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 +58 to +62

private long generateNewId() {
return idGenerator.getAndIncrement();
}

Copy link

Choose a reason for hiding this comment

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

이 메소드는 LocalUrlLinkRepository의 책임에 맞지 않는것 같아요

idGenerator가 generateNewId를 해주면 안될까요?
AtomicLong란걸 알 필요는 없는것 같아요

}
}

throw new UniqueHashCreationException("유니크한 URL 해시를 생성하는 데 실패했습니다.");
Copy link

Choose a reason for hiding this comment

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

유니크한 URL 해시를 생성하는 데 실패했단거는 여기서는 사실 서버쪽의 잘못이라고 볼 수 있어요.
사용자의 잘못이 아니라면, 우리 서버가 잘못한 이부분을 사용자한테 보여줄 필요가 있을까 고민해보면 좋을것 같아요.
재시도 하면 성공할까요?

import com.prgrms.wonu606.shorturl.domain.UrlHash;
import org.springframework.stereotype.Component;

/**
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 +28 to +30

@Test
void whenShortUrlExists_thenRedirect() throws Exception {
Copy link

Choose a reason for hiding this comment

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

DisplayName도 같이 써주면 더 좋을것 같아요!

Comment on lines +30 to +35
@Autowired
MockMvc mockMvc;

@Autowired
ObjectMapper objectMapper;

Copy link

Choose a reason for hiding this comment

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

이 친구들은 왜 default로 하셨는지 궁금합니다.

통일성을 맞춰보면 어떨까여

Comment on lines +22 to +40
@Override
public Long save(UrlLink urlLink) {
validateUrlLink(urlLink);

Long newId = generateNewId();

urlLink.initializeId(newId);

storage.put(newId, urlLink);

hashedUrlIndex.compute(urlLink.getUrlHash(), (key, existingUrlLink) -> {
if (existingUrlLink != null) {
throw new IllegalArgumentException("UrlHash가 이미 인덱싱 되어 있습니다. Current UrlHash: " + urlLink.getUrlHash());
}
return urlLink;
});

return newId;
}
Copy link

Choose a reason for hiding this comment

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

UrlLink가 밖에서 생성 되어서 아쉽게도 initializeId()라는 메소드를 만들었네요 .

 @Override
    public UrlLink save(UrlLink urlLinkToSave) {
        Long newId = generateNewId();

        UrlLink newUrlLink = new UrlLink(newId, urlLinkToSave.getOriginalUrl(), urlLinkToSave.getUrlHash());

        storage.put(newId, newUrlLink);

        hashedUrlIndex.compute(newUrlLink.getUrlHash(), (key, existingUrlLink) -> {
            if (existingUrlLink != null) {
                throw new IllegalArgumentException("UrlHash가 이미 인덱싱 되어 있습니다. Current UrlHash: " + newUrlLink.getUrlHash());
            }
            return newUrlLink;
        });

        return newUrlLink;
    }

이렇게 하면 객체의 불변성을 유지하면서 필요한 정보를 캡슐화할 수 있어요
그러나 몇가지 주의해야해요

  • 메모리 사용량 증가
  • 추가적인 생성자 관리 방법
  • 객체 동일성

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.

영수님이 이미 많은 부분을 리뷰해주셔서 따로 전달할 부분은 보이지 않네요 ㅎ 과제하시느라 고생많으셨어요 ㅎ

)
public interface UrlShortenerApiResponseMapper {

@Mapping(target = "shortenUrl", expression = "java(baseUrl + result.hashedShortUrl())")

Choose a reason for hiding this comment

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

추후 미션으로 expression = "java()" 를 활용하지 않는 방법은 없을까를 고민해보시길 ㅎ

Comment on lines +4 to +8

public record UrlHash(
String value
) {

Choose a reason for hiding this comment

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

hash라는 방식을 활용하지만 굳이 클래스명, 변수명에 적을 필요는 없을듯. (내 알빠는 아니고 무언가 담겨있어. 난 열쇠는 모르지만 열쇠를 아는 놈이 알아서 열든지 말든지 하겄지.)

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