Brandbrand
Plan een gesprek
Brandbrand
Serverkasten in een donkere serverruimte, vol kabels en brandende statuslampjes

Waarom we Shopify-apps in Laravel bouwen

Een Shopify-app is vooral een backend. Webhooks verwerken, data synchroniseren, taken op tijd draaien. Waarom Laravel daar sterk in is, met de wachtrij en de planner als hart.

Een app is meer dan een scherm

Wat een winkelier van een app ziet, is een paar schermen in de Shopify-admin. Het meeste werk gebeurt daarachter. Een bestelling komt binnen en moet naar het ERP. Elke nacht moeten de voorraden van duizend producten kloppen. Een factuur moet als PDF klaarstaan als de klant erom vraagt.

Dat werk loopt niet op het ritme van een bezoeker die op een knop drukt. Het komt in pieken, het moet opnieuw als een systeem even niet antwoordt, en het moet op vaste tijden draaien. Laravel is een PHP-framework dat precies daarvoor alles meelevert: een wachtrij, een planner, een database-laag en tests.

Shopify wacht vijf seconden

Shopify meldt gebeurtenissen met webhooks: een bestelling, een nieuwe klant, een verwijderde app. Je app moet binnen vijf seconden antwoorden. Lukt dat niet, dan probeert Shopify het nog acht keer in vier uur. Faalt de webhook acht keer achter elkaar, dan verwijdert Shopify een abonnement dat via de API is aangemaakt, en ontvang je geen meldingen meer.

Een bestelling naar een ERP sturen duurt vaak langer dan vijf seconden. Daarom doet de app op het moment zelf maar twee dingen: controleren dat het bericht echt van Shopify komt, en het werk in de wachtrij zetten. Shopify raadt die aanpak zelf aan.

// Controleer de handtekening, zet het werk in de wachtrij, en antwoord meteen.
Route::post('/webhooks/orders/create', function (Request $request) {
    $hmac = base64_encode(hash_hmac('sha256', $request->getContent(), config('services.shopify.secret'), true));
    abort_unless(hash_equals($hmac, (string) $request->header('X-Shopify-Hmac-Sha256')), 401);

    ProcessOrder::dispatch($request->header('X-Shopify-Webhook-Id'), $request->json()->all());

    return response()->noContent();
});
routes/api.php

Een wachtrij die zichzelf herstelt

In de wachtrij pakt een worker de taak op en voert hem uit, los van het verzoek van Shopify. Wat Laravel daar standaard bij doet:

  • Opnieuw proberen. Je stelt in hoe vaak een taak het mag proberen en hoe lang hij tussendoor wacht. Ligt het ERP vijf minuten plat, dan loopt de bestelling daarna gewoon door.
  • Mislukte taken bewaren. Wat het na alle pogingen niet haalt, komt in een tabel met mislukte taken, met de foutmelding erbij. Eén commando zet ze opnieuw in de wachtrij.
  • Geen dubbel werk. Een unieke taak wordt niet nog eens ingepland zolang dezelfde nog wacht of loopt. Shopify kan een webhook vaker sturen; met het webhook-ID herken je de herhaling.
  • Overzicht. Horizon toont welke taken er wachten, hoe lang ze duren en wat er misging, voor wachtrijen in Redis.

Binnen de limieten van Shopify

De GraphQL Admin API van Shopify rekent per verzoek punten. Je app heeft een budget dat elke seconde wordt aangevuld; wie te veel tegelijk vraagt, krijgt een fout. In Laravel geef je een taak een limiet mee. Zit het budget op, dan gaat de taak terug in de wachtrij en probeert hij het later, in plaats van te falen.

Voor grote hoeveelheden data, zoals alle bestellingen van een jaar, gebruik je de bulk operations van Shopify. Shopify draait de vraag zelf en levert een bestand op met het resultaat. De vraag zelf telt niet mee voor de limiet. Laravel verwerkt dat bestand daarna in batches, elke batch een eigen taak in de wachtrij.

Eén cronregel voor alle taken

Voor taken op vaste tijden heeft de server één cronregel nodig, die elke minuut de planner van Laravel start. Welke taken wanneer draaien, staat in de code, zodat het met de rest van de app in versiebeheer zit en door een review gaat.

Een taak die in een app met webhooks niet mag ontbreken: periodiek bij Shopify ophalen wat er veranderd is, voor het geval een webhook niet aankwam. Shopify garandeert de levering van webhooks niet en raadt zo'n controle zelf aan.

// De server start dit elke minuut; Laravel bepaalt wat er aan de beurt is.
Schedule::job(new ReconcileOrders)->hourly()->withoutOverlapping();
Schedule::job(new SyncStock)->everyFifteenMinutes()->onOneServer();
Schedule::command('queue:prune-failed --hours=168')->daily();
routes/console.php

En het template van Shopify?

Shopify raadt voor de meeste nieuwe apps zijn eigen template aan, gebouwd op React Router in JavaScript. Dat is een goede start voor een app die vooral uit schermen bestaat. Laravel is dus een keuze, geen vereiste.

Wij kiezen Laravel als de app vooral achter de schermen werkt: koppelingen met een ERP of boekhouding, taken die minuten duren, en veel data. Dan wil je een wachtrij, een planner, migraties voor de database en tests die al in het framework zitten, in plaats van ze zelf bij elkaar te zoeken.

Ons standpunt

Een webhook die we missen, halen we zelf op. Een app die op de levering van Shopify vertrouwt, mist vroeg of laat een bestelling.
Brandbrand

Vragen hierover

Moet een Shopify-app in Laravel?

Nee. Shopify raadt voor de meeste apps een template in JavaScript aan. Laravel kiezen wij voor apps met veel werk achter de schermen: koppelingen, lange taken en veel data.

Wat gebeurt er als de server van de app even plat ligt?

Shopify probeert een webhook nog acht keer in vier uur. Komt hij daarna nog niet aan, dan haalt de periodieke controle de gemiste wijzigingen op bij Shopify.

Kan Laravel de API-limieten van Shopify aan?

Ja. Een taak met een limiet wacht tot er weer budget is, en grote hoeveelheden data gaan via de bulk operations van Shopify.

Wat is een wachtrij precies?

Een lijst taken die een apart proces één voor één uitvoert. De app hoeft er niet op te wachten, en een taak die faalt, kan opnieuw.