Kilka pytań dotyczących pracy z API

0

Cześć! Uczę się pracować z API i w związku z tym mam parę pytań.
Chciałbym napisać aplikację która wskazuje drużyny najczęściej remisują w swojej lidze. W tym celu znalazłem fajne api które udostępnia historię rozgrywek kilku lig.
Do obsługi requestów wykorzystuję zestaw bibliotek uniresta a jako odpowiedź dostaję jsona w takim formacie jak pod tym linkiem

  1. Jak w takim wypadku w mądry sposób stworzyć obiekt który będzie przechowywał wszystkie mecze? Jestem kompletnie zielony, pierwszy raz mam styczność z takim rzutowaniem.
    Na moje rozumowanie powinienem taką odpowiedź parsować na obiekt API który to miałby z kolei tablicę obiektów Fixtures a ta miałaby pole "id" i znów tablicę obiektów Details gdzie dopiero tutaj otrzymywałbym interesujące mnie dane. Czy to naprawdę musi być tak zagmatwane? W jaki sposób to najbardziej uprościć?
    Czy może łatwiej byłoby użyć mongo i tam flirtować sobie dane?

  2. W odpowiedzi tablica fixtures ma mnóstwo nieinteresujących mnie pól, czy jest jakiś sposób żeby wskazać te które miałyby zostać do mnie dostarczone? I czy skróci to czas oczekiwania na odpowiedź?

Uff, ile mi zajęło żeby przelać problem na słowa!

1
  1. Użyj jakieś biblioteki, która parsuje jsony na obiekty POJO. Tutaj masz 3 przykłady: http://www.java67.com/2016/10/3-ways-to-convert-string-to-json-object-in-java.html Potem już musisz sobie odpowiednio wyciągnąć dane jakie Cię interesują

  2. Jest coś takiego jak GraphQL, który pozwala na to aby requestować tylko o te dane, które Cię interesują. Jednakże to jest zależne od implementacji serwera. Jeśli serwer zwraca Ci to co wysłałeś to nie masz możliwości pobrania mniejszej ilości danych. Możesz jeszcze przejrzeć czy nie ma jakieś metody API, która zwraca mniej kompleksowe dane. Ostatecznie podczas parsowania jsona do POJO możesz olać parę pól, ale to nie wpłynie na wydajność sieciową.

1

Tego jsona możesz skonwertowac do pojo przy pomocy http://www.jsonschema2pojo.org (On pewnie jest za duży do konwersji online - offline pewnie da radę). Potem dane możesz prosto pobrać przy pomocy https://square.github.io/retrofit/ + gson.

0

super! dzięki Wam za naprowadzenie. :) zabieram się do lektury.

0

Przyjrzałem się temu JSONowi z bliska (na laptopie) Czy tam przypadkiem klucze są dynamiczne np

{
  "api": {
    "results": 380,
    "fixtures": {
      "65": {
        "fixture_id": "65",
        "event_timestamp": "1533927600",
        "event_date": "2018-08-10T19:00:00+00:00",
        "league_id": "2",
    	[...] 
      },
      "66": {
        "fixture_id": "66",

? Jeśli tak to konwersja nie będzie taka trywialna.

0

@lubie_programowac: właściwie to tych dynamicznych nie będę w ogóle ruszał także raczej nie ma problemu.

Co do tematu:przy pomocy Gsona udało mi się deserializować dane na POJO, zmieniłem jednak api z którego korzystałem (klik dla podglądu). Przy poprzednim musiałbym kombinować żeby zliczyć wszystkie remisy każdej drużyny, teraz natomiast mam od razu udostępnianą całą tabelę ligi.

Dalsza część postu dotyczy tego, że strasznie nie podoba mi się mój kod. Jestem pewny, że modyfikatory dostępu są całkowicie kosmiczne zresztą jak cała składnia tej klasy. Jednak wszystko działa więc jestem z siebie częściowo zadowolony. Czy mógłby mi ktoś dać kilka wskazówek jak uporządkować ten kod?

Także JSON którego tak naprawdę bym potrzebował wygląda tak:

{
	"competition" : { 
		"name" : "Premier League"
	},
	"standings" : [
	{
		"table" : [
			"team" : {
				"name" : "Manchester City" 
			},
			"playedGames" : 2,
			"draw" : 0
		]
	}]
}

W celu serializacji stworzyłem taki obiekt:

package pl.sienkiewicz.utils;

import java.util.List;

public class JSONResult {
	
	private Competition competition;
	private List<Standings> standings;
	
	private class Competition {
		private String name;
	}
	
	public class Standings {
		
		private List<TeamPosition> table;
		private String type;
		
		private class TeamPosition{
			Team team;
			String playedGames; 
			String draw;			
			
			private class Team{
				String name;
			}
			
			public String getTeamName() {return team.name;}
		}
		
		public void printStandings() {
			if(type.equalsIgnoreCase("total"))
			for(TeamPosition team : table) {
				System.out.println(type + " " + team.getTeamName() + " games: " + team.playedGames + " draws: " + team.draw);
			}
		}
	}
	
	public List<Standings> getStandings(){
		return standings;
	}

}

0

@Edit
Zmieniłem wszystkie modyfikatory na prywatne - oprócz tych metod które wywołuje z innego pakietu - i dodałem gettery w miejscach gdzie wcześniej odwoływałem się bezpośrednio do pól.

Mam w zasadzie jeszcze jedno pytanie. W klasieTeamPosition chciałbym mieć jeszcze jedno pole jakim jest średnia remisów w stosunku do wszystkich meczy. Tylko nie bardzo wiem w jakim momencie powinienem był obliczyć tą wartość. Próbowałem w konstruktorze ale to nic nie dało. Chciałem sprawdzić czy on się w ogóle wywołuje ale wygląda na to, że nie. Jak to z nim jest w tym przypadku?

1

EDIT: Przeczytaj koniec mojego posta i rozważ moją propozycję. Jeśli tak zrobisz to nie ma co przesadnie refaktorować tych klas.

  1. Porozdzielaj te klasy do osobnych plików java. Klas prywatnych owszem, używa się np wtedy gdy klasa wykorzystana będzie tylko w tym miejscu i jest ściśle powiązana z klasą parent. Skoro uczysz się programować to lepiej i łatwiej będzie to trzymać w osobnych plikach. Ty zrobiłeś poziom zagnieżdżenia tych klas równy aż 4:D Czy to wymusza na Tobie GSON?

  2. Jak poprawisz punkt 1. to twój program będzie wyglądał znacznie czytelniej. Zadbaj o modyfikatory dostępu i wyślij kod jeszcze raz, zobaczymy co jeszcze można poprawić.

  3. Do pól TeamPosition ciągle odwołujesz się bezpośrednio

  4. po ifie daj {} to poprawi czytelność kodu.

  5. Wszystko u Ciebie jest Stringami. Draw i playedGames lepiej by było intami.

Co do Twojego pytania to ja bym dodał nową metodę obok printStandings() która to liczy i zwraca. Ewentualnie może to liczyć i zapisywać do jakiegoś pola, żeby tego nie trzeba było liczyć za każdym razem.

Często jest tak, że obiekty, które powstają po deserializacji obiektów z API są bardzo dziwne i nieintuicyjne. Może byłoby lepiej jakbyś faktycznie odczytał całego jsona (z wszystkimi danymi) na POJO, i traktował to jako coś niezbyt czytelnego. Potem byś sobie zrobił klasę konwertującą, która przepisze ci te POJO na kolejną warstwę POJO, ale tym razem przygotujesz już sobie obiekty, które będą ładne i użyteczne dla twojej aplikacji. Bez jakiś zagnieżdzanych klas, z wyliczoną średnią, z ładnym toString(). To by była dobra lekcja

0

@Berylo: dzięki za wartościowe porady. Zrobiłem tak jak mi poradziłeś i teraz faktycznie pracuje mi się znacznie łatwiej. Właściwie to teraz kod jest bardziej czytelniejszy aniżeli wcześniej.
Kodu jest sporo a że nie chcę wklejać tylu klas to wrzuciłem projekt na githuba (klik) Właściwie teraz to już nie wiem o co pytać bo na ten moment działa wszystko jak powinno i czuję, że mam nad tym kontrolę. Teraz zauważyłem że mam mnóstwo importów w Controllerze których w ogóle nie używam - ale to dlatego że dla testów wszystko właśnie tam trzymałem.

Dalej sobie będę powoli rozwijać aplikacje ale jeśli ktoś ma jakieś uwagi czy też pomysły co mógłbym zrobić żeby wynieść z tego pożyteczną lekcję to byłbym wdzięczny za odzew!

0

Cieszę się, że mogłem pomóc. Mam jeszcze parę uwag.

  1. Nie wrzucaj wszystkiego na repo. Target, build czy pliki/foldery projektu zaczynające się od . nie powinny być na githubie
  2. APIService, PredictionerService dopisz java doc
  3. callAPI wygląda na to że nie ma sensu podawać key za każdym razem skoro masz to zdefiniowane w APIServiceImpl. Mogłoby to być private static final
  4. formatuj kod. W eclipsie to jest ctlr+shift+F. W intelliJ nie pamietam. Czasami brakuje wcięć
  5. nieużywane importy wyrzucaj, nie ma potrzeby ich trzymac. Ctrl+shift+o da eclipsa
  6. PredictionerServiceImpl, tak samo, private static final jeśli to jest twój stały zbiór lig. Fajnie jakbyś mogł zrobić to konfigurowalne w jakimś .properties (to samo z punktem 3.) Nie widzę sensu żeby to trzymać w taki sposób i zwracać geterem. To jest stała więc zrób to static final i daj do interfejsu albo lepiej do jakiegos pliku .properites
  7. metoda printTeams w PredictionerServiceImpl nie ma sensu. W ogóle PredictionerServiceImpl nie ma sensu bo ten service nic nie robi użytecznego.
  8. TeamUtils nie powinno trzymać stanu. Tutaj mają być tylko metody pomocnicze. Wrzucasz coś i dostajesz wynik. Jak chcesz sobie zrobić takie miejsce do zapisywania do zrób jakieś TeamInMemoryRepository i będziesz miał taką pseudo bazę danych w pamięci. Zainicjuj to tylko gdzieś na początku i wstrzykuj gdzie chcesz. (wtedy już nie statici)
  9. Jak ustatlilismy te metode wywalamy getListToCalculatePrediction ale chciałem nawiązać do jej nazwy. Nie możesz nazywać metody w taki sposób. To jest tak naprawdę getter. A jakbym chciał pobrać sobie tę listę żeby zrobić coś innego niż caluclatePrediction to co wtedy? Nazwa metody powinna mówić co ta metoda robi, a nie do czego powinno być użyte to co zwraca.
  10. CallApi nie jest nigdzie używane poza servicem? Jeśli tak to może być private.
  11. W związku z powyższymi punktami getLeagueFixturesDetails nie musi mieć parametru
  12. GameStats i TeamDetails wydają się posiadać osobno te same dane. Zerknij na to czy jest sens trzymać obie klasy, albo czy TeamDetails nie powinno lepiej korzystać z GameStats. Nie znam tego API ani tego co chcesz zrobić więc mogę się mylić. Po prostu przypatrz się czy jest OK.
0

Ekstra! Strasznie mi pomogły Twoje uwagi i sporo się nauczyłem próbując je wprowadzić w projekt. Chętnie bym Ci postawił piwo zamiast pustego 'dziękuję':D
Najbardziej jestem sceptyczny co do tych propertisów. Mam wrażenie że zrobiłem to w sposób w jaki nie powinno się tego robić, wygląda okropnie i wydaje się być strasznie kłopotliwe.
Nie chcę zakładać nowego tematu a mam jeszcze jedno, ostatnie pytanie: w jaki sposób zamiast wyświetlać informacje w konsoli poprzez PrinterService, przesłać je do widoku?
Podsyłam jeszcze raz linka do gita

1
  1. Co do propertisów to nie ma tragedii. Może lepiej wczytać je raz przy starcie aplikacji, a nie za każdym razem gdy się do nich odwołujesz. Operacje na plikach są wolne i powinno się ich unikać w runtime jeśli to możliwe. Tutaj masz przykład jak gość obsłużył propertisy, możesz troszkę podrasować swoje rozwiązanie: https://www.baeldung.com/java-properties

  2. Koniecznie zmień property na listę. np league.codes=BL1,PL... wczytaj to jako String i zrób split po przecinku. W tym momencie jakbyś chciał do propertisów dodać np api-key to już nie możesz bo wszystko wrzuciłeś bez kluczy. W plikach properties powinno się trzymać klucz=wartość.

  3. Co do twojego pytania o prezentacje danych to do tego służy Twój kontroler. zamiast "Check console for team details!"; zwróć to co teraz drukujesz w metodzie printerService.printTeamDetails(matchType)

0

Właśnie nie podobało mi się to, że trzymam same klucze w propertisach a nie wiedziałem jak zawrzeć tam jakąś listę. Nie przyszłoby mi do głowy żeby to splitować po przecinkach. Zaraz zabieram się za poprawę kodu. Dzięki!
A jeśli chodzi o prezentację danych to moim zamiarem nie jest wyświetlenie tego po prostu jako plain text a zwrócenie całej, wystylizowanej strony z tymi danymi. W jaki sposób powinienem do tego podejść? I czy w ogóle warto zahaczać o jakiegoś angulara i ogólnie front, celując w przyszłości w pracę na backendzie?

0

Hmm, no w sumie jakieś podstawowe pojęcie o froncie warto mieć ale jak celujesz w backend i dopiero się uczysz javy to nie mieszałbym sobie w głowie angularem czy js na Twoim miejscu. Ja też jestem backendowiec i nie znam angulara. Z czasem zobaczysz, że wszystkiego można się łatwo i szybko nauczyć jeśli trzeba jak nabierzesz doświadczenia.

Jeśli zależy Ci na tym, żeby jakoś prezentować dane to możesz zerknąć na jakiś podstawowy tutorial jakiegoś frontendowego frameworka. Ale tutaj musi doradzić Ci jakiś frontendowiec albo fullstack, ja się nie znam.

1 użytkowników online, w tym zalogowanych: 0, gości: 1