Pokemanager
Het MongoDB-schema en gerichte refactorings aan de Laravel-backend van een Pokémon-verzamelapp.
Een eigen applicatie, van analyse tot deployment
Voor Full Stack Development binnen de module Advanced Programming bij Avans moesten we met een team van drie studenten een eigen fullstackapplicatie ontwikkelen. We moesten de toepassing analyseren en ontwerpen, vervolgens bouwen en met een teststrategie onderbouwen. Bij de oplevering hoorden ook video’s waarin we het testen en deployen lieten zien. De opdracht ging daarmee over de hele keten, van gebruikersinterface tot gegevensopslag.
De toepassing mochten we zelf kiezen. Het lesmateriaal nam MEAN — MongoDB, Express, Angular en Node.js — als uitgangspunt, maar een andere stack was toegestaan als we die verantwoordden op schaalbaarheid, gangbaarheid en bruikbaarheid. Wij kozen voor Pokemanager: een webapplicatie waarin Pokémon-kaartverzamelaars kaarten en sets moesten kunnen bekijken en hun bezit bijhouden. We werkten dit uit in user stories, usecases en acceptatiecriteria, met Laravel, MongoDB en een React/TypeScript-interface via Inertia als technische opzet.
Mijn werk richtte zich op het MongoDB-schema en de Laravel-backend die de externe kaartcatalogus verwerkt. Ik bouwde mee aan de service en adapter en veranderde hoe die modellen opbouwen en gegevens voor opslag aanbieden. Daarnaast werkte ik aan bestaande React/TypeScript-componenten voor kaartweergave en collectiebeheer, die via Inertia op Laravel aansloten.
-
Persoon
Kaartverzamelaar
Wil kaarten en sets bekijken en eigen bezit bijhouden.
De kaartverzamelaar is de beoogde gebruiker van de catalogus en het collectiebeheer. -
Softwaresysteem · teamproject
Pokemanager
Teamapplicatie voor een catalogus en persoonlijke verzamelingen.
Pokemanager vraagt catalogusgegevens op bij de externe Pokémon TCG API. -
Extern softwaresysteem
Pokémon TCG API
Levert kaart- en setgegevens.
Het datamodel en mijn plek in de backend
De Pokémon TCG API levert gegevens over kaarten en sets. Het aantal exemplaren dat iemand bezit hoort bij de eigen applicatie. Bij het uitwerken van het model kwamen die twee soorten gegevens samen. Ik werkte aan PokemonTcgService, PokemonTcgAdapter, modellen en opslag; mijn teamgenoten werkten ook aan de interface, paginering, caching en de oorspronkelijke collectieservice. Aan de modelimplementatie werkten we met meerdere teamleden.
Ik paste de kaartweergave in CardGrid en CardItem aan en werkte aan de kaartenpagina die Laravel-gegevens via Inertia ontvangt. Op een aanvullende ontwikkelbranch paste ik ook de collectiebediening aan: na een geslaagde mutatie werden de collectiegegevens opnieuw opgevraagd. Dit waren bijdragen aan bestaande componenten. De verschillende branchversies zijn niet als één volledige gebruikersstroom gevalideerd.
Het MongoDB-schema uitwerken
Voor Pokemanager werkte ik het MongoDB-databaseschema uit. Het oorspronkelijke klassendiagram laat zien hoe ik het model uitwerkte: CollectionCard beschrijft een kaart binnen een collectie, met onder meer het aantal exemplaren en een verwijzing naar Card. Die kaart verwijst naar een Set en bevat ingesloten gegevens, zoals aanvallen en afbeeldingen.
Bekijk het oorspronkelijke klassendiagram (SVG).
De taakverdeling binnen Laravel
Laravel levert via Inertia de gegevens voor de React-pagina’s. Binnen Laravel haalt de service de externe catalogus op via de Pokémon TCG software development kit (SDK). De adapter zet de ontvangen gegevens om naar onze modellen. Beide onderdelen draaien in dezelfde applicatie, met de Laravel-integratie voor MongoDB als verbinding naar de opslag.
In het teamontwerp onderbouwden we deze opzet met services en een adapter. Vanwege de schaal van het project wilden we de structuur eenvoudig houden en voegden we geen extra Repository pattern toe, een aparte laag voor datatoegang. Binnen die gezamenlijke opzet werkte ik de grens tussen het opbouwen en opslaan van modellen verder uit.
-
Container · React / TypeScript
Browserapplicatie
Toont kaarten, sets en collectie.
Binnen Pokemanager · softwaresysteem. De browser vraagt pagina’s en Inertia-props op bij Laravel via HTTP. -
Container · PHP / Laravel / Inertia
Laravel-webapplicatie
Verwerkt paginaverzoeken en externe kaartgegevens.
Binnen Pokemanager · softwaresysteem. Laravel leest en schrijft applicatiegegevens in MongoDB via de Laravel-integratie.Laravel vraagt via de Pokémon TCG SDK catalogusgegevens op bij de externe API. -
Container · MongoDB
MongoDB
Opslag voor kaarten, sets en collecties.
Binnen Pokemanager · softwaresysteem. -
Extern softwaresysteem
Pokémon TCG API
Bron voor kaart- en setgegevens.
Modellen opbouwen en daarna opslaan
In de adapter liepen dataomzetting en databasezoekacties nog door elkaar. Een methode die een extern gegeven naar een model vertaalde, kon daardoor ook bestaande gegevens opzoeken. Ik veranderde die verdeling, zodat duidelijker werd welk onderdeel de omzetting verzorgt en welk onderdeel de opslag afhandelt.
Zoekacties uit de adaptermethoden halen
Verschillende adaptermethoden gebruikten firstOrNew: zoek een bestaand model of maak een nieuw modelobject als er geen overeenkomst is. Ik verving deze aanroepen door new en bracht de opslag onder in de service. De betreffende omzettingsmethoden bouwen daarmee een object op uit de aangeleverde gegevens, zonder eerst naar een opgeslagen versie te zoeken.
Een veldmapping is daardoor in de adapter terug te vinden, terwijl de service bepaalt hoe de attributen worden opgeslagen. De adapter blijft wel afhankelijk van Laravel/MongoDB-modellen en adaptSet bevat nog een relatiebewerking. De wijziging maakt de taken op die plekken duidelijker; de hele adapter is daarmee niet onafhankelijk van de opslag geworden.
Omgaan met een kaart die al bestaat
Een nieuw opgebouwd modelobject kan de identifier dragen van een kaart die al in de database staat. De eerdere opslag met save() kon daarbij een fout opleveren. Ik verving die aanroep door een upsert op de modelsleutel en attributen: een bewerking die gegevens kan toevoegen of bijwerken. Daarmee kreeg de service een manier om opnieuw aangeboden kaartgegevens bij een bestaande identifier te verwerken.
Die bewerking vraagt ook om controle van de relaties. Een kaart kan in het geheugen aan andere objecten gekoppeld zijn zonder dat die allemaal worden opgeslagen en na herladen terugkomen. De upsert verwerkt de modelattributen; of de volledige kaart met relaties die ronde doorloopt, moet afzonderlijk worden getest.
-
Container · teamcontext
Browserapplicatie
React en TypeScript.
De browser vraagt kaart- en setpagina’s op bij de controllers via HTTP en Inertia. -
Component · PHP / Laravel · teamcontext
Card- en SetController
Verwerken paginaverzoeken.
Binnen Laravel-webapplicatie · container. CardController en SetController vragen catalogusgegevens op bij PokemonTcgService. -
Component · PHP · eigen werk
PokemonTcgService
Haalt SDK-data op en biedt attributen aan voor opslag.
Binnen Laravel-webapplicatie · container. PokemonTcgService laat PokemonTcgAdapter SDK-objecten omzetten naar applicatiemodellen.PokemonTcgService gebruikt de Pokémon TCG SDK om de externe catalogus te raadplegen.PokemonTcgService biedt modelattributen voor opslag aan via de MongoDB-modellen. -
Component · PHP · eigen werk
PokemonTcgAdapter
Bouwt applicatiemodellen uit SDK-objecten.
Binnen Laravel-webapplicatie · container. -
Bibliotheek · PHP · derde partij
Pokémon TCG SDK
Verzorgt de verzoeken aan de externe API.
Binnen Laravel-webapplicatie · container. De SDK communiceert met de externe Pokémon TCG API via HTTPS en JSON. -
Container · MongoDB
MongoDB
Bewaart modelattributen.
-
Extern softwaresysteem
Pokémon TCG API
Levert kaart- en setgegevens.
De omzetting van een kaart leesbaar maken
De Pokémon TCG SDK levert objecten met getters voor bijvoorbeeld naam, afbeeldingen, aanvallen en zeldzaamheid. De adapter vertaalt die naar de attributen en relaties van onze Laravel-modellen. Omdat een kaart veel onderdelen heeft, werkte ik ook aan de indeling van die omzettingscode.
Relatiedetails onderbrengen in helpers
In de onderzochte code is adaptCard() opgedeeld met helpers voor relaties, meervoudige relaties en marktgegevens. De hoofdmethode bouwt eerst de kaart op uit de SDK-velden en laat daarna de bijbehorende objecten koppelen. Zo kun je de hoofdstappen volgen zonder alle relatiedetails tegelijk te lezen. Om één relatie volledig te volgen, moet je wel de bijbehorende helper openen.
Bij zeldzaamheid levert de externe bron bijvoorbeeld een string. adaptRarity() maakt daar een Rarity-object van, dat via een relatiehelper aan de kaart wordt gekoppeld. Hier verschilt de implementatie van het eerdere ontwerp: het oorspronkelijke klassendiagram beschrijft zeldzaamheid nog als tekstveld op Card.
-
Klasse · Pokemon\Models · extern
Card
+ getId(): string+ getName(): string+ getRarity(): ?string
-
Klasse · App\Adapters · eigen werk
PokemonTcgAdapter
+ adaptCard(SdkCard): Card+ adaptSet(SdkSet): Set+ adaptRarity(string): Rarity− setCardRelationships(…): void− setCardManyToManyRelationships(…): void− setCardMarketRelationships(…): void
-
Klasse · App\Models · teamcontext
Card
Attributen: id, name, hp, …+ rarity(): EmbedsOne+ images(): EmbedsOne+ attacks(): EmbedsMany
-
Klasse · App\Models · teamcontext
Rarity
Attribuut: name
De vier C4-diagrammen op deze pagina zijn achteraf gemaakt op basis van de projectcode. Ze zoomen in van de toepassing naar de onderdelen binnen Laravel en de klassen rond de adapter. Het eerder gelinkte klassendiagram hoort bij het oorspronkelijke projectontwerp.
Een uitgewerkt schema en gerichte backendwijzigingen
Met het MongoDB-schema en mijn werk aan de service en adapter leverde ik een bijdrage aan de verwerking van externe kaartgegevens. De refactorings maken die verwerking beter te volgen: de betreffende zoekacties zijn uit de omzettingsmethoden gehaald, de service biedt attributen via een upsert aan voor opslag.
Voor de volledige gegevensstroom ontbreekt nog testbewijs. De aanwezige tests behandelen authenticatie, accountinstellingen en dashboardtoegang, maar onderbouwen geen correcte adaptermapping of het opslaan en herladen van kaartrelaties. De huidige catalogus- en collectiestroom is niet volledig gevalideerd. Het uitgewerkte ontwerp en de backendwijzigingen vormen daarom het onderbouwde resultaat.
Daarnaast paste ik type-annotaties en de PHPStan/Larastan-configuratie aan. Een geslaagde analyserun is niet aangetoond.