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

Spring Boot - Working with third-party API #11

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

sshestaka
Copy link

  • 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.

- 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#/
Comment on lines 8 to 9
//private static final CharacterService characterService =
// new CharacterServiceImpl();

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();

Choose a reason for hiding this comment

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

same

Comment on lines 40 to 46
@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();
}

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

Copy link
Author

Choose a reason for hiding this comment

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

OK, thanks!

Comment on lines 33 to 63
@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);
}

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

Copy link
Author

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...

Comment on lines 67 to 68
List<Character> allCharactersFromDb = characterRepository.findAll();
int maxIdInDb = allCharactersFromDb.size();

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? :)

Suggested change
List<Character> allCharactersFromDb = characterRepository.findAll();
int maxIdInDb = allCharactersFromDb.size();
int maxIdInDb = characterRepository.count();

Copy link
Author

Choose a reason for hiding this comment

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

Of corse))

Comment on lines 75 to 78
@Override
public void deleteAllCharacters() {
characterRepository.deleteAll();
}

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.
@sshestaka sshestaka requested a review from olekskov October 23, 2023 14:22
Comment on lines 40 to 45
@GetMapping("/download/all")
@Operation(summary = "Download all characters",
description = "Download a list of all available characters")
public List<Character> downloadAll() {
return characterService.saveAll();
}

Choose a reason for hiding this comment

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

Метод так і не видалено. Навіщо він тобі, якщо ти використовуєш postConstruct? Логіка і так запуститься без ендпоінту взагалі

Copy link
Author

Choose a reason for hiding this comment

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

Я не зрозумів що треба було його видалити... Я навмистно добавив цей метод, щоб коли аплікейшн вже запущено, можна було ще раз перезавантажити контент)) мені здалося що це буде корисно... але зараз видаляю)

Comment on lines 34 to 43
@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);
}

Choose a reason for hiding this comment

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

Наявність методу saveAll() без аргументів дуже спантеличує. Загалом сервіс собі працює з базою і дтошками, а тут чогось йому треба йти кудись на стороннє апі і щось там витягувати

Suggested change
@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();

Choose a reason for hiding this comment

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

Suggested change
List<Character> saveAll();

Comment on lines 16 to 40
@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();
}
}

Choose a reason for hiding this comment

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

Весь код наповнення бази логічніше виглядає тут

Suggested change
@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);
}
}

Copy link
Author

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.
@sshestaka sshestaka requested a review from olekskov October 24, 2023 13:12
Copy link

@Ivan95kos Ivan95kos left a comment

Choose a reason for hiding this comment

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

Nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants