문제1 - 각 리그 국기를 클릭하여, 알맞은 순위표로 변경하기
[문제1 - 해결 방법]
: 자바스크립트를 이용하여, "버튼 클릭 이벤트" 와 "AJAX 비동기 통신" 을 이용하여 해결
문제2 - 불필요하게 너무 많은 API 요청으로 인한 오류 발생
"football-data.org" 에서 "free plan" 은 '분당 10회' 로 API 요청이 제한되어있다.
[문제2 - 해결 방법]
API 요청으로 받은 응답을 Caffeine 캐시를 이용하여 관리한다.
이를 통해 불필요하게 많은 요청을 방지한다.
'build.gradle' dependencies 추가
//Caffeine 캐시
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'com.github.ben-manes.caffeine:caffeine'
'application.yml' 에 캐시 정책 설정
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=100, expireAfterWrite=60s
캐시의 최대 크기를 100개로 제한
쓰기 후 60초(1분) 후 만료되도독 설정
'HomeController' 의 각 순위표 요청 엔드 포인트 마다 캐시명, 예외처리를 설정
//PL Standings
@GetMapping("/standings/pl")
@ResponseBody
@Cacheable(value = "plStandings", unless = "#result == null or #result.isEmpty()")
public List<StandingsDto> getPremierLeagueStandings() throws IOException, InterruptedException {
ResponseEntity<Map> response = getAPIResponse("https://api.football-data.org/v4/competitions/PL/standings");
return getStandingsDtoList(response);
}
//분데스리가, 세리에A, 라리가, 리그앙 도 알맞게 설정
+) 페이지 처음 로드 시 각 리그 버튼 속 국기를 로드 하기 위해 리그 데이터를 API 요청해야 하는 문제
총 5개의 API 요청이 이미 필요함 (캐시 적용 X)
URL이 자주 변하지 않기 때문에 국기 URL을 배열로 미리 저장해 놓는 방식을 채택
문제3 - 페이징한 리그 매치 일정이 View에 정상적으로 출력되지 않는 문제
페이징 전에는 일정이 올바르게 출력이 되었는데, 페이징 하고 난 후 아래와 같이 정상적으로 출력되지 않았다.
리그 매치 일정 API 요청 엔드 포인트는 다음과 같다.
//****** 페이징 이전 엔드 포인트 ******//
//리그 별 매치 일정 검색 (leagueId 기반)
@GetMapping("/matches/league/{leagueId}")
@ResponseBody
@Cacheable(value = "leagueMatchesCache", key = "#leagueId", unless = "#result == null or #result.isEmpty()")
public List<MatchDto> getMatchesByLeagueId(@PathVariable Long leagueId) throws IOException, InterruptedException {
String apiUrl = String.format("https://api.football-data.org/v4/competitions/%d/matches", leagueId);
ResponseEntity<Map> response = getAPIResponse(apiUrl);
return getMatchDtos(response);
}
//****** 페이징 적용 엔드 포인트 ******//
//리그 별 매치 일정 검색 (leagueId 기반)
@GetMapping("/matches/league/{leagueId}")
@ResponseBody
@Cacheable(value = "leagueMatchesCache", key = "#leagueId", unless = "#result == null or #result.isEmpty()")
public Page<MatchDto> getMatchesByLeagueId(@PathVariable Long leagueId, Pageable pageable) throws IOException, InterruptedException {
String apiUrl = String.format("https://api.football-data.org/v4/competitions/%d/matches", leagueId);
ResponseEntity<Map> response = getAPIResponse(apiUrl);
List<MatchDto> matches = getMatchDtos(response);
int start = (int) pageable.getOffset();
int end = Math.min((start + pageable.getPageSize()), matches.size());
PageImpl<MatchDto> matchDtos = new PageImpl<>(matches.subList(start, end), pageable, matches.size());
return matchDtos;
}
페이징 전 엔드포인트의 반환 값 : List<MatchDto>
페이징 적용 후 엔드포인트의 반환 값 : PageImpl<MatchDto>
문제의 원인은 엔드포인트의 반환값이 달라졌기 때문에 야기되었다.
반환값이 달라졌기 때문에, 반환되는 JSON의 양식도 달라졌다.
기존과 달리 "content" 속성이 하나더 감싸고 있다.
전처리 과정에서 "content" 속성으로 한단계 더 들어가는 작업이 필요하다.
문제4 - 엔드포인트 캐싱 로직이 정상적으로 작동하지 않는 문제
[변경 전 팀 스쿼드 엔드 포인트]
@GetMapping("/squad/{teamId}")
@ResponseBody
@Cacheable(value = "squadCache", key = "#teamId", unless = "#result == null or #result.isEmpty()")
public SquadDto getSquadByTeamId(@PathVariable Long teamId) throws IOException, InterruptedException {
String apiUrl = String.format("https://api.football-data.org/v4/teams/%d", teamId);
ResponseEntity<Map> response = getAPIResponse(apiUrl);
return getSquadDto(response);
}
@Cacheable(value = "squadCache", key = "#teamId", unless = "#result == null or #result.isEmpty()")
@Cacheable() 에 변수로
1. value : 데이터를 저장할 캐시 공간 명칭
2. key : 캐시 공간을 구분할 키
3. unless : 캐시에 저장하지 않을 예외 조건
이 세가지를 입력하였다.
세번째, "unless" 로 부터 문제가 발생하였다.
"#result == null" , "#result.isEmpty()" 둘 중 하나라도 True 라면, 캐시에 저장하지 않는다.
"squal/{teamId}" 엔드 포인트는 반환 타입으로 SquadDto 를 갖기 때문에, List 타입이 비어있는지 확인하기 위한 용도 사용되는 ".isEmpty()" 가 적용될 수 없다.
따라서 "#result.isEmpty()" 는 항상 "True" 를 반환하게 된다.
실제로 캐시에 값이 저장되지 않았는데, 저장하지 않는 예외 상황으로 인해서, 값이 저장될 수가 없는 상태이다.
@Cacheable(value = "squadCache", key = "#teamId")
위와 같이 캐싱 로직을 변경하여 해결하였다.
[변경 후 팀 스쿼드 엔드 포인트]
@GetMapping("/squad/{teamId}")
@ResponseBody
@Cacheable(value = "squadCache", key = "#teamId")
public SquadDto getSquadByTeamId(@PathVariable Long teamId) throws IOException, InterruptedException {
String apiUrl = String.format("https://api.football-data.org/v4/teams/%d", teamId);
ResponseEntity<Map> response = getAPIResponse(apiUrl);
return getSquadDto(response);
}
+) 리스트를 반환 값으로 사용하는 엔드 포인트에서 사용되는 캐싱 로직
@GetMapping("/standings/{leagueId}")
@ResponseBody
@Cacheable(value = "standingsCache", key = "#leagueId", unless = "#result == null or #result.isEmpty()")
public List<StandingsDto> getStandingsByLeagueId(@PathVariable Long leagueId) throws IOException, InterruptedException {
String apiUrl = String.format("https://api.football-data.org/v4/competitions/%d/standings", leagueId);
ResponseEntity<Map> response = getAPIResponse(apiUrl);
return getStandingsDtoList(response);
}
리스트를 반환 값으로 하기 때문에 #result.isEmpty() 를 포함한 예외 조건을 적용하였다.
문제5 - 스프링 부트 애플리케이션 에서 " src/main/resources/templates "의 위치에 teamInfo.html 파일을 위치해 놓았는데, 스프링 부트가 찾지 못하는 문제.
- src/main/resources/static/teamInfo.html
- src/main/resources/public/teamInfo.html
- src/main/resources/resources/teamInfo.html
- src/main/resources/META-INF/resources/teamInfo.html
스프링 부트는 정적 콘텐츠를 위 4개의 경로로 조회한다.
" src/main/resources/templates " 위치는 템플릿 엔진(thyleaf 등 ... )을 통해 조회 가능한 위치이다.
스프링 부트가 해당 위치의 teamInfo.html을 조회하기 위해서는, 스프링 MVC 컨트롤러를 구성하여, 직접 이어줘야 한다.
@Controller
@RequiredArgsConstructor
public class TeamController {
@GetMapping("/teamInfo")
public String teamInfo(@RequestParam(name = "teamId") Integer teamId, Model model) {
model.addAttribute("teamId", teamId);
return "teamInfo";
}
}
'PROJECT > 해외 축구 정보 웹서비스' 카테고리의 다른 글
[Football Info] 1. HomeController 분석 (0) | 2024.03.15 |
---|---|
[Football Info] 0. ApiResponse (0) | 2024.03.15 |
[해외축구] 리그 매치 일정 (League Match Schedule) 페이징 하기 (0) | 2024.03.07 |
[해외축구] 1. 개발 환경 설정하기 (0) | 2024.02.19 |
[해외축구] 0. 해외축구정보 웹서비스(가제) 개요 / 기능 설명 (0) | 2024.02.19 |