단축 URL 서비스 개발 (1) - 프로젝트 생성, 기능 구현

sookyoung.k·2024년 6월 21일
0

☕Java

목록 보기
11/11
post-thumbnail

한빛미디어 "이것이 취업을 위한 백엔드 개발이다 with 자바" CH.13 실전 과제 테스트 문제 풀이입니다.


🖥️ 과제 테스트 제시

✅ 과제 요구사항 (Shorten-Url-Service)

단축 URL 서비스

요구사항

  1. bitly 같은 단축 URL 서비스를 만들어야 합니다.
  2. 단축된 URL의 키(Key)는 8글자로 생성되어야 합니다. '단축된 URL의 키'는 'https://bit.ly/3onGWgK' 에서 경로(Path)에 해당하는 '3onGWgK'를 의미합니다. bitly에서는 7글자의 키를 사용합니다.
  3. 키 생성 알고리즘은 자유롭게 구현하시면 됩니다.
  4. 단축된 URL로 사용자가 요청하면 원래의 URL로 리다이렉트(Redirect)되어야 합니다.
  5. 원래의 URL로 다시 단축 URL을 생성해도 항상 새로운 단축 URL이 생성되어야 합니다. 이때 기존에 생성되었던 단축 URL도 여전히 동작해야 합니다.
  6. 단축된 URL -> 원본 URL로 리다이렉트 될 때마다 카운트가 증가되어야하고, 해당 정보를 확인할 수 있는 API가 있어야합니다.
  7. 데이터베이스 없이 컬렉션을 활용하여 데이터를 저장해야합니다.
  8. 기능이 정상 동작하는 것을 확인할 수 있는 적절한 테스트 코드가 있어야 합니다.
  9. (선택) 해당 서비스를 사용할 수 있는 UI 페이지를 구현해주세요.

필요 API

경로, HTTP 메서드, 파라미터 전달은 여러분의 의도에 맞게 적절히 구현해주세요.

  1. 단축 URL 생성 API
  2. 단축 URL 리다이렉트 API
  3. 단축 URL 정보 조회 API

사실 처음엔 단축 URL 서비스 자체가 평상시에 생각해본 적이 없던 거여서 이게 뭘까 하고 요구사항을 파악하는데 좀 오래 걸렸었다. 약간 읽기 싫었던 마음도 있음

필요 API같은 경우 제시하지 않는 경우가 많다고 한다. (API 설계부터 평가 요소)
우선은 제시된 내용을 확인하여 문제 풀이를 엿보도록 한다....

✳️ 단축 URL 서비스 살펴보기

bitly 분석하기

유사한 서비스가 있다면 해당 서비스를 참고하는 것이 과제를 이해하는 데 도움이 많이 될 것이다.

현재는 회원 가입을 해야만 서비스를 이용할 수 있다. 근데 구글 계정으로 회원가입을 하려니까 자꾸 Bad Request가 떠서... (아 머야~ 로그인 고쳐주세요~) 그냥 책 내용을 보면서 파악했다.

이런식으로 단축 URL이 생성된다.

👉🏻 7글자로 문자열을 짧게 단축
👉🏻 숫자와 알파벳 대소문자로 구성
👉🏻 숫자 3과 4로 시작한다.

여기서 멈추지 말고! 개발자 도구의 [Network] 탭에서 기록된 HTTP 요청을 모두 확인해보는 것이 좋다.

단축 URL로 요청했을 때 어떤 응답이 돌아오는지 살펴봐야 한다.

과제에 활용해야 할 HTTP 헤더

오 로그인 성공함 ㅎ 그래서 실제로 한 번 단축 URL을 만들어보고 Network 탭 내용을 가져와봤다.

나는 내 벨로그 홈페이지를 단축 URL로 만들었다.

[Headers] 탭을 살펴본다.

  • 'Request URL'로는 우리가 요청한 URL이 들어가 있다.
  • 'Request Method'에는 우리가 URL을 통해 직접 요청한 GET 메서드로 요청되었다.
  • 'Status Code'의 301 상태 코드는 리다이렉트 상태 코드 중에서도 Moved Permanently라는 이름을 가지고 있다. (영구히 이동되었음을 의미한다.)

[Response Heeaders] 항목을 살펴본다.

  • 'Location'에는 원래의 웹 페이지 URL이 적혀 있다. 웹 브라우저에게 이동할 URL을 알려주는 부분이다.

정리하자면
1. 웹 브라우저에서 단축 URL로 접속
2. 단축된 URL에서 상태코드 301 응답과 응답 헤더의 'Location'으로 이동할 페이지의 URL을 알려준다.
3. 웹 브라우저가 해당 URL로 다시 요청하여 원래 웹 페이지로 접속하게 된다.

👩🏻‍💻 문제 풀어보기

문제 풀이가 막막할 수 있다... 문제 읽자마자 막막했음. 책을 따라서 구현해보고 이후에는 스스로 구현해보는 식으로 연습을 해보기로~

🕹️ 프로젝트 생성과 컨트롤러 구현

문제풀이 순서

  1. 요구사항 분석
  2. 프로젝트 생성
  3. 패키지 추가
  4. 데이터 정의
  5. 컨트롤러 추가
  6. DTO 추가
  7. 서비스 코드 추가
  8. 레포지토리 코드 추가
  9. Postman으로 테스트하면서 기능 개발
  10. 테스트 코드 추가

분석할 요구사항을 코드로 옮기기 전에는 잘 와닿지 않을 수 있다. (나처럼...) 그럴 때는 우선 큰 흐름에서 어떤 데이터가 필요한지, 어떤 API가 필요한지 정도만 설계한 후 시작해도 좋다고 한다. 나중에 다시 풀어볼 때 꼭 참고하기로 ㅎㅎ

프로젝트 생성 및 패키지 추가

스프링 이니셜라이저에서 프로젝트를 생성해준다. 우선은 Spring Web과 Validation를 의조성 추가해준 후 생성한다.

그리고 이런 식으로 패키지를 먼저 추가해준다.

데이터 정의

요구사항을 다시 잘 읽어보고 어떠한 데이터가 필요한지 추려본다.

  • 단축된 URL
  • 원래의 URL
  • 리다이렉트 카운트

이 세 가지 데이터가 필요하다.

키 생성 알고리즘은 결국 단축된 URL을 생성하는 것에 대한 내용이기 때문에 따로 데이터로 저장할 필요가 없다.

데이터를 정의했으면 데이터를 저장할 클래스를 추가한다.

ShortenUrl.java

package kr.co.shortenurlservice.domain;

public class ShortenUrl {
    private String originalUrl;		// 원래의 URL
    private String shortenUrlKey;	// 단축된 URL
    private Long redirectCount;		// 리다이렉트 카운트
}

도메인 객체로 다뤄야 할 대상이기 때문에 Domain 패키지에 생성한다.

컨트롤러 추가

ShortenUrlRestController.java

package kr.co.shortenurlservice.presentation;

import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
public class ShortenUrlRestController {

    @RequestMapping(value = "/shortenUrl", method = RequestMethod.POST)
    public ResponseEntity<?> createShortenUrl(
            @Valid @RequestBody ShortenUrlCreateRequestDto shortenUrlCreateRequestDto
            ) {
        return ResponseEntity.ok().body(null);
    }

    @RequestMapping(value = "{shortenUrlKey}", method = RequestMethod.GET)
    public ResponseEntity<?> redirectShortenUrl(
            @PathVariable String shortenUrlKey
    ) {
        return ResponseEntity.ok().body(null);
    }

    @RequestMapping(value = "/shortenUrl/{shortenUrlKey}", method = RequestMethod.GET)
    public ResponseEntity<?> getShortenUrlInformation(
            @PathVariable String shortenUrlKey
    ) {
        return ResponseEntity.ok().body(null);
    }

}

컨트롤러는 presentation 계층에 생성해준다.

우리는 세 가지 API가 필요하다.

  1. 단축 URL 생성 API → /shortenUrl, POST
  2. 단축 URL 리다이렉트 API → /{shortenUrlKey}, GET
  3. 단축 URL 정보 조회 API → /shortenUrl/{shortenUrlKey}, GET

요구사항을 자세히 살펴본다.

'5. 원래의 URL로 다시 단축 URL을 생성해도 항상 새로운 단축 URL이 생성되어야 하고, 이때 기존에 생성되었던 단축 URL도 여전히 동작되어야 한다.'

→ 이는 단축 URL 생성 API는 멱등성이 없어야 한다는 의미이다. 동일한 요청을 보내도 계속 새로운 단축 URL이 생겨야 한다. 이러한 이유가 아니어도 '생성'하는 것이기 때문에 POST 메서드를 사용하겠지만, 이런 점을 유의하면서 다른 문제 풀 때도 잘 분석해봅시다!

우선은 반환값을 모르기 때문에 적당히 코드를 채워둔다.

DTO 추가

단축 URL을 생성하는 API에서 어떤 데이터를 요청받고 응답해야 하는지 생각해본다.

👩🏻‍💻 : '단축할 URL', 즉 원래의 URL을 주세요!

클라이언트는 단축 URL 생성 API에게 단축할 API, 즉 원래의 URL을 전달해야 한다. 그러면 해당 API에서 서비스를 호출하여 단축 URL을 생성하고, 해당 단축 URL을 저장하면 될 것이다. 그리고 생성된 단축 URL을 클라이언트에게 응답으로 보내 주면 된다.

ShortenUrlCreatRequestDto.java

package kr.co.shortenurlservice.presentation;

import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.URL;

public class ShortenUrlCreateRequestDto {
    @NotNull
    @URL(regexp = "[(http(s)?):\\/\\/(www\\.)?a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)")
    private String originalUrl;

    public String getOriginalUrl() {
        return originalUrl;
    }
}

요청에 필요한 DTO를 생성한다. 정규표현식은 직접 입력하는 것보다 복붙하는 것이... 안전...

✓ getter를 추가한 이유는 컨트롤러에서 JSON으로 변환되는 과정에서 필요하기 때문에 추가했다.
✓ originalUrl에 대한 @NotNull 애너테이션 추가
@URL 애너테이션을 사용해 URL에 대한 유효성 검사를 진행할 수 있다.

ShortenUrlCreateResponseDto.java

package kr.co.shortenurlservice.presentation;

import kr.co.shortenurlservice.domain.ShortenUrl;

public class ShortenUrlCreateResponseDto {
    private String originalUrl;
    private String shortenUrlKey;

    public ShortenUrlCreateResponseDto(ShortenUrl shortenUrl) {
        this.originalUrl = shortenUrl.getOriginalUrl();
        this.shortenUrlKey = shortenUrl.getShortenUrlKey();
    }

    public String getOriginalUrl() {
        return originalUrl;
    }

    public String getShortenUrlKey() {
        return shortenUrlKey;
    }
}

응답에 필요한 DTO를 정의한다. 원래의 URL 주소와 단축된 URL 키가 응답으로 필요하기 때문에 정의해준다.

ShortenUrlRestController.java

    @RequestMapping(value = "/shortenUrl", method = RequestMethod.POST)
    public ResponseEntity<ShortenUrlCreateRequestDto> createShortenUrl(
            @Valid @RequestBody ShortenUrlCreateRequestDto shortenUrlCreateRequestDto
            ) {
        return ResponseEntity.ok().body(null);
    }

요청과 응답 DTO를 지정한 뒤라면 컨트롤러에 적절히 반환값을 줄 수 있다.


* 단축 URL 리다이렉트 → shortenUrlKey를 @PathVariable을 통해서 문자열로 받는다.
* 단축 URL 정보 조회 API → shortenUrlKey를 @PathVariable로 받아 shortenUrlKey에 해당하는 단축 URL 정보를 응답으로 준다.

ShortenUrlInformationDto.java

package kr.co.shortenurlservice.presentation;

public class ShortenUrlInformationDto {
    private String originalUrl;
    private String shortenUrlKey;
    private Long redirectCount;

    public String getOriginalUrl() {
        return originalUrl;
    }

    public String getShortenUrlKey() {
        return shortenUrlKey;
    }

    public Long getRedirectCount() {
        return redirectCount;
    }
}

ShortenUrlRestController.java

    @RequestMapping(value = "{shortenUrlKey}", method = RequestMethod.GET)
    public ResponseEntity<?> redirectShortenUrl(
            @PathVariable String shortenUrlKey
    ) {
        return ResponseEntity.ok().body(null);
    }

    @RequestMapping(value = "/shortenUrl/{shortenUrlKey}", method = RequestMethod.GET)
    public ResponseEntity<ShortenUrlInformationDto> getShortenUrlInformation(
            @PathVariable String shortenUrlKey
    ) {
        return ResponseEntity.ok().body(null);
    }

이를 바탕으로 단축 URL 정보가 담긴 DTO를 정의하고 컨트롤러의 반환값을 적절하게 채워준다.

👩🏻‍💻 단축 URL 기능 구현

기능 구현을 위해서 서비스 코드와 레포지토리 코드를 구현해 전체 코드를 완성해준다. 둘은 함께 만들게 되지만 우선 서비스에 어떤 메서드가 필요한지 정의하는 것이 필요하다. 서비스는 컨트롤러에 의해 호출되기 때문에 컨트롤러에 있는 API 별로 어떤 기능이 수행되어야 하는지 미리 정의해야 한다.

단축 URL 생성 기능 추가 - 컨트롤러와 서비스

단축 URL 생성하는 API를 호출할 때 필요한 기능
👉🏻 원래의 URL을 단축 URL로 바꿔주기
👉🏻 그리고 그것을 저장하기

컨트롤러는 URL이 저장된다는 사실을 알 필요가 없다. 때문에 컨트롤러의 입장에서는 단순히 원래의 URL에 해당하는 단축 URL을 생성하는 과정이라고 볼 수 있다.

따라서 그에 걸맞는 이름의 메서드를 만들어주고(generateShortenUrl), 파라미터는 요청 DTO를 받으면 된다.
어플리케이션 계층에 SimpleShortenUrlService 클래스를 추가한다.

SimpleShortenUrlService.java

package kr.co.shortenurlservice.application;

import kr.co.shortenurlservice.domain.ShortenUrlRepository;
import kr.co.shortenurlservice.presentation.ShortenUrlCreateRequestDto;
import kr.co.shortenurlservice.presentation.ShortenUrlCreateResponseDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SimpleShortenUrlService {

    public ShortenUrlCreateResponseDto generateShortenUrl(
            ShortenUrlCreateRequestDto shortenUrlCreateRequestDto
    ) {
        return null;
    }
}

👉🏻 generateShortenUrl() 메서드에서 해야 하는 일
1. 단축 URL 키 생성
2. 원래의 URL과 단축 URL 키를 통해 ShortenUrl 도메인 객체 생성
3. 생성된 ShortenUrl을 레포지토리를 통해 저장
4. ShortenUrl을 ShortenUrlCreateResponseDto로 변환하여 반환

이렇게 써보니까 빡쎄다... 이걸 나 혼자서 다 생각하고 구현해낼 수 있을까? 갑자기 막막... 하지만 연습해야겠죠?

먼저 컨트롤러를 수정해준다.

ShortenUrlRestController.java

package kr.co.shortenurlservice.presentation;

import jakarta.validation.Valid;
import kr.co.shortenurlservice.application.SimpleShortenUrlService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
public class ShortenUrlRestController {

    private SimpleShortenUrlService simpleShortenUrlService;

    @Autowired
    ShortenUrlRestController(SimpleShortenUrlService simpleShortenUrlService) {
        this.simpleShortenUrlService = simpleShortenUrlService;
    }

    @RequestMapping(value = "/shortenUrl", method = RequestMethod.POST)
    public ResponseEntity<ShortenUrlCreateResponseDto> createShortenUrl(
            @Valid @RequestBody ShortenUrlCreateRequestDto shortenUrlCreateRequestDto
    ) {
        ShortenUrlCreateResponseDto shortenUrlCreateResponseDto = simpleShortenUrlService.generateShortenUrl(shortenUrlCreateRequestDto);
        return ResponseEntity.ok(shortenUrlCreateResponseDto);
    }
}

컨트롤러에서 generateShortenUrl()를 호출하도록 코드를 수정한다.

그리고 단축 URL을 생성하는 기능을 수행하는 서비스 메서드를 구현한다.
ShortenUrlRepository.java

package kr.co.shortenurlservice.domain;

public interface ShortenUrlRepository {
}

SimpleShortenUrlService.java

package kr.co.shortenurlservice.application;

import kr.co.shortenurlservice.domain.ShortenUrlRepository;
import kr.co.shortenurlservice.presentation.ShortenUrlCreateRequestDto;
import kr.co.shortenurlservice.presentation.ShortenUrlCreateResponseDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SimpleShortenUrlService {

    private ShortenUrlRepository shortenUrlRepository;

    @Autowired
    SimpleShortenUrlService(ShortenUrlRepository shortenUrlRepository) {
        this.shortenUrlRepository = shortenUrlRepository;
    }

    public ShortenUrlCreateResponseDto generateShortenUrl(
            ShortenUrlCreateRequestDto shortenUrlCreateRequestDto
    ) {
        return null;
    }
}

단축 URL 생성 기능 추가 - 단축 URL 생성 로직

요구사항 - 단축 URL 키는 8글자, 생성 알고리즘은 자유롭게 구현하면 된다.

자유롭되, 논리가 있어야 한다.

아무 문자열이나 사용하기 보다는 어떤 문자열을 단축 URL의 키로 사용하는 것이 적절할지 생각해야 한다.

👉🏻 우리는 Base56이라는 인코딩 방식에 사용되는 문자열을 사용하는 방법으로 진행할 것이다.

ShortenUrl.java

package kr.co.shortenurlservice.domain;

import java.util.Random;

public class ShortenUrl {
    private String originalUrl;
    private String shortenUrlKey;
    private Long redirectCount;

    public static String generateShortenUrlKey() {
        String base56Characters = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz";
        Random random = new Random();
        StringBuilder shortenUrlKey = new StringBuilder();

        for (int count = 0; count < 8; count++) {
            int base56CharactersIndex = random.nextInt(0, base56Characters.length());
            char base56Character = base56Characters.charAt(base56CharactersIndex);
            shortenUrlKey.append(base56Character);
        }
        return shortenUrlKey.toString();
    }
}

문자열 생성 메서드를 ShortenUrl에 추가한다. (도메인 정보니깐...! 끄덕)

  • Base56 인코딩 방식의 문자열 집합을 보면 몇 가지 빠져있다.
    숫자 0, 대문자 O, 소문자 o / 숫자 1, 대문자 I, 소문자 l → 서로 비슷해서 헷갈리기 때문에 제외
  • 반복문을 돌며 Base56 문자열을 하나씩 뽑아서 StringBuilder로 붙이는 과정을 8번 반복한 후 반환한다.

SimpleShorterUrlService.java

@Service
public class SimpleShortenUrlService {
    private ShortenUrlRepository shortenUrlRepository;
    @Autowired
    SimpleShortenUrlService(ShortenUrlRepository shortenUrlRepository) {
        this.shortenUrlRepository = shortenUrlRepository;
    }
    public ShortenUrlCreateResponseDto generateShortenUrl(
            ShortenUrlCreateRequestDto shortenUrlCreateRequestDto
    ) {
        String originalUrl = shortenUrlCreateRequestDto.getOriginalUrl();
        String shortenUrlKey = ShortenUrl.generateShortenUrlKey();

        ShortenUrl shortenUrl = new ShortenUrl(originalUrl, shortenUrlKey);
        shortenUrlRepository.saveShortenUrl(shortenUrl);

        ShortenUrlCreateResponseDto shortenUrlCreateResponseDto = new ShortenUrlCreateResponseDto(shortenUrl);
        return shortenUrlCreateResponseDto;
    }
}

만들어둔 메서드를 활용하여 서비스 코드를 수정한다.

하지만 아직 수정해야 할 부분이 많다. 먼저 ShorterUrl의 문자열 2개를 받는 생성자가 없다. 또한 saveShorterUrl 메서드도 없고, ShortenUrlCreateResponseDto에도 ShorterUrl을 받을 생성자도 없다.

이를 하나씩 차근차근 고쳐본다.

ShorterUrl.java

    public ShortenUrl(String originalUrl, String shortenUrlKey) {
        this.originalUrl = originalUrl;
        this.shortenUrlKey = shortenUrlKey;
        this.redirectCount = 0L;
    }

    public String getShortenUrlKey() {
        return shortenUrlKey;
    }

    public Long getRedirectCount() {
        return redirectCount;
    }

ShortenUrlRepository.java

package kr.co.shortenurlservice.domain;

public interface ShortenUrlRepository {
    void saveShortenUrl(ShortenUrl shortenUrl);
}

ShortenUrlCreateResponseDto.java

package kr.co.shortenurlservice.presentation;

import kr.co.shortenurlservice.domain.ShortenUrl;

public class ShortenUrlCreateResponseDto {
    private String originalUrl;
    private String shortenUrlKey;

    public ShortenUrlCreateResponseDto(ShortenUrl shortenUrl) {
        this.originalUrl = shortenUrl.getOriginalUrl();
        this.shortenUrlKey = shortenUrl.getShortenUrlKey();
    }

    public String getOriginalUrl() {
        return originalUrl;
    }

    public String getShortenUrlKey() {
        return shortenUrlKey;
    }
}

단축 URL 생성 기능 추가 - 생성된 단축 URL 저장 로직

이제 레포지토리 코드를 추가해야 한다! 인터페이스는 이미 추가한 상황이고, 요구사항에서 컬렉션을 활용하라고 했으나 어떤 컬렉션을 활용해야 하는지는 나와있지 않다.

단축 URL 키를 통해 단축 URL을 찾아내기 때문에 MAP이 적당하다. (Collection 인터페이스 밑에는 없지만 일반적으로 컬렉션으로 본다.)

MapShortenUrlRepository.java

package kr.co.shortenurlservice.infrastructure;

import kr.co.shortenurlservice.domain.ShortenUrl;
import kr.co.shortenurlservice.domain.ShortenUrlRepository;
import org.springframework.stereotype.Repository;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Repository
public class MapShortenUrlRepository implements ShortenUrlRepository {
    private Map<String, ShortenUrl> shortenUrls = new ConcurrentHashMap<>();

    @Override
    public  void saveShortenUrl(ShortenUrl shortenUrl) {
        shortenUrls.put(shortenUrl.getShortenUrlKey(), shortenUrl);
    }
}
  • Map을 인터페이스로 ConcurrentHashMap 구현체를 사용한 레포지토리를 추가했다.
  • 저장 시 단축 URL을 Map의 키로 사용하고, 밸류는 ShortUrl 자체를 저장한다.

단축 URL 정보 조회 기능 추가

ShortenUrlRestController.java

    @RequestMapping(value = "/shortenUrl/{shortenUrlKey}", method = RequestMethod.GET)
    public ResponseEntity<ShortenUrlInformationDto> getShortenUrlInformation(
            @PathVariable String shortenUrlKey
    ) {
        ShortenUrlInformationDto shortenUrlInformationDto = simpleShortenUrlService.getShortenUrlInformationByShortenUrlKey(shortenUrlKey);
        return ResponseEntity.ok(shortenUrlInformationDto);
    }

먼저 컨트롤러에 우리가 필요한 메서드를 정의한다. 우리는 단축 URL을 통해서 원래의 URL을 가져와야 한다. 그리고 가져온 원래의 URL을 ShortenUrlInformationDto에 담아 반환한다.

서비스 코드로 가서 getShortenUrlInformationByShortenUrlKey() 메서드를 만들어준다.

SimpleShortenUrlService.java

    public ShortenUrlInformationDto getShortenUrlInformationByShortenUrlKey(String shortenUrlKey) {
        ShortenUrl shortenUrl = shortenUrlRepository.findShortenUrlByShortenUrlKey(shortenUrlKey);

        ShortenUrlInformationDto shortenUrlInformationDto = new ShortenUrlInformationDto(shortenUrl);
        return shortenUrlInformationDto;
    }

단축 URL 키를 통해서 원래의 URL을 가져온다. 그리고 그것을 ShortenUrlInformationDto에 담는다. 이것이 서비스에 필요한 로직이다.

ShortenUrlRepository.java

package kr.co.shortenurlservice.domain;

public interface ShortenUrlRepository {
    void saveShortenUrl(ShortenUrl shortenUrl);
    ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey);
}

ShortenUrlRepository 인터페이스에 findShortenUrlByShortenUrlKey() 메서드를 정의해준다. 단축 URL 키를 인자로 받아서 ShortenUrl에 담아준다.

ShortenUrlInformationDto.java

package kr.co.shortenurlservice.presentation;

import kr.co.shortenurlservice.domain.ShortenUrl;

public class ShortenUrlInformationDto {
    private String originalUrl;
    private String shortenUrlKey;
    private Long redirectCount;

    public ShortenUrlInformationDto(ShortenUrl shortenUrl) {
        this.originalUrl = shortenUrl.getOriginalUrl();
        this.shortenUrlKey = shortenUrl.getShortenUrlKey();
        this.redirectCount = shortenUrl.getRedirectCount();
    }

    public String getOriginalUrl() {
        return originalUrl;
    }
    public String getShortenUrlKey() {
        return shortenUrlKey;
    }
    public Long getRedirectCount() {
        return redirectCount;
    }
}

ShortenUrlInformationDto에는 ShortenUrl을 인자로 받아오는 생성자가 없기 때문에 생성자를 만들어준다. 동시에 필요한 getter 메서드도 만들어준다.

MapShortenUrlRepository.java

	(생략)
    @Override
    public ShortenUrl findShortenUrlByShortenUrlKey(String shortenUrlKey) {
        ShortenUrl shortenUrl = shortenUrls.get(shortenUrlKey);
        return shortenUrl;
    }
}

컨트롤러에서 @PathVariable로 받은 shortUrlKey를 사용해 ShortUrl을 조회하고, 다시 ShortenUrlInformationDto로 변환하여 컨트롤러에게 반환 후 클라이언트에게 응답을 준다.

단축 URL 리다이렉트 기능 추가

사실은 이게 핵심!

요구사항 - 단축된 URL이 원본 URL로 리다이렉트 될 때마다 카운트가 증가해야 하고, 해당 정보를 확인할 수 있어야 한다.

SimpleShortenUrlService.java

    public String getOriginalUrlByShortenUrlKey(String shortenUrlKey) {
        ShortenUrl shortenUrl = shortenUrlRepository.findShortenUrlByShortenUrlKey(shortenUrlKey);

        shortenUrl.increaseRedirectCount();
        shortenUrlRepository.saveShortenUrl(shortenUrl);

        String originalUrl = shortenUrl.getOriginalUrl();

        return originalUrl;
    }

따라서 서비스에 새로운 메서드를 추가한다. getOriginalUrlByShortenUrlKey()는 단축된 URL을 요청받는다.
findShortenUrlByShortenUrlKey()를 통해서 ShortenUrl을 받아오고, 리다이렉트 카운트를 올린다.
그리고 바뀐 ShortenUrl 저장한 후, 원래의 URL을 뽑아서 반환한다.

ShortenUrl.java

    public void increaseRedirectCount() {
        this.redirectCount = this.redirectCount + 1;
    }

리다이렉트 카운트를 올리기 위해 ShortenUrl에 카운트를 증가시키는 increaseRedirectCount() 메서드를 만든다.

ShortenUrlRestController.java

package kr.co.shortenurlservice.presentation;
import jakarta.validation.Valid;
import kr.co.shortenurlservice.application.SimpleShortenUrlService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.net.URI;
import java.net.URISyntaxException;

@RestController
public class ShortenUrlRestController {

	(생략) 
    
    @RequestMapping(value = "{shortenUrlKey}", method = RequestMethod.GET)
    public ResponseEntity<?> redirectShortenUrl(
            @PathVariable String shortenUrlKey
    ) throws URISyntaxException {
        String originalUrl = simpleShortenUrlService.getOriginalUrlByShortenUrlKey(shortenUrlKey);

        URI redirectUri = new URI(originalUrl);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setLocation(redirectUri);

        return new ResponseEntity<>(httpHeaders, HttpStatus.MOVED_PERMANENTLY);
    }

    (생략)
}

리다이렉트의 핵심은 컨트롤러에 있다. @PathVariable을 통해 단축 URL 키를 인자로 받는다.
받은 단축 키를 서비스의 getOriginalUrlByShortenUrlKey()메서드를 호출해 원본 URL을 찾는다.

그 후 로직은 원본 URL을 redirectUri에 저장한 후, HttpHeaders 객체를 생성하여 그 안에 setLocation() 메서드로 Location을 넣어준다.
그리고 반환값에 헤더와 HttpStatus 상태 코드(301)을 넣어주면 기능 추가 완료!


레포지토리에 인터페이스를 추가하고 그걸 상속받고... 서비스 코드에 로직을 구현하는 것이 아직은 어려운 것 같다. 책을 다 끝낸 후 혼자서 구현해볼 수있도록 노력해야겠다. 일단 아는 대로 설명해봄...

profile
영차영차 😎

0개의 댓글