-
Notifications
You must be signed in to change notification settings - Fork 259
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
Spring Boot - Working with third-party API #11
base: main
Are you sure you want to change the base?
Conversation
- Created API has two methods: 1. The request randomly generates a wiki about one character in the universe the animated series Rick & Morty (according the task) 2. The request takes a string as an argument, and returns a list of all characters whose name contains the search string. - All data is loaded from the source: "https://rickandmortyapi.com/api/character/" - Data loading occurs one-time during the launch of the application. - Liquibase with changelog files have been added. - MySQL DB is the main data base. - H2 DB used for tests. - All requests were documented using Swagger. Swagger is available by path: http://localhost:8080/swagger-ui/index.html#/
//private static final CharacterService characterService = | ||
// new CharacterServiceImpl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove commented code
|
||
public static void main(String[] args) { | ||
SpringApplication.run(Application.class, args); | ||
//characterService.saveAll(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
@GetMapping("/download/all") | ||
@Operation(summary = "Download all characters", | ||
description = "Download a list of all available characters from " | ||
+ "https://rickandmortyapi.com/api/character/") | ||
public List<Character> downloadAll() { | ||
return characterService.downloadAllCharacters(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyone who finds this endpoint may spam external api on your behalf. Please do not leave such obvious vulnerabilities in code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, thanks!
@PostConstruct | ||
public void init() { | ||
downloadAllCharacters(); | ||
} | ||
|
||
@Override | ||
public List<Character> downloadAllCharacters() { | ||
HttpClient httpClient = HttpClient.newHttpClient(); | ||
HttpRequest httpRequest = HttpRequest.newBuilder() | ||
.GET() | ||
.uri(URI.create(GET_ALL_CHARACTERS_URL)) | ||
.build(); | ||
CharacterResponseDataDto dataDto; | ||
try { | ||
HttpResponse<String> response = httpClient.send(httpRequest, | ||
HttpResponse.BodyHandlers.ofString()); | ||
dataDto = objectMapper.readValue(response.body(), | ||
CharacterResponseDataDto.class); | ||
} catch (IOException | InterruptedException e) { | ||
throw new RuntimeException(e); | ||
} | ||
List<CharacterResults> characterResults = dataDto.getResults(); | ||
List<CharacterDto> characterDtos = characterResults.stream() | ||
.map(characterMapper::fromApiToDto) | ||
.toList(); | ||
List<Character> characterList = characterDtos.stream() | ||
.map(characterMapper::toModel) | ||
.toList(); | ||
deleteAllCharacters(); | ||
return characterRepository.saveAll(characterList); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please extract this part to separate component, it's should not be part of character service
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I have moved this logic to the separate class...
List<Character> allCharactersFromDb = characterRepository.findAll(); | ||
int maxIdInDb = allCharactersFromDb.size(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not fetch more data from DB than required.
Ok that this data has few hundreds of rows, what would you do if there were few millions, also make list with all of them? :)
List<Character> allCharactersFromDb = characterRepository.findAll(); | |
int maxIdInDb = allCharactersFromDb.size(); | |
int maxIdInDb = characterRepository.count(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of corse))
@Override | ||
public void deleteAllCharacters() { | ||
characterRepository.deleteAll(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks strange as public method, it's used only internally. Please remove from interface and inline in implementation
2. Removed URL from API description 3. Data download logic moved to the separate class. 4. Refactored the logic which calculate first and last id in the DB in findByRandomId method. For this purpose I have been added a new method getMaxIdFromCharacters in the CharacterRepository. 5. Refactored access modifier for deleteAllCharacters - to private.
@GetMapping("/download/all") | ||
@Operation(summary = "Download all characters", | ||
description = "Download a list of all available characters") | ||
public List<Character> downloadAll() { | ||
return characterService.saveAll(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Метод так і не видалено. Навіщо він тобі, якщо ти використовуєш postConstruct? Логіка і так запуститься без ендпоінту взагалі
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я не зрозумів що треба було його видалити... Я навмистно добавив цей метод, щоб коли аплікейшн вже запущено, можна було ще раз перезавантажити контент)) мені здалося що це буде корисно... але зараз видаляю)
@Override | ||
public List<Character> saveAll() { | ||
List<CharacterResults> characterResults = charactersClient.downloadAllCharacters(); | ||
List<Character> characterList = characterResults.stream() | ||
.map(characterMapper::fromApiToDto) | ||
.map(characterMapper::toModel) | ||
.toList(); | ||
deleteAllCharacters(); | ||
return characterRepository.saveAll(characterList); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Наявність методу saveAll() без аргументів дуже спантеличує. Загалом сервіс собі працює з базою і дтошками, а тут чогось йому треба йти кудись на стороннє апі і щось там витягувати
@Override | |
public List<Character> saveAll() { | |
List<CharacterResults> characterResults = charactersClient.downloadAllCharacters(); | |
List<Character> characterList = characterResults.stream() | |
.map(characterMapper::fromApiToDto) | |
.map(characterMapper::toModel) | |
.toList(); | |
deleteAllCharacters(); | |
return characterRepository.saveAll(characterList); | |
} |
import org.springframework.data.domain.Pageable; | ||
|
||
public interface CharacterService { | ||
List<Character> saveAll(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List<Character> saveAll(); |
@Component | ||
@RequiredArgsConstructor | ||
public class CharactersClient { | ||
private final ObjectMapper objectMapper; | ||
|
||
@Value("${mate.academy.get.all.characters.url}") | ||
private String getAllCharactersUrl; | ||
|
||
public List<CharacterResults> downloadAllCharacters() { | ||
HttpClient httpClient = HttpClient.newHttpClient(); | ||
HttpRequest httpRequest = HttpRequest.newBuilder() | ||
.GET() | ||
.uri(URI.create(getAllCharactersUrl)) | ||
.build(); | ||
CharacterResponseDataDto dataDto; | ||
try { | ||
HttpResponse<String> response = httpClient.send(httpRequest, | ||
HttpResponse.BodyHandlers.ofString()); | ||
dataDto = objectMapper.readValue(response.body(), CharacterResponseDataDto.class); | ||
} catch (IOException | InterruptedException e) { | ||
throw new RuntimeException(e); | ||
} | ||
return dataDto.getResults(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Весь код наповнення бази логічніше виглядає тут
@Component | |
@RequiredArgsConstructor | |
public class CharactersClient { | |
private final ObjectMapper objectMapper; | |
@Value("${mate.academy.get.all.characters.url}") | |
private String getAllCharactersUrl; | |
public List<CharacterResults> downloadAllCharacters() { | |
HttpClient httpClient = HttpClient.newHttpClient(); | |
HttpRequest httpRequest = HttpRequest.newBuilder() | |
.GET() | |
.uri(URI.create(getAllCharactersUrl)) | |
.build(); | |
CharacterResponseDataDto dataDto; | |
try { | |
HttpResponse<String> response = httpClient.send(httpRequest, | |
HttpResponse.BodyHandlers.ofString()); | |
dataDto = objectMapper.readValue(response.body(), CharacterResponseDataDto.class); | |
} catch (IOException | InterruptedException e) { | |
throw new RuntimeException(e); | |
} | |
return dataDto.getResults(); | |
} | |
} | |
@Component | |
@RequiredArgsConstructor | |
public class CharactersClient { | |
private final ObjectMapper objectMapper; | |
//add repository | |
@Value("${mate.academy.get.all.characters.url}") | |
private String getAllCharactersUrl; | |
@PostConstruct | |
private List<CharacterResults> downloadAllCharacters() { | |
HttpClient httpClient = HttpClient.newHttpClient(); | |
HttpRequest httpRequest = HttpRequest.newBuilder() | |
.GET() | |
.uri(URI.create(getAllCharactersUrl)) | |
.build(); | |
CharacterResponseDataDto dataDto; | |
try { | |
HttpResponse<String> response = httpClient.send(httpRequest, | |
HttpResponse.BodyHandlers.ofString()); | |
dataDto = objectMapper.readValue(response.body(), CharacterResponseDataDto.class); | |
} catch (IOException | InterruptedException e) { | |
throw new RuntimeException(e); | |
} | |
List<Character> characterList = dataDto.getResults().stream() | |
.map(characterMapper::fromApiToDto) | |
.map(characterMapper::toModel) | |
.toList(); | |
characterRepository.deleteAll(); | |
characterRepository.saveAll(characterList); | |
} | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я зрозумів) Взагалі видаляю метод save в CharacterService і переношу всю логіку по збереженню даних в CharactersClient!
2. Method saveAll and all logic connected with save a new data removed from CharacterService. 3. Class CharactersClient have been refactored: added logic for work with the DB and deleteAll / saveAll methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice
All data is loaded from the source: "https://rickandmortyapi.com/api/character/"
Data loading occurs one-time during the launch of the application.
Liquibase with changelog files have been added.
MySQL DB is the main data base.
H2 DB used for tests.
All requests were documented using Swagger. Swagger is available by path: http://localhost:8080/swagger-ui/index.html#/