👋 Herzlich Willkommen zu meiner Check24-GenDev Best Combination Lösung!
- Demo / Deployment / Run Locally
- Systemarchitektur
- Funktionalitäten
- Greedy Algorithmus
- Codequalität & Performance
- Mögliche Verbesserungen
- Dankeschön
📌 Deployment Frontend: https://best-combination-pwa.web.app/ (the initial loading time can take some seconds as backend is deployed on "free instance"
📌 Lokal starten: Backend:
npm run start:backendund dann Frontend:
npm run start:frontend- Installiert alle Dependencies
- Startet Backend und Frontend (BE:
localhost:8080, FE:localhost:3009)
| Kategorie | Technologie |
|---|---|
| Programmiersprache | TypeScript |
| Laufzeitumgebung | Node.js |
| Framework | Express.js |
| Datenbank | Google Firebase Firestore |
| Caching | node-cache |
| Deployment | Google Cloud Platform |
Das Backend bietet eine RESTful API und wurde mit Node.js und Express umgesetzt. Durch TypeScript wird Typensicherheit und bessere Wartbarkeit erreicht. Folgende Endpunkte sind verfügbar:
/api/gamesGET: Gibt die Games zurück/api/streamingofferGET: Gibt die Streamingangebote zurück/api/streamingpackageGET: Gibt die Providerinformationen zurück
- NoSQL-Datenbank: Google Firebase Firestore
- Import: Daten wurden von
csvnachjsonkonvertiert und in Collections abgelegt. - Vorteile: Flexible Datenspeicherung und keine JOIN-Operationen.
- Nachteile: Joins müssen dann im Frontend durch Typescript gemacht werden.
- Datenabfrage durch das Backend erfolgt jeweils im
backend/servicesOrdner - Es wurde ein Caching-Mechanismus eingeführt, der zum einen die Leseoperationen an die Datenbank deutlich minimiert, zum anderen aber auch die Performance im Frontend erhöht
- Implementiert mit
node-cache, welche die Daten im RAM des Servers speichert, solange der Prozess der spezifischen Serverinstanz aktiv ist (!!!lokal funktioniert es, das deployte Backend nur teilweise, wenn die Serverinstanz aktiv ist!!!) - Für jede Abfrage (games, streamingoffer, streamingpackage) existiert ein "eigener" Nodecache, auf den durch ein unique Schlüssel zugegriffen wird
- Bei der Datenabfrage wird also zunächst der Cache abgefragt, bei Cache-Hit werden die Daten aus dem Cache zurückgegeben, bei Cache-Miss werden die Daten von der DB abgefragt und für nächste Anfragen dann gecached
| Kategorie | Technologie |
|---|---|
| Programmiersprache | TypeScript |
| Framework | React |
| Styling | Material-UI, Styled-components |
| Datenverwaltung | TanStack Query, zustand |
| Hilfsbibliotheken | Day.js, React Window |
| Deployment | Google Firebase |
- TanStack Query / React Query:
- Zur Vereinfachung des Datenmanagements und der Kommunikation mit dem Server, zum Prefetchen der Daten und zum clientseitigen Halten der Daten, die vom Server zurückgegeben werden.
- zustand:
- zum Verwalten des clientseitigen Zustands (wie z.B. selektierte Teams, ausgewählte Filter, usw.), wodurch auch bei reload und bei nächsten Besuchen der Zustand erhalten bleibt.
| Seiten | Pfad |
|---|---|
| Home | / |
| Suche + Favoritenauswahl | /favouriteselection |
| Überblick + Filter | /overviewfilter |
| Ergebnis / Best Combination | /bestcombination |
| Provider-Preis Überblick | /providerprices |
| spez. Spiele finden | /gamefinder |
Alles, was mit 🌟 versehenen ist, stellt ein zusätzliches Feature dar, welches nicht explizit in der Challenge gefordert wurde.
Anzeigen
- Startpunkt der Anwendung
- Attraction Section: Button, um neuen Vergleich zu starten
- 🌟Überblick der Favoriten und der letzten Best Combination wenn vorhanden
- Links zu den Seiten: GameFinder, Provider Prices, Best Combination
Anzeigen
- Suche nach Teams und 🌟Wettbewerben
- 🌟Empfehlungen basierend auf letzten ausgewählten Favoriten + bekannte / beliebte Wettbewerbe (einfach anpassbar :)
- Mit Klick auf die Chips sind die Teams selektiert
- Möglichkeit alle Teams auszuwählen (ihr wollt es ja testen :)) und 🌟alle Favoriten zurückzusetzten
- Sobald Teams/Wettbewerbe ausgewählt sind, können diese auch wieder in der unteren Section entfernt werden.
- 🌟Favoriten bleiben bei Reload erhalten (implementiert mit
zustand)
Anzeigen
- Übersicht aller selektierten Favoriten
- 🌟Möglichkeit, Filter zu setzen (nur zukünftige Spiele berücksichtigen und nur monatl. kündbare Verträge vorschlagen wenn möglich)
- 🌟Filter bleiben bei Reload erhalten durch
zustand - Button „Show Result“ leitet zur Best Combination
Anzeigen
- 5 Hauptteile:
- 🌟Filter: Auswahl Live/Highlight/Default
- Best Providers: Berechnung durch Greedy Algorithmus, Anzeige der für die Selektion beste Provider
- Provider Tabelle: Lazy Loaded, mit Pagination, tabellarischer Überblick welcher Provider welchen Wettbewerb zeigt, sortiert nach Abdeckung, Möglichkeit nur die besten Provider oder alle anderen anzuzeigen
- Wettbewerbe: Abdeckung der ausgewählten Wettbewerbe (Live und highlight), auch hier mit Pagination zur Reduktion der Ladezeit
- 🌟Covered/Uncovered Games: Details zu abgedeckten und nicht abgedeckten Spielen
Anzeigen
- Auswahl eines Wettbewerbs
- Auswahl von Heim- und Auswärtsteam
- Anzeige der Live- und Highlight-Übertragungen
Anzeigen
- Überblick über die Preise der Provider
Für die Berechnung der besten Kombination von Providern, die zusammen möglichst alle Spiele der selektierten Favoriten abdecken sollten wurde in meinem Code der Greedy Algorithmus angewandt. Code: frontend-web\src\services\util\bc\getBestCombi.ts
- Zuvor: Aufruf der Funktion mit Parametern
- Spiele der selektierten Teams
- alle Streamingangebote
- alle Streamingpackages
- mode (Live, Hihglight, Default)
- Spiele der selektierten Teams
- Zuvor (2):
- alle relevanten Spiel-IDs aus der Liste der gefilterten Spielen extrahieren
- Zuordnung Angebote - Spiele: welche Streamingangebote gibt es zu den jeweiligen Spielen im jeweiligen Mode
- Zuordnung Angebote - Streamingpackages: Die einzelnen Angebote werden den jeweiligen Streaming-Paketen ihrer Anbieter zugeordnet.
- Durchführung des Greedy Algorithmus
- Sortieren der Pakete nach Anzahl der von ihnen abgedeckten Spiele in absteigender Reihenfolge, bei gleicher Abdeckung => nach Preis entscheiden
- Bestpackage Auswahl: Das Paket, das die meisten aktuell nicht abgedeckten Spiele enthält wird ausgewählt
- Abdeckung aktualisieren: Die durch das ausgewählte Paket abgedeckten Spiele werden markiert, sodass sie bei zukünftigen Schritten nicht erneut berücksichtigt werden
- Wiederholung, bis entweder alle Spiele abgedeckt sind oder keine weiteren Pakete mehr zur Verfügung stehen
- Nach Anwenden des Algorithmus:
- es wird geprüft, ob es Spiele gibt, die durch kein ausgewähltes Paket abgedeckt werden konnten.
- Die Anbieter, deren Pakete Teil der finalen Lösung sind, werden ausgewählt und nach ihrer Abdeckungsrate sortiert.
Bei dieser Challenge wurde besonders auf die Codequalität geachtet, anders als bei meiner letzten Bewerbung auf das Stipendium.
- TypeScript statt JavaScript: Typsicherheit und besser wartbarer Code
- gleicher Aufbau bei React Komponenten: erst typesDefinition für Props, dann React Code
- ESLint & Prettier: Einhaltung von Code-Standards
Eine gute Performance ist durch einige Überlegungen, sowohl client- als auch serverseitig gewährleistet.
-
Backend-Caching: Wie schon im oberen Abschnitt erwähnt, wurde ein Cachingmechanismus angewandt, um zum einen Anfragen an die Datenbank zu minimieren, zum anderen auch die Antwortszeit an den Client zu verbessern
-
Frontend:
- React Query: Daten-Caching und Prefetching (Anwendung in der gesamten Applikation Frontend): durch die Nutzung von React Query wird ein gutes Caching und ein gute Kommunikationsverhalten zum Server gewährleistet. Konkret wird in App.tsx alle nötigen Daten prefetched und durch die Einstellungen des QueryClients im Cache abgelegt. Es erfolgen also pro Session genau 3 Request, diese Daten werden für die ganze Session gehalten, genauer gesagt 1 Tag (= staleTime, die Zeit, nachdem Daten als veraltet betrachtet werden).
- Lazy Loading: Optimierung der Ladezeit (Anwendung:
best-combination-fullstack-web-app/frontend-web/src/components/result/bestcombination/providerXcompetitonsXgames/ProviderXcompetitionsXgamesTable.tsx). Es basiert darauf,dass Teile der Anwendung nur bei Bedarf geladen werden. Das reduziert die initiale Ladezeit und verbessert die Performance, bei mir im Projekt wird das angewandt, um die initiale Ladezeit zwischen "Überblick/Filter" und dem Ergebnis zu reduzieren. Auf der Route /bestcombination wird BCProvider als Teil des Ergebnis-Displays genutzt. Die Tabelle (ProviderXCompetitionsXgamesTableLazy) wird jedoch erst geladen, wenn der Benutzer sie explizit durch das Accordion öffnet, die Tabelle erhält durch viele Icons eine erhöhte Renderingzeit. - React Window: Virtuelles Scrollen großer Listen (Anwendung:
best-combination-fullstack-web-app/frontend-web/src/components/result/bestcombination/coverage/BCCoveredGamesModal.tsx): Durch das Konzept der Virtualisierung werden nur die Spiele, die aktuell im sichtbaren Bereich sind, gerendert. Dadurch werden die Anzahl der DOM-Knoten deutlich verringert. - useMemo:
- Styling: Einheitliche Nutzung von Material-UI und styled-components würde den Code besser lesbar machen.
- Lazy Loading: Das Implementieren von Lazy Loading hat zwar einige Vorteile hinsichtlich der Ladezeit gebracht, aber hat den unschönen Nebeneffekt, dass bei vielen selektieren Favoriten die gesamte /bestcombination Route für kurze Zeit nicht bedienbar ist (z.B. kein Klick auf Show Details bei den Covered Games möglich).
- Caching im Backend: Der Einsatz einer Lösung wie Redis, die einen zentralen Cache bietet, hat gegenüber einem "lokalem" Caching mit Node-Cache erhebliche Vorteile in Bezug auf Effizienz.“
- Performance hinsichtlich des Renderings: Aufwendige Styles unnötige geschachtelte HTML-Elemente könnten entfernt werden, um für ein noch schnelleres Ergebnis insbesondere der Tabelle der Bestcombination zu sorgen
Zuletzt möchte ich mich noch bedanken, mir und anderen Studenten die Möglichkeit geben, mit solch großen Datensatzen und "real-world" Applikationen an eigenen Projekten zu arbeiten. Es hat mir sehr viel Spaß gemacht, die Herausforderung anzunehmen. Ich bin selbst Fußballfan und habe Woche für Woche das Problem, den Provider zu finden der das Spiel zeigt, deshalb war es etwas besonderes für mich so etwas selbst zu programmieren. Ich freue mich von Ihnen bald zu hören :)
Bei Fragen, bitte an david.luff03@gmail.com :)
Viele Grüße,
David

