일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- YouTube Data API
- coarse-grained
- nestjs library
- API 설계
- API 개발
- NestJS
- monorepo
- 어드민 페이지
- typeorm
- 자바
- 멀티테넌시
- nestjs libraries
- jest
- 티스토리챌린지
- SW마에스트로
- 권한검증
- mailerservice
- SROOM
- 추상 클래스
- java
- 오브젝트
- 파일조회
- Connection pool
- nestjs decorator
- 책임부과
- 셀렉트어드민
- fine-grained
- 오블완
- guard
- Mock
- Today
- Total
독산구너
Youtube Data API 사용기 - 추상클래스 사용해서 url 생성 본문
SROOM 프로젝트에서는 다음 화면과 같이 youtube 에 있는 영상, 재생목록을 키워드로 검색하고, 상세정보를 불러와 학습 코스에 등록하는 기능이 존재합니다.
이때 정보를 불러오기 위해 Youtube Data API 를 사용하는데, 이곳에서 제공하는 api는 다음과 같습니다.
출처 : https://developers.google.com/youtube/v3/docs
[search] -> 키워드 검색결과를 가져오는 api 입니다.
GET https://www.googleapis.com/youtube/v3/search
이때, 쿼리 파라미터로 여러 값이 입력될 수 있습니다. (이외에도 여러 파라미터가 존재합니다)
- q : 검색된 키워드
- part : 필요한 정보 (snippet, id, kind ... )
- order : 검색결과 연관성 (date, rating, title ...)
- pageToken : 다음 페이지 또는 이전 페이지를 불러오기 위해 제공된 토큰
- type : 검색 결과 타입 (channel, playlist, video)
- maxResults : 검색 결과 리스트의 최대 개수
[video] -> 영상에 대한 상세정보를 가져오는 api 입니다.
GET https://www.googleapis.com/youtube/v3/videos
- id : 영상 id
- part : 필요한 정보 (snippet, id, kind, contentDetails, statistics ... )
- field : part에서 중복된 정보를 피하기 위해 상세 정보 지정 ex) id,snippet(publishedAt,title,description,thumbnails,channelTitle,defaultAudioLanguage),contentDetails(duration,dimension)
[playlist] -> 재생목록에 대한 상세정보를 가져오는 api 입니다.
GET https://www.googleapis.com/youtube/v3/playlists
- id : 재생목록 id
- part : 필요한 정보 (snippet, id, kind, contentDetails, status ... )
- channelId : 채널 id
- field
[playlistItem] -> 재생목록 안의 영상들에 대한 정보를 리스트로 받아오는 api 입니다.
GET https://www.googleapis.com/youtube/v3/playlistItems
- id : 재생록록 id
- part
- maxResults : 리스트로 받아올 영상 최대개수. 최대 50개
- pageToken : 다음 리스트를 받아오기 위한 토큰
다른 여러 api 들이 존재하지만, 스룸 프로젝트에서는 위 4개의 api 만을 사용합니다.
자바 스프링 서버에서는 사용자의 요청에 맞게 파라미터를 조합시켜 api를 호출하고, 결과를 받아와야 합니다.
다음과 같은 코드로 url을 생성할 수 있습니다.
public String createSearchUrl(String keyword, int limit, String nextPageToken, String prevPageToken) {
String url = "https://www.googleapis.com/youtube/v3/search?";
String pageTokenOrNull = chooseTokenOrNull(nextPageToken, prevPageToken);
String partQuery = "part=id,snippet";
String fieldsQuery = "&fields=nextPageToken,prevPageToken,pageInfo,items(id,snippet(title,channelTitle,thumbnails,description,publishTime))";
String maxResultsQuery = "&maxResults=".concat(String.valueOf(limit));
String apikeyQuery = "&key=".concat(Secret.getGoogleCloudApiKey());
String typeQuery = "&type=playlist,video";
String qQuery = "&q=".concat(keyword);
url = url.concat(partQuery).concat(fieldsQuery).concat(maxResultsQuery).concat(typeQuery).concat(apikeyQuery).concat(qQuery);
if (pageTokenOrNull != null) {
String pageTokenQuery = "&pageToken=".concat(pageTokenOrNull);
url = url.concat(pageTokenQuery);
}
log.info("youtube request uri:" + url);
return url;
}
private String createVideoDetailURl(String lectureId) {
String url = "https://www.googleapis.com/youtube/v3/videos?";
String partQuery = "part=snippet,contentDetails,statistics,status";
String fieldsQuery = "&fields=pageInfo(totalResults),items(id,snippet(publishedAt,title,description,thumbnails,channelTitle,defaultAudioLanguage),contentDetails(duration,dimension),status(uploadStatus,embeddable),statistics(viewCount))";
String lectureIdQuery = "&id=".concat(lectureId);
String keyQuery = "&key=".concat(Secret.getGoogleCloudApiKey());
url = url.concat(partQuery).concat(fieldsQuery).concat(lectureIdQuery).concat(keyQuery);
return url;
}
private String createPlaylistDetailUrl(String lectureId) {
String url = "https://www.googleapis.com/youtube/v3/playlists?";
String partQuery = "part=id,snippet,status,contentDetails";
String fieldsQuery = "&fields=pageInfo,items(id,snippet(publishedAt,title,description,thumbnails,channelTitle),status,contentDetails)";
String lectureIdQuery = "&id=".concat(lectureId);
String keyQuery = "&key=".concat(Secret.getGoogleCloudApiKey());
url = url.concat(partQuery).concat(fieldsQuery).concat(lectureIdQuery).concat(keyQuery);
return url;
}
private String createPlaylistItems(String lectureId, int limit) {
String url = "https://www.googleapis.com/youtube/v3/playlistItems?";
String partQuery = "part=snippet,status";
String fieldsQuery = "&fields=pageInfo,nextPageToken,prevPageToken,items(snippet(title,position,resourceId,thumbnails),status)";
String maxResultsQuery = "&maxResults=".concat(String.valueOf(limit));
String playlistIdQuery = "&playlistId=".concat(lectureId);
String keyQuery = "&key=".concat(Secret.getGoogleCloudApiKey());
url = url.concat(partQuery).concat(fieldsQuery).concat(maxResultsQuery).concat(playlistIdQuery).concat(keyQuery);
return url;
}
하지만 이 메서드에는 다음과 같은 문제점이 있습니다.
1. video, search, playlist, playlistItem에서 동일한 코드가 반복된다.
2. limit, pageToken 등 파라미터를 요청하지 않을 때도 메서드에 null 등으로 전달해야 한다.
3. 모두 같은 형태를 띄고있는 같은 api이고, endpoint만 다를 뿐인데 서로 분리되어 있다.
이 문제점들을 해결하기 위해 파라미터를 전달해 요청 객체를 만들 수 있도록 추상클래스를 만들고, builder를 사용하기로 하였습니다.
또한 "https://www.googleapis.com/youtube/v3/"는 yml 파일로 빼놓고, api별 고정되어 있는 part, field 파라미터등은 상수화 하였습니다.
변화된 코드는 다음과 같습니다.
추상클래스 YoutubeReq에서는 api별 endPoint와 파라미터를 받아 Map 타입으로 리턴하는 함수를 정의하였습니다.
public abstract class YoutubeReq {
protected String endPoint;
public abstract Map<String, String> getParameters();
public String getEndPoint() {
return endPoint;
}
}
YoutubeReq를 상속받는 클래스는 다음과 같습니다.
@Builder
public class SearchReq extends YoutubeReq {
private final String keyword;
private final int limit;
private final String filter;
private final String pageToken;
private final String type;
{
endPoint = "/search";
}
@Override
public Map<String, String> getParameters() {
Map<String, String> params = new HashMap<>(YoutubeUtil.LECTURE_LIST_PARAMETERS);
params.put("maxResults", String.valueOf(limit));
if (filter.equals("all")) {
params.put("type", "playlist,video");
} else {
params.put("type", filter);
}
params.put("q", keyword);
if (pageToken != null) {
params.put("pageToken", pageToken);
}
return params;
}
}
@Builder
public class VideoReq extends YoutubeReq {
private final String videoCode;
{
endPoint = "/videos";
}
@Override
public Map<String, String> getParameters() {
Map<String, String> params = new HashMap<>(YoutubeUtil.VIDEO_PARAMETERS);
params.put("id", videoCode);
return params;
}
}
@Builder
public class PlaylistReq extends YoutubeReq {
private final String playlistCode;
{
endPoint = "/playlists";
}
@Override
public Map<String, String> getParameters() {
Map<String, String> params = new HashMap<>(YoutubeUtil.PLAYLIST_PARAMETERS);
params.put("id", playlistCode);
return params;
}
}
@Builder
public class PlaylistItemReq extends YoutubeReq {
private final String playlistCode;
private final String nextPageToken;
private final int limit;
{
endPoint = "/playlistItems";
}
@Override
public Map<String, String> getParameters() {
Map<String, String> params = new HashMap<>(YoutubeUtil.PLAYLIST_ITEMS_PARAMETERS);
params.put("playlistId", playlistCode);
params.put("maxResults", String.valueOf(limit));
if (nextPageToken != null) {
params.put("pageToken", nextPageToken);
}
return params;
}
}
이때 endPoint를 각 클래스별로 재정의하기 위해 인스턴스 초기화 블록을 사용하였습니다.
또한 어떠한 값을 Youtube 에서 가져올지 결정하는 part, field 파라미터는 YoutubeUtil 클래스에 상수로 정의하여 사용합니다.
이렇게 코드를 깔끔하게 바꿀 수 있었습니다.
다음은 리팩토링 된 메서드를 사용해 youtube data api를 요청하는 예시입니다
이때, 자바의 여러 http 요청 library 중 web client를 사용해 요청하였습니다.
httpUrlConnection, Okhttp 등을 사용할 수 있는데, 이에 대한 내용은 resultClass 사용 등 자세한 요청 로직과 함께 다음 글에서 작성하도록 하겠습니다.
public <T> Mono<T> getYoutubeVo(YoutubeReq req, Class<T> resultClass) {
return this.webClient
.get()
.uri(uriBuilder -> {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(baseUrl)
.path(req.getEndPoint())
.queryParam("key", googleCloudApiKey);
req.getParameters().forEach(uriComponentsBuilder::queryParam);
return uriComponentsBuilder.build().toUri();
})
.retrieve()
.bodyToMono(resultClass);
}
여기서 baseUrl은 yml 파일에 정의되어 있는 "https://www.googleapis.com/youtube/v3" 이고, api 요청을 위한 googleCloudApiKey도 secure.properties에 숨겨져 있습니다.
'프로젝트 > [SW마에스트로] SROOM' 카테고리의 다른 글
Dto, Vo, Entity의 차이점과 쓰임에 대해서 (4) | 2023.10.24 |
---|---|
데이터 중심 설계에서 책임 주도 설계로 ('오브젝트'를 읽고) -2 책임 주도 설계 과정(강의등록) (3) | 2023.10.15 |
데이터 중심 설계에서 책임 주도 설계로 ('오브젝트'를 읽고) -1 기존 프로젝트 문제점 발견 (0) | 2023.10.15 |
Youtube Data API 사용기 - 추상클래스 이용 (0) | 2023.08.21 |
[Agile] Jira, Confluence 사용기 - 스프린트 플래닝 (0) | 2023.08.14 |