Pokemanager

Het MongoDB-schema en gerichte refactorings aan de Laravel-backend van een Pokémon-verzamelapp.

Full-stack webontwikkeling 6 min leestijd

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.

C1 · Systeemcontext De catalogus en het persoonlijke kaartbezit Systeemcontext
  1. Persoon Kaartverzamelaar

    Wil kaarten en sets bekijken en eigen bezit bijhouden.

    De kaartverzamelaar is de beoogde gebruiker van de catalogus en het collectiebeheer.
  2. Softwaresysteem · teamproject Pokemanager

    Teamapplicatie voor een catalogus en persoonlijke verzamelingen.

    Pokemanager vraagt catalogusgegevens op bij de externe Pokémon TCG API.
  3. Extern softwaresysteem Pokémon TCG API

    Levert kaart- en setgegevens.

Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
De catalogus en het persoonlijke kaartbezit · Beoogd gebruik van Pokemanager als verzamelapp, met de Pokémon TCG API als bron voor kaarten en sets.

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.

C2 · Containerdiagram De onderdelen van de webapplicatie Containers
  1. 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.
  2. 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.
  3. Container · MongoDB MongoDB

    Opslag voor kaarten, sets en collecties.

    Binnen Pokemanager · softwaresysteem.
  4. Extern softwaresysteem Pokémon TCG API

    Bron voor kaart- en setgegevens.

Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
De onderdelen van de webapplicatie · De teamopzet bestaat uit een React-interface, Laravel en MongoDB. Inertia verbindt de interface met Laravel.

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.

C3 · Componentdiagram De service en adapter binnen Laravel Componenten · Laravel
  1. Container · teamcontext Browserapplicatie

    React en TypeScript.

    De browser vraagt kaart- en setpagina’s op bij de controllers via HTTP en Inertia.
  2. Component · PHP / Laravel · teamcontext Card- en SetController

    Verwerken paginaverzoeken.

    Binnen Laravel-webapplicatie · container. CardController en SetController vragen catalogusgegevens op bij PokemonTcgService.
  3. 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.
  4. Component · PHP · eigen werk PokemonTcgAdapter

    Bouwt applicatiemodellen uit SDK-objecten.

    Binnen Laravel-webapplicatie · container.
  5. 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.
  6. Container · MongoDB MongoDB

    Bewaart modelattributen.

  7. Extern softwaresysteem Pokémon TCG API

    Levert kaart- en setgegevens.

Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
De service en adapter binnen Laravel · Mijn werk aan de service en adapter binnen de catalogusroute. Controllers en interface tonen de teamcontext; de SDK draait als bibliotheek binnen Laravel. De figuur toont de codeopzet, geen gevalideerde volledige gegevensstroom.

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.

C4 · Codediagram Hoe de adapter een kaart opbouwt Code · adapter
  1. Klasse · Pokemon\Models · extern Card
    • + getId(): string
    • + getName(): string
    • + getRarity(): ?string
  2. Klasse · App\Adapters · eigen werk PokemonTcgAdapter
    • + adaptCard(SdkCard): Card
    • + adaptSet(SdkSet): Set
    • + adaptRarity(string): Rarity
    • − setCardRelationships(…): void
    • − setCardManyToManyRelationships(…): void
    • − setCardMarketRelationships(…): void
    PokemonTcgAdapter leest de getters van Pokemon\Models\Card, hier aangeduid als SdkCard.adaptCard() maakt een App\Models\Card en roept setCardRelationships() aan om gerelateerde objecten te koppelen.adaptRarity() maakt een Rarity-object op basis van een string, zonder databasezoekactie.
  3. Klasse · App\Models · teamcontext Card
    • Attributen: id, name, hp, …
    • + rarity(): EmbedsOne
    • + images(): EmbedsOne
    • + attacks(): EmbedsMany
    Het Card-model definieert rarity() als een EmbedsOne-relatie naar Rarity. De adapter koppelt het opgebouwde object met setRelation().
  4. Klasse · App\Models · teamcontext Rarity
    • Attribuut: name
Onderdeel · type in het blok Extern onderdeel Systeem- of containergrens Gerichte relatie · uitleg bij de pijl
Hoe de adapter een kaart opbouwt · Klassen en methoden voor de omzetting naar een kaartmodel. SdkCard en SdkSet benoemen externe SDK-typen. … verkort de parameterlijst; + is publiek en − privé.

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.

TERUG NAAR SITE