Å integrere en storskala WooCommerce-nettbutikk med et forretningssystem (Enterprise Resource Planning - ERP) er en av de mest krevende oppgavene innen moderne e-handelsarkitektur. Når produktkatalogen overstiger femti tusen varelinjer (SKU-er), det daglige transaksjonsvolumet teller tusenvis av ordrer og lagerbeholdningen må synkroniseres i sanntid på tvers av flere salgskanaler, bryter tradisjonelle synkrone punkt-til-punkt-grensesnitt sammen. Direkte HTTP-forespørsler mellom systemene resulterer under trafikktopper i kaskaderende gateway-tidsavbrudd, databasedeadlocks, overbelastning fra webhook-bølger og kritiske oversalg i kassen.
En feiltolerant og skalerbar toveis WooCommerce ERP-integrasjon krever en asynkron, hendelsesdrevet og frakoblet arkitektur bygget på fem tekniske grunnpilarer:
- Asynkron meldingsbuffring: Inntak av webhooks og kjøpshendelser i en mellomliggende kø (Redis Streams eller Cloudflare Queues) med umiddelbar HTTP 202 Accepted-kvittering på under 25 ms, slik at nettbutikken frikobles fra ERP-systemets svartid.
- Garantert idempotens: Håndheving av streng deduplisering via
X-Idempotency-Key-headere og atomære distribuerte låser i Redis for å utelukke doble ordrebokføringer og doble faktureringer. - Samtidighetskontroll i databasen: Bruk av MySQL InnoDB-radlåsing (
SELECT ... FOR UPDATE) eller optimistisk versjonskontroll for å eliminere kappløpstilstander og oversalg under store kampanjer. - Robust feilhåndtering: Implementering av eksponensiell tilbakegang med tilfeldig forsinkelse (full jitter) og ruting av vedvarende feil til en beriket Dead Letter Queue (DLQ) for inspeksjon og automatisk replay.
- To-trinns dataavstemming: Kombinasjon av timebaserte delta-kjøringer med nattlige kryptografiske sjekksum-kontroller (SHA-256-blokker) for å fjerne uoverensstemmelser i lager og pris.
For utviklingsteam og virksomheter som planlegger eller oppgraderer integrasjonen mellom nettbutikk og forretningssystem, tilbyr våre spesialiserte WooCommerce ERP-integrasjonstjenester skreddersydd rådgivning og leveranse. Denne veilederen tar for seg de tekniske arkitekturmønstrene, integrasjonsmatrisen for ledende ERP-plattformer, produksjonsklar PHP 8.4-kode og operasjonelle driftsrutiner for feilsøking i produksjon.
Arkitektonisk fundament: Frakoblet hendelsesdrevet integrasjon
I enkle WooCommerce-oppsett trigger hendelser i nettbutikken direkte synkrone HTTP-kall til forretningssystemet. Når en kunde fullfører et kjøp, utløses kroken woocommerce_checkout_order_processed, som umiddelbart starter en synkron cURL-forbindelse til SAP S/4HANA, Microsoft Dynamics 365 eller Comarch Optima.
Dette synkrone mønsteret medfører en betydelig arkitektonisk sårbarhet:
[Kundens nettleser]
│ (1) Fullfør bestilling i kassen
▼
[WooCommerce / PHP-FPM Worker] ──(2) Synkront HTTP POST-kall──▶ [ERP-endepunkt (Tregt / Nede)]
│ │
│ ◀───(3) HTTP 504 Gateway Timeout (Tråd blokkert i 60s)────────┘
▼
[Kunden ser feilmelding] ──▶ Flere klikk ──▶ Databaselåser & dupliserte ordrer
Når ERP-systemet gjennomfører nattlige batch-kjøringer, databasevedlikehold eller opplever nettverksforsinkelser, stiger responstiden fra 200 millisekunder til 30 sekunder eller mer. Fordi PHP-FPM-arbeidstråder er bundet synkront til åpne socket-forbindelser, tømmes webserverens tilgjengelige trådpool på få sekunder. Nye kunder som besøker nettbutikken for å se på produkter eller laste handlekurven, møtes av HTTP 504 Gateway Timeout-feil. Dersom nettverksforbindelsen brytes etter at ERP-systemet har opprettet ordren, men før WooCommerce mottar bekreftelsen, vil ukoordinerte nye forsøk dessuten opprette doble salgsordrer og dobbeltreservere varer på lageret.
Det frakoblede meldingsmegler-mønsteret
For å oppnå enterprise-stabilitet må hendelsesproduksjon frikobles fullstendig fra hendelsesprosessering. Nettbutikken og ERP-systemet kommuniserer utelukkende gjennom en mellomliggende meldingsmegler (event broker).
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ FRAKOBLET HENDELSESDREVET INTEGRASJONS-PIPELINE │
│ (Komplett frakobling mellom checkout i nettbutikken og prosessering mot ERP-systemet) │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────┐ ┌─────────────────────────┐
│ WooCommerce-butikk │ │ ERP-system │
│ (Ordre- og kundehub) │ │ (Lager og hovedbok) │
└────────────┬────────────┘ └────────────▲────────────┘
│ │
(1) Raskt inntak (4) Kontrollert utsending
(Ikke-blokkerende) (Tilpasset API-kvoter)
▼ │
┌────────────────────────────────────────────────────────────┴───────────────────────────┐
│ MELDINGSMEGLER OG BUFFER │
│ (Redis Streams / Cloudflare Queues) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌────────────────────────────┐ │
│ │ orders.incoming │ │ inventory.delta │ │ dead.letter.queue (DLQ) │ │
│ │ [Hendelse 1][Hend 2]│ │ [SKU 101][SKU 102] │ │ [Feilede pakker + kontekst│ │
│ └──────────┬───────────┘ └──────────┬───────────┘ └─────────────▲──────────────┘ │
└──────────────┼─────────────────────────┼────────────────────────────┼──────────────────┘
│ │ │
(2) Les strøm med (5) Oppdater lager (3) Maks antall
konsumentgruppe via InnoDB-låser forsøk nådd
▼ ▼ │
┌─────────────────────────────────────────────────────────────────────┴──────────────────┐
│ WP-CLI DAEMON WORKER POOL (PHP 8.4) │
│ │
│ - Signalhåndtering (SIGTERM/SIGINT) - Idempotensvalidering (Redis SETNX) │
│ - Minnehåndtering (wp_cache_flush) - Eksponensiell tilbakegang med full jitter │
└────────────────────────────────────────────────────────────────────────────────────────┘
- Rask hendelseslagring: Når kunden fullfører en handel, skriver WooCommerce en kompakt datapakke til en Redis Stream (
orders.incoming) og viser ordrebekreftelsen til kunden på under 15 millisekunder. - Asynkron bakgrunnsprosessering: En flåte med overvåkede bakgrunnsprosesser som kjører via WP-CLI henter meldinger fra strømmen i et tempo som er tilpasset ERP-systemets kapasitet.
- Kontrollert mottrykk (backpressure): Dersom ERP-systemet struper innkommende trafikk eller er nede for vedlikehold, akkumuleres meldingene trygt i bufferen uten å påvirke nettbutikkens ytelse eller brukeropplevelse.
- Idempotent utførelse: Hver melding bærer en unik og deterministisk idempotensnøkkel. Hvis en arbeidsprosess krasjer uventet, vil en ny prosess ta over fra listen over ventende meldinger uten risiko for dupliserte poster i databasen.
ERP-systemmatrise og protokollkompatibilitet
Ulike forretningssystemer krever ulike tilnærminger til nettverksprotokoller, samtidighet, autentisering og gjennomstrømningskapasitet. En vellykket integrasjon forutsetter at man tilpasser arkitekturen til egenskapene ved det aktuelle systemet.
Tabellen nedenfor sammenligner fire sentrale forretningssystemer som ofte integreres med WooCommerce i det europeiske og nordiske markedet:
| ERP-plattform | Støttede protokoller | Gjennomstrømningsprofil | Samtidighets- og låsemodell | Vanlige arkitektoniske feilkilder |
|---|---|---|---|---|
| SAP S/4HANA | OData v4, IDoc over SAP BTP, RFC, Async SOAP | Høy batch-kapasitet (10k+ rader/min); middels responstid ved enkeltkall | SAP Logical Unit of Work (LUW); Enqueue-serverlåser; OData batch-grenser | Tilkoblingspool-metning ved omstart av SAP Cloud Connector; tidsavbrudd ved komplekse stykklister (BOM) |
| Comarch Optima / XL | Optima WebAPI, COM DLL-automatisering, MS SQL Staging | Moderat API-hastighet (50-100 ordrer/min); svært høy via SQL-staging (50k/min) | Single-Threaded Apartment (STA) i COM; MS SQL tabell-låseskalering (sp_lock) | Minnelekkasjer i COM-prosesser som krever omstart; lisenskonflikter ved hengende tråder |
| InsERT Subiekt GT / nexo | Subiekt GT Sfera (COM/OLE), Subiekt nexo PRO SDK (.NET / WebAPI) | GT: 30-80 ordrer/min; nexo PRO: 250+ ordrer/min | SQL Server radlåsing på dok__Dokument og tw__Towar; desktop-COM-begrensninger | COM-tråder låses av modale systemdialoger i GT; indeksfragmentering ved store varemengder |
| Microsoft Dynamics 365 BC | Business Central REST API v2.0, OData v4, AL API Pages | Grense på 600 kall/min i sky; JSON-batching (100 delkall per pakke) | Snapshot-isolasjon i Azure SQL; låsetidsavbrudd på ordrehode ved bokføring | HTTP 429 hastighetsbegrensning; tapte webhook-varsler ved massiv fakturering |
Integrasjonsmønstre for SAP S/4HANA
I SAP S/4HANA skjer integrasjonen typisk via SAP Business Technology Platform (SAP BTP) og SAP Cloud Connector. Synkrone enkeltkall via standard OData v4-tjenester (API_SALES_ORDER_SRV) har en gjennomsnittlig nettverksforsinkelse på 450 til 800 millisekunder per forespørsel.
For nettbutikker med høyt volum er det nødvendig å benytte asynkrone batch-prosesser:
- JSON-batching: Slå sammen opptil hundre salgsordrer i én flerparts OData-endringspakke. Dette reduserer overhead ved TCP-håndtrykk og sikrer at enten alle dokumentene i pakken opprettes, eller ingen.
- Asynkrone IDoc-køer: For store vare- og prisimportkjøringer bør man benytte standardiserte IDoc-grensesnitt (som
ORDERS05ellerMATMAS05) som behandles via bakgrunnsjobber i SAP. - Håndtering av logiske enheter (SAP LUWs): SAP bruker Logical Units of Work for å opprettholde transaksjonell integritet. Integrasjonen må ta vare på det returnerte SAP-dokumentnummeret og lagre det i en indeksert oppslagstabell (
wp_wc_orders_erp_lookup) koblet til WooCommerce-ordre-ID-en.
Comarch ERP Optima og Comarch ERP XL
Comarch Optima er utbredt i sentraleuropeiske bedrifter. Ettersom systemet opprinnelig ble bygget for Microsoft SQL Server på lokale Windows-miljøer, stiller moderne API-integrasjon spesifikke krav.
- WebAPI kontra COM-automatisering: Selv om Optima WebAPI tilbyr et REST-grensesnitt, instansierer det underliggende COM-objekter (
Optima.dll). Dette krever at hver arbeidstråd initialiseres i en Single-Threaded Apartment (STA)-modell viaCoInitialize. - Håndtering av lisenser: Dersom flere parallelle PHP-prosesser forsøker å opprette ordrer via COM uten køstyring, vil ressursgrenser i Windows og begrensninger på modullisenser føre til umiddelbare avvisninger.
- Staging-tabell-arkitektur: For B2B-løsninger med titusener av lagerjusteringer per time er den mest pålitelige tilnærmingen å speile lager og priser direkte fra skrivebeskyttede SQL-snapshots til Redis, mens ordreopprettelse håndteres av dedikerte, sekvensielle Windows-arbeidsprosesser.
InsERT Subiekt GT og Subiekt nexo PRO
InsERT Subiekt GT benytter Sfera for Subiekt GT (et COM/OLE Automation-grensesnitt), mens det mer moderne Subiekt nexo PRO tilbyr et komplett .NET SDK og WebAPI.
- Begrensninger i Subiekt GT Sfera: Sfera-kall kjører synkront i Windows-skrivebordsmiljøet. Dersom en uventet feil oppstår eller en modal systemdialog åpnes i bakgrunnen, fryser COM-tråden på ubestemt tid. Integrasjonstjenesten må kjøre som en overvåket Windows-tjeneste (i C# eller Go) med strenge tidsavbrudd på 30 sekunder og automatisk omstart ved heng.
- Fordeler med Subiekt nexo PRO: Nexo PRO støtter asynkron flertrådskjøring. Ved oppdatering av produktkatalogen bør man hente endringer basert på tidsstempelet
Zmieniono, slik at kun poster som er nyere enn forrige kontrollpunkt overføres.
Microsoft Dynamics 365 Business Central
Skyversjonen av Business Central stiller strenge krav til ressursbruk:
- Hastighetsbegrensninger (Rate Limits): Microsoft tillater maksimalt 600 API-kall per minutt per miljø. Overskridelse resulterer i HTTP 429 Too Many Requests med en
Retry-After-header. - $batch-endepunkter: For å overføre 50 ordrer uten 50 separate HTTP-forespørsler, sender man en POST-forespørsel til
https://api.businesscentral.dynamics.com/v2.0/{tenant}/production/api/v2.0/$batchmed en samling delkall som behandles sekvensielt i nettskyen. - Endringssporing (Change Data Capture): I stedet for kontinuerlig polling etter lagerendringer, bør man abonnere på Webhooks i Business Central (Graph-varslinger) for å trigge målrettede delta-synkroniseringer ved endringer.
Mønstre for feiltoleranse ved høyt transaksjonsvolum
En profesjonell integrasjon må designes under forutsetning av at nettverkstilkoblinger, databaser og eksterne tjenester vil feile periodisk. Fem arkitekturmønstre danner grunnlaget for feiltoleranse.
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ MØNSTER FOR IDEMPOTENS OG FEILHÅNDTERING │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Innkommende forespørsel / Webhook
│
▼
┌───────────────────────────────────┐
│ Hent ut X-Idempotency-Key header │
└─────────────────┬─────────────────┘
│
▼
┌───────────────────────────────────┐
│ Atomisk Redis-lås: │
│ SETNX lock:idempotency:{key} │
└─────────┬─────────────────────────┘
│
┌─────┴────────────────────────┐
│ Nøkkel opprettet (Ny) │ Nøkkel finnes (Duplikat / Under behandling)
▼ ▼
┌───────────────────────────┐ ┌─────────────────────────────────────────────────┐
│ Status: PROCESSING │ │ Sjekk status: │
│ (TTL: 86400 sekunder) │ │ - Hvis 'PROCESSING': Returner HTTP 409 Conflict │
└─────────┬─────────────────┘ │ - Hvis 'COMPLETED': Returner lagret svar │
│ └─────────────────────────────────────────────────┘
▼
┌───────────────────────────┐
│ Utfør forretningslogikk │
│ (Transaksjon + DB-skriving│
└─────────┬─────────────────┘
│
┌─────┴────────────────────────┐
│ Suksess │ Feil (Unntak / Tidsavbrudd)
▼ ▼
┌───────────────────────────┐ ┌─────────────────────────────────────────────────┐
│ Oppdater status i Redis: │ │ Beregn eksponensiell tilbakegang med jitter: │
│ COMPLETED + Resultatcache │ │ sleep = min(T_max, T_base * 2^forsøk) + rand() │
└─────────┬─────────────────┘ └─────────────┬───────────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────┐ ┌─────────────────────────────────────────────────┐
│ Returner HTTP 200/201 │ │ Hvis forsøk < 5: Legg tilbake i forsinket kø │
└───────────────────────────┘ │ Hvis forsøk >= 5: Ruter til Dead Letter Queue │
└─────────────────────────────────────────────────┘
1. Idempotent forespørselshåndtering (X-Idempotency-Key)
I asynkrone nettverk er gjentatte sendinger og dupliserte webhooks en naturlig del av hverdagen. Uten idempotenssikring kan et duplisert betalingsvarsel føre til doble leveranseordrer eller doble tilbakebetalinger til kunden.
Alle modifiserende forespørsler må bære en unik idempotensnøkkel:
- Nøkkelstruktur: Klienten sender en
X-Idempotency-Key-header (UUIDv4) eller serveren genererer en deterministisk hash:sha256(order_id + status + timestamp). - Atomisk tilstandslåsing: Før oppdraget utføres, gjør arbeidsprosessen en atomisk registrering i Redis:
SET lock:idempotency:{hash} "PROCESSING" NX EX 86400 - Håndtering av tilstander:
- Dersom nøkkelen lagres vellykket (
OK), starter behandlingen. Ved fullføring oppdateres verdien til"COMPLETED:{json_svar}". - Dersom nøkkelen eksisterer med verdien
"PROCESSING", behandles oppdraget allerede av en annen prosess. Forespørselen avvises med HTTP 409 Conflict eller venter på fullføring. - Dersom verdien starter med
"COMPLETED:", returneres det bufrede svaret umiddelbart sammen med headerenX-Cache-Lookup: HIT.
- Dersom nøkkelen lagres vellykket (
2. Redis Streams for pålitelig meldingsrekkefølge
Tradisjonelle Redis-lister (LPUSH/RPOP) mister meldinger dersom en prosess krasjer etter at meldingen er hentet, men før databasetransaksjonen er fullført. Redis Streams løser dette med konsumentgrupper:
- Legg til meldinger (
XADD): Hendelser skrives til en permanent append-only-logg med millisekund-tidsstempel. - Konsumentgrupper (
XREADGROUP): Flere arbeidsprosesser leser fra samme strøm uten å duplisere oppgaver. Redis holder oversikt over hvilken konsument som eier hver enkelt melding. - Eksplisitt bekreftelse (
XACK): En melding fjernes først fra ventelisten (PEL) når arbeidsprosessen har utført alle databaseoppdateringer og kaltXACK. - Gjenoppretting av tapte meldinger (
XCLAIM): Hvis en prosess krasjer, kan andre arbeidere undersøke PEL viaXPENDINGog overta meldinger som har ventet i mer enn 60 sekunder viaXCLAIM.
3. Dead letter-køer (DLQ) og eksponensiell tilbakegang med full jitter
Integrasjonsfeil må deles inn i to kategorier:
- Forbigående feil: Nettverkstidsavbrudd, HTTP 502/503/504-gatewayfeil, HTTP 429-begrensninger og databasedeadlocks i MySQL. Disse skal forsøkes på nytt automatisk.
- Permanente feil: HTTP 400 Bad Request, 404 Not Found, 422 Unprocessable Entity, feil i mva-oppsett eller skjemafeil. Disse skal aldri forsøkes automatisk.
For forbigående feil benyttes eksponensiell tilbakegang med tilfeldig variasjon (full jitter) for å forhindre at mange prosesser overbelaster et ERP-system i oppstartsfasen:
$$\text{Intervall} = \min\left(T_{\text{max}}, T_{\text{base}} \times 2^{\text{forsøk}}\right)$$
$$\text{Ventetid} = \text{random}\left(0, \text{Intervall}\right)$$
Hvis en melding feiler etter fem forsøk, rutes den til en Dead Letter Queue (dlq:erp_sync). DLQ-posten inneholder hele datapakken, unntaksstakken, HTTP-statuskoden og tidsstempelet for manuell inspeksjon og gjenoppretting.
Samtidighetskontroll i databasen og låsing av lagerbeholdning
Under store kampanjer eller salgsutløsninger hender det at hundrevis av kunder forsøker å kjøpe de samme varene innenfor få sekunder. Hvis to prosesser leser lagerbeholdningen samtidig, bekrefter tilgjengelighet og oppdaterer databasen i parallell, vil nettbutikken overselge varer som ikke finnes på lager.
Pessimistisk låsing: MySQL InnoDB SELECT FOR UPDATE
WooCommerce lagrer lagerbeholdning i MySQL-databasen. Standardfunksjonene get_stock_quantity() og wc_update_product_stock() utfører ikke-låsende lesinger og er derfor ikke trygge ved høy samtidighet.
Pessimistisk låsing eliminerer kappløpstilstander ved å låse den aktuelle raden frem til transaksjonen er fullført:
Transaksjon A (Kunde 1) Transaksjon B (Kunde 2)
─────────────────────── ───────────────────────
START TRANSACTION; START TRANSACTION;
SELECT stock_quantity SELECT stock_quantity
FROM wp_wc_product_meta FROM wp_wc_product_meta
WHERE product_id = 4500 WHERE product_id = 4500
FOR UPDATE; FOR UPDATE;
──▶ Rad låst av Transaksjon A ──▶ VENTER (Tråd blokkert)
Sjekk beholdning: 5 på lager.
Trekk fra: 5 - 1 = 4.
UPDATE wp_wc_product_meta
SET stock_quantity = 4
WHERE product_id = 4500;
COMMIT; ── Lås frigis ────────────────────────▶ Lås tildeles Transaksjon B!
Sjekk beholdning: 4 på lager.
Trekk fra: 4 - 1 = 3.
UPDATE wp_wc_product_meta SET ...
COMMIT;
Viktig arkitekturprinsipp: Utfør aldri et eksternt HTTP-kall mot ERP-systemet mens du holder en åpen databasetransaksjon. Lås raden, oppdater de lokale tabellene, fullfør transaksjonen på under 50 millisekunder, og legg deretter ERP-synkroniseringen i den asynkrone køen.
Optimistisk samtidighetskontroll (OCC)
Ved moderat skrivebelastning gir optimistisk samtidighetskontroll høyere gjennomstrømning uten låsing under lesefasen.
Dette implementeres ved å legge til en version-kolonne i tabellen for produktmetadata:
UPDATE wp_wc_product_meta
SET stock_quantity = stock_quantity - :kjopsantall,
version = version + 1
WHERE product_id = :produkt_id
AND version = :forventet_versjon
AND stock_quantity >= :kjopsantall;
Dersom en annen prosess har oppdatert varen i mellomtiden, vil versjonsbetingelsen feile og null rader oppdateres. Applikasjonen fanger opp dette og prøver på nytt med det nye versjonsnummeret.
Produksjonskode: PHP 8.4 kø-konsument og WP-CLI-daemon
De følgende komponentene viser hvordan disse mønstrene implementeres i et produksjonsmiljø med PHP 8.4 og WooCommerce.
Listing 1: Bakgrunnskø-konsument som WP-CLI-daemon (QueueConsumerCommand.php)
Denne WP-CLI-kommandoen kjører som en kontinuerlig bakgrunnstjeneste under Systemd eller Supervisord. Den lytter på Redis Streams, behandler meldinger i bolker, håndterer POSIX-signaler og styrer minnebruken.
<?php
declare(strict_types=1);
namespace WPPoland\ErpIntegration\Cli;
use WP_CLI;
use Redis;
use Throwable;
if (!defined('ABSPATH')) {
exit;
}
/**
* Overvåket WP-CLI bakgrunnsdaemon for asynkron ERP-synkronisering.
*/
class QueueConsumerCommand
{
private const STREAM_KEY = 'erp:stream:orders';
private const CONSUMER_GROUP = 'erp_sync_group';
private const BATCH_SIZE = 10;
private const BLOCK_TIMEOUT_MS = 2000;
private const MAX_MEMORY_BYTES = 134217728; // 128 MB grense før kontrollert omstart
private Redis $redis;
private string $consumerName;
private bool $shouldRun = true;
public function __construct()
{
$this->consumerName = 'worker_' . gethostname() . '_' . getmypid();
$this->initRedis();
$this->registerSignalHandlers();
}
/**
* Inngangspunkt for: wp erp-queue consume
*/
public function __invoke(array $args, array $assocArgs): void
{
WP_CLI::line("Starter ERP-køkonsument [{$this->consumerName}] på PHP " . PHP_VERSION);
$this->ensureConsumerGroup();
$processedCount = 0;
while ($this->shouldRun) {
// Behandle POSIX-signaler
if (function_exists('pcntl_signal_dispatch')) {
pcntl_signal_dispatch();
}
try {
$messages = $this->redis->xReadGroup(
self::CONSUMER_GROUP,
$this->consumerName,
[self::STREAM_KEY => '>'],
self::BATCH_SIZE,
self::BLOCK_TIMEOUT_MS
);
if (empty($messages) || !isset($messages[self::STREAM_KEY])) {
$this->reclaimOrphanedMessages();
$this->checkMemoryThreshold();
continue;
}
foreach ($messages[self::STREAM_KEY] as $messageId => $payload) {
$this->processMessage((string) $messageId, $payload);
$processedCount++;
}
$this->checkMemoryThreshold();
} catch (Throwable $e) {
WP_CLI::error("Ubehandlet unntak i konsumentløkken: " . $e->getMessage(), false);
sleep(2); // Demping etter infrastrukturfeil
}
}
WP_CLI::success("ERP-køkonsument avsluttet kontrollert etter å ha behandlet {$processedCount} meldinger.");
}
private function processMessage(string $messageId, array $payload): void
{
$orderId = isset($payload['order_id']) ? (int) $payload['order_id'] : 0;
$idempotencyKey = $payload['idempotency_key'] ?? '';
if ($orderId <= 0 || empty($idempotencyKey)) {
WP_CLI::warning("Ugyldig datapakke i melding {$messageId}. Videresender til DLQ.");
$this->routeToDlq($messageId, $payload, 'Valideringsfeil: mangler order_id eller idempotency_key');
$this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
return;
}
// Sjekk idempotenslås i Redis
$lockKey = "erp:lock:idemp:{$idempotencyKey}";
$acquired = $this->redis->set($lockKey, 'PROCESSING', ['NX', 'EX' => 86400]);
if (!$acquired) {
$status = (string) $this->redis->get($lockKey);
if (str_starts_with($status, 'COMPLETED')) {
WP_CLI::line("Hendelse {$idempotencyKey} er allerede fullført. Bekrefter melding.");
$this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
return;
}
WP_CLI::line("Hendelse {$idempotencyKey} behandles for øyeblikket av en annen prosess. Hopper over.");
return;
}
try {
// Utfør synkroniseringslogikk
$this->syncOrderToErp($orderId, $payload);
// Merk som fullført og bekreft i strømmen
$this->redis->set($lockKey, 'COMPLETED:' . time(), ['EX' => 86400]);
$this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
WP_CLI::line("Synkroniserte ordre #{$orderId} [Meldings-ID: {$messageId}]");
} catch (Throwable $e) {
WP_CLI::warning("Feil under synkronisering av ordre #{$orderId}: " . $e->getMessage());
$this->redis->del($lockKey); // Frigi lås for nytt forsøk
$attempts = isset($payload['_retry_count']) ? ((int) $payload['_retry_count']) + 1 : 1;
if ($attempts >= 5) {
$this->routeToDlq($messageId, $payload, $e->getMessage());
$this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
} else {
// Legg tilbake i køen med økt teller
$payload['_retry_count'] = $attempts;
$this->redis->xAdd(self::STREAM_KEY, '*', $payload);
$this->redis->xAck(self::STREAM_KEY, self::CONSUMER_GROUP, [$messageId]);
}
}
}
private function syncOrderToErp(int $orderId, array $payload): void
{
$order = wc_get_order($orderId);
if (!$order) {
throw new \RuntimeException("WooCommerce-ordre #{$orderId} finnes ikke i databasen.");
}
// Eksempel: Kall ERP-klientadapter
// $this->erpClient->createSalesOrder($order);
}
private function reclaimOrphanedMessages(): void
{
// Se etter meldinger som har vært ubesvart i over 60 sekunder
$pending = $this->redis->xPending(self::STREAM_KEY, self::CONSUMER_GROUP, '-', '+', 5);
if (empty($pending)) {
return;
}
$staleIds = [];
foreach ($pending as $entry) {
$messageId = $entry[0];
$idleMs = $entry[2];
if ($idleMs > 60000) {
$staleIds[] = $messageId;
}
}
if (!empty($staleIds)) {
$claimed = $this->redis->xClaim(
self::STREAM_KEY,
self::CONSUMER_GROUP,
$this->consumerName,
60000,
$staleIds,
['JUSTID']
);
WP_CLI::line("Overtok " . count($claimed) . " foreldreløse meldinger fra feilede prosesser.");
}
}
private function routeToDlq(string $messageId, array $payload, string $reason): void
{
$dlqEntry = [
'original_id' => $messageId,
'payload' => json_encode($payload, JSON_THROW_ON_ERROR),
'failure_reason' => $reason,
'failed_at' => (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format(\DateTimeInterface::ATOM),
'consumer' => $this->consumerName,
];
$this->redis->xAdd('erp:stream:dlq', '*', $dlqEntry);
WP_CLI::error("Melding {$messageId} flyttet til DLQ: {$reason}", false);
}
private function checkMemoryThreshold(): void
{
// Tøm WordPress-objektbuffer og SQL-spørringslogg
wp_cache_flush();
global $wpdb;
$wpdb->queries = [];
if (function_exists('gc_collect_cycles')) {
gc_collect_cycles();
}
$memoryUsed = memory_get_usage(true);
if ($memoryUsed >= self::MAX_MEMORY_BYTES) {
WP_CLI::line("Minnegrense nådd (" . round($memoryUsed / 1048576, 2) . " MB). Starter om prosessen...");
$this->shouldRun = false;
}
}
private function registerSignalHandlers(): void
{
if (!function_exists('pcntl_signal')) {
return;
}
pcntl_signal(SIGTERM, function () {
WP_CLI::line("Mottok SIGTERM. Fullfører pågående pakke før avslutning...");
$this->shouldRun = false;
});
pcntl_signal(SIGINT, function () {
WP_CLI::line("Mottok SIGINT. Avslutter...");
$this->shouldRun = false;
});
}
private function initRedis(): void
{
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379, 2.5);
}
private function ensureConsumerGroup(): void
{
try {
$this->redis->xGroup('CREATE', self::STREAM_KEY, self::CONSUMER_GROUP, '0', true);
} catch (Throwable) {
// Gruppen eksisterer allerede
}
}
}
Listing 2: Sikker webhook-endepunkt med HMAC-validering (WebhookController.php)
Denne REST API-kontrolleren tar imot varsler fra ERP-systemet (for eksempel lagerjusteringer), verifiserer SHA-256 HMAC-signaturer i konstant tid, validerer tidsstempler og lagrer hendelsene i Redis Streams på under 20 millisekunder.
<?php
declare(strict_types=1);
namespace WPPoland\ErpIntegration\Api;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use Redis;
use Throwable;
if (!defined('ABSPATH')) {
exit;
}
class WebhookController extends WP_REST_Controller
{
protected $namespace = 'erp-sync/v1';
protected $rest_base = 'webhook';
private const WEBHOOK_SECRET_OPTION = 'erp_webhook_hmac_secret';
private const MAX_TIMESTAMP_SKEW_SECONDS = 300; // 5-minutters tidsvindu mot replay-angrep
public function register_routes(): void
{
register_rest_route($this->namespace, '/' . $this->rest_base, [
[
'methods' => 'POST',
'callback' => [$this, 'handleIncomingWebhook'],
'permission_callback' => [$this, 'validateHmacSignature'],
],
]);
}
/**
* Verifisering av HMAC-signatur og tidsstempel i konstant tid.
*/
public function validateHmacSignature(WP_REST_Request $request): bool|WP_Error
{
$signatureHeader = $request->get_header('x-erp-signature-256');
$timestampHeader = $request->get_header('x-erp-timestamp');
if (empty($signatureHeader) || empty($timestampHeader)) {
return new WP_Error(
'rest_forbidden',
'Påkrevde autentiseringsheadere mangler: X-ERP-Signature-256 eller X-ERP-Timestamp.',
['status' => 401]
);
}
// Valider tidsstempel for å forhindre replay-angrep
$requestTime = (int) $timestampHeader;
$currentTime = time();
if (abs($currentTime - $requestTime) > self::MAX_TIMESTAMP_SKEW_SECONDS) {
return new WP_Error(
'rest_forbidden',
'Webhook-tidsstempelet avviker mer enn 300 sekunder fra servertiden.',
['status' => 403]
);
}
$rawBody = $request->get_body();
$secret = (string) get_option(self::WEBHOOK_SECRET_OPTION, '');
if (empty($secret)) {
return new WP_Error('rest_error', 'Serverens HMAC-hemmelighet er ikke konfigurert.', ['status' => 500]);
}
$signedPayload = "t={$timestampHeader}.{$rawBody}";
$expectedSignature = hash_hmac('sha256', $signedPayload, $secret);
// Konstant-tid sammenligning for å nøytralisere timing-angrep
if (!hash_equals($expectedSignature, $signatureHeader)) {
return new WP_Error(
'rest_forbidden',
'Ugyldig kryptografisk HMAC-signatur.',
['status' => 403]
);
}
return true;
}
/**
* Ikke-blokkerende mottak og buffring i Redis.
*/
public function handleIncomingWebhook(WP_REST_Request $request): WP_REST_Response|WP_Error
{
$params = $request->get_json_params();
if (empty($params) || !is_array($params)) {
return new WP_Error('rest_bad_request', 'Ugyldig JSON-kropp.', ['status' => 400]);
}
$eventId = $request->get_header('x-idempotency-key') ?: wp_generate_uuid4();
$eventType = sanitize_text_field((string) ($params['event_type'] ?? 'inventory_delta'));
try {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 1.0);
// Skriv til Redis Stream for bakgrunnsbehandling
$streamPayload = [
'event_id' => $eventId,
'event_type' => $eventType,
'received_at' => (string) microtime(true),
'payload_json' => json_encode($params, JSON_THROW_ON_ERROR),
];
$messageId = $redis->xAdd('erp:stream:incoming_webhooks', '*', $streamPayload);
return new WP_REST_Response([
'status' => 'accepted',
'message_id' => $messageId,
'event_id' => $eventId,
], 202);
} catch (Throwable $e) {
return new WP_Error(
'rest_internal_error',
'Kunne ikke lagre webhook i køen: ' . $e->getMessage(),
['status' => 500]
);
}
}
}
Listing 3: Atomisk lagerreduksjon med SELECT FOR UPDATE (StockManager.php)
Denne databasetjenesten håndterer lagerreduksjoner i WooCommerce under utsjekk. Den benytter MySQL InnoDB-transaksjoner, radlåser og automatisk gjenoppretting ved deadlocks.
<?php
declare(strict_types=1);
namespace WPPoland\ErpIntegration\Database;
use wpdb;
use RuntimeException;
use InvalidArgumentException;
use Throwable;
if (!defined('ABSPATH')) {
exit;
}
class StockManager
{
private wpdb $db;
private const MAX_DEADLOCK_RETRIES = 3;
public function __construct()
{
global $wpdb;
$this->db = $wpdb;
}
/**
* Reduserer lagerbeholdning atomisk med eksklusiv radlåsing.
*
* @param int $productId Vare- eller variasjons-ID
* @param int $quantityToDeduct Positivt heltall
* @return int Gjenstående lagerantall
* @throws RuntimeException Ved utilstrekkelig lager eller vedvarende deadlock
*/
public function deductStockAtomically(int $productId, int $quantityToDeduct): int
{
if ($quantityToDeduct <= 0) {
throw new InvalidArgumentException("Antall som skal trekkes fra må være større enn null.");
}
$attempt = 0;
while ($attempt < self::MAX_DEADLOCK_RETRIES) {
$attempt++;
try {
$this->db->query('START TRANSACTION');
// Lås lagerraden eksklusivt for oppdatering
$query = $this->db->prepare(
"SELECT meta_value FROM {$this->db->postmeta}
WHERE post_id = %d AND meta_key = '_stock'
FOR UPDATE",
$productId
);
$currentStockRaw = $this->db->get_var($query);
if ($currentStockRaw === null) {
throw new RuntimeException("Lagerpost for vare-ID {$productId} ble ikke funnet.");
}
$currentStock = (int) $currentStockRaw;
if ($currentStock < $quantityToDeduct) {
$this->db->query('ROLLBACK');
throw new RuntimeException(
"Ikke nok varer på lager. Forespurt: {$quantityToDeduct}, Tilgjengelig: {$currentStock}"
);
}
$newStock = $currentStock - $quantityToDeduct;
// Oppdater lagerantall i databasen
$this->db->update(
$this->db->postmeta,
['meta_value' => (string) $newStock],
['post_id' => $productId, 'meta_key' => '_stock'],
['%s'],
['%d', '%s']
);
// Oppdater lagerstatus dersom beholdningen når null
if ($newStock === 0) {
$this->db->update(
$this->db->postmeta,
['meta_value' => 'outofstock'],
['post_id' => $productId, 'meta_key' => '_stock_status'],
['%s'],
['%d', '%s']
);
}
$this->db->query('COMMIT');
// Tøm WooCommerce-objektbuffere etter gjennomført transaksjon
wp_cache_delete($productId, 'post_meta');
if (function_exists('wc_delete_product_transients')) {
wc_delete_product_transients($productId);
}
return $newStock;
} catch (Throwable $e) {
$this->db->query('ROLLBACK');
// Fange opp MySQL Deadlock-feil 1213
$isDeadlock = str_contains($e->getMessage(), 'Deadlock found') ||
($this->db->last_error && str_contains($this->db->last_error, '1213'));
if ($isDeadlock && $attempt < self::MAX_DEADLOCK_RETRIES) {
// Eksponensiell ventetid med tilfeldig variasjon før nytt forsøk
$backoffUs = (int) (pow(2, $attempt) * 10000 + random_int(1000, 5000));
usleep($backoffUs);
continue;
}
throw new RuntimeException(
"Lagertransaksjon feilet i databasen [Forsøk {$attempt}]: " . $e->getMessage(),
0,
$e
);
}
}
throw new RuntimeException("Maksimalt antall gjentakelser ved deadlock overskredet for vare {$productId}.");
}
}
Helhetlig avstemming og løsning av split-brain-konflikter
Selv med robuste transaksjonskøer og strenge idempotenslåser vil eksterne hendelser — som fysiske returer til lageret, manuelle kassesalg i fysisk butikk (POS) eller gjenoppretting av eldre database-sikkerhetskopier — over tid skape uoverensstemmelser mellom WooCommerce og ERP-systemet.
En komplett integrasjonsarkitektur må derfor inkludere automatiserte avstemmingsrutiner og krystallklare regler for systemautoritet.
Styringsregler for autoritativ datakilde (Single source of truth)
For å unngå motstridende toveis oppdateringer må ansvarsområdene defineres strengt:
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ SPLIT-BRAIN-STYRINGSMODELL │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────────────┐
│ ERP-SYSTEM (HOVEDKILDE) │
│ │
│ - Fysisk lagerbeholdning (Hovedlager og eksterne varehus) │
│ - B2B- og B2C-prislister, rabattmatriser og volumavtaler │
│ - Varestamdata (Hoved-SKU, strekkoder/EAN, toll- og avgiftskoder) │
│ - Fakturering, regnskap og hovedbokføring │
└───────────────────────────────────────────┬───────────────────────────────────────────┘
│
Autoritativ synkronisering mot nettbutikk
│
▼
┌───────────────────────────────────────────────────────────────────────────────────────┐
│ WOOCOMMERCE (NETTBUTIKK-FRONTEND) │
│ │
│ - Handlekurv-økter og sanntids kjøpsintensjon fra kunder │
│ - Markedsføringstekster, SEO-metadata og kategoristrukturer │
│ - Midlertidige lagerreserveringer under utsjekk (5 minutters levetid) │
│ - Kundeprofiler og leveringsadressedata │
└───────────────────────────────────────────────────────────────────────────────────────┘
- Lagerbeholdning: ERP-systemet er den ubestridte hovedkilden. Ved uoverensstemmelser under avstemming vil ERP-verdien alltid overskrive lagerverdien i WooCommerce.
- Priser og rabatter: ERP-systemet er hovedkilde. WooCommerce beregner mva og rabatter for visning i kassen, men ERP-systemet foretar den endelige verifiseringen og beregningen ved opprettelse av salgsordren.
- Ordreopprettelse: WooCommerce er hovedkilde for kundenes kjøpsintensjon. Når en ordre er gjennomført og betalingen autorisert, holder WooCommerce den opprinnelige posten og sender den til køen. Så snart ERP-systemet har mottatt ordren og tildelt et internt dokumentnummer (
ERP_DOC_ID), overtar ERP eierskapet for videre statusendringer (plukk, pakking, forsendelse, kansellering).
To-trinns avstemmingsarkitektur
Et helhetlig avstemmingsrammeverk opererer på to ulike tidsintervaller:
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ TO-TRINNS AVSTEMMINGSLØKKER │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────────────┐
│ TRINN 1: KONTINUERLIG TIMEBASERT DELTA-AVSTEMMING │
│ (Høy frekvens, lett datamengde, rask oppdagelse av avvik) │
│ │
│ Spørring mot ERP og WooCommerce: │
│ WHERE updated_at >= NOW() - INTERVAL 90 MINUTE │
│ ──▶ Identifiser endrede SKU-er og ordrer ──▶ Legg i prioritert reparasjonskø │
└───────────────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────────────┐
│ TRINN 2: NATTLIG KRYPTOGRAFISK SJEKKSUM-REVISJON │
│ (Full katalogintegritet, blokksammenligning, automatisert oppretting) │
│ │
│ WooCommerce-katalog (Blokk 01: SKU 00001 - 00500) ──▶ SHA-256: e3b0c442... │
│ │ │
│ [Hash-sammenligning] │
│ │ │
│ ERP-katalog (Blokk 01: SKU 00001 - 00500) ──▶ SHA-256: e3b0c442... │
│ │
│ - Ved MATCH: Blokken er verifisert. Fortsett til Blokk 02. │
│ - Ved AVVIK: Kjør detaljert vare-for-vare synkronisering kun for Blokk 01. │
└───────────────────────────────────────────────────────────────────────────────────────┘
Trinn 1: Timebasert delta-avstemming
Den timebaserte avstemmingen sjekker begge systemer for poster som er endret i løpet av de siste 90 minuttene (som gir et 30-minutters overlappingsvindu).
- WooCommerce spør
wp_wc_ordersetter poster meddate_updated_gmt >= (NOW() - INTERVAL 90 MINUTE). - ERP-adapteren henter endrede dokumenter og lagerbevegelser.
- Avstemmingsmotoren sammenligner statusene. Eventuelle manglende ordrer eller avvikende statuser legges umiddelbart inn i den prioriterte reparasjonskøen.
Trinn 2: Nattlig kryptografisk sjekksum-revisjon
Å sammenligne hundre tusen varelinjer element for element over API-et hver natt belaster nettverket og databasen unødvendig. Løsningen er å benytte blokkvis hashing:
- Del hele produktkatalogen inn i sorterte blokker på 500 varer hver (f.eks. Blokk 001: SKU
A0001tilA0500). - Generer en SHA-256-hash av de sammenslåtte dataene for hver blokk: $$\text{Hash} = \text{SHA256}\left(\sum_{i=1}^{500} \text{SKU}_i + \text{Pris}_i + \text{Lager}_i\right)$$
- ERP-systemet produserer identiske hasher for de samme blokkene.
- Integrasjonsmotoren sammenligner de 200 blokk-hashene.
- Dersom 198 hasher stemmer overens, er 99 000 varer matematisk garantert i full synk.
- Kun de 2 avvikende blokkene (totalt 1 000 varer) lastes ned for detaljert reparasjon, noe som reduserer datamengden med 99 prosent.
Feilsøkingshåndbok: Løsning av hendelser i produksjon
Dersom det oppstår driftsavvik i produksjon, må utviklingsteamet reagere raskt ved hjelp av faste prosedyrer.
Hendelse 1: Webhook-feillevering og kappløpstilstander
- Symptomer: En ordre i WooCommerce blir manuelt kansellert av en administrator, men fem minutter senere endres statusen automatisk tilbake til “Behandles” på grunn av en forsinket webhook fra ERP-systemet.
- Årsak: Asynkrone nettverk garanterer ikke at datapakker ankommer i den rekkefølgen de ble sendt. Webhook B (sendt 14:02) ankom før Webhook A (sendt 14:00).
- Løsningsprosedyre:
- Legg til et versjonsnummer (
revision_id) eller et UTC-tidsstempel i webhook-dataene. - Opprett en versjonskolonne i
wp_wc_orders_erp_lookup. - Oppdater databasen kun dersom den innkommende versjonen er nyere enn den som allerede er lagret:
UPDATE wp_wc_orders_erp_lookup SET erp_status = :new_status, last_event_version = :incoming_version WHERE order_id = :order_id AND last_event_version < :incoming_version; - Hvis ingen rader oppdateres, forkastes den utdaterte meldingen med loggføringen
EVENT_SUPERSEDED.
- Legg til et versjonsnummer (
Hendelse 2: Databasedeadlocks under store kampanjer
- Symptomer: Kunder får feilmelding i kassen under et stort salg. MySQL-loggen viser:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction. - Årsak: To parallelle kjøp inneholder de samme to varene (Vare A og Vare B) i ulik rekkefølge. Transaksjon 1 låser Vare A og venter på Vare B; Transaksjon 2 låser Vare B og venter på Vare A.
- Løsningsprosedyre:
- Implementer kanonisk låserekkefølge. Sorter alltid produkt-ID-ene numerisk i stigende rekkefølge før
SELECT ... FOR UPDATEkalles:$productIds = [842, 105, 330]; sort($productIds, SORT_NUMERIC); // Blir: [105, 330, 842] foreach ($productIds as $id) { $stockManager->deductStockAtomically($id, $cartItems[$id]['qty']); } - Ettersom alle samtidige transaksjoner etterspør låsene i nøyaktig samme rekkefølge, blir sirkulære deadlocks matematisk umulige.
- Implementer kanonisk låserekkefølge. Sorter alltid produkt-ID-ene numerisk i stigende rekkefølge før
SAF-T Regnskap, EHF via Peppol og innrapportering til Altinn
Den norske delen av en WooCommerce-ERP-integrasjon handler mindre om synkronisering og mer om hva Skatteetaten kan be om å få se. Bokføringsloven krever at bokførte opplysninger skal kunne gjengis i standardisert form, og den formen er SAF-T Regnskap: en XML-fil som virksomheten må kunne produsere på forespørsel.
Kravet treffer integrasjonen på et konkret punkt. SAF-T bygger på kontospesifikasjon og bilagsserier, ikke på ordre-ID i nettbutikken. Hvis koblingen ikke fører med seg kontostreng og bilagsnummer fra ERP tilbake til ordren, må sammenstillingen gjøres manuelt den dagen forespørselen kommer.
| SAF-T-element | Kilde | Vanlig feil i integrasjoner |
|---|---|---|
GeneralLedgerEntries | ERP | ordre uten bilagsnummer i retur |
Customers | ERP, ikke nettbutikken | dublett fordi e-post brukes som nøkkel |
TaxTable | ERP | avgiftskode utledet i nettbutikken |
Products | ERP | varenummer avviker fra SKU |
Kunderaden er den som ryker oftest. Nettbutikken identifiserer kunder på e-post, mens regnskapet identifiserer dem på kundenummer og for virksomheter på organisasjonsnummer. Bruker koblingen e-post som nøkkel, oppstår det dubletter i det øyeblikket samme person handler både privat og på vegne av arbeidsgiveren.
For fakturaformat er EHF standarden, teknisk basert på Peppol BIS Billing 3.0 og sendt gjennom Peppol-nettverket. Mot offentlig sektor er dette obligatorisk, og mottakeren identifiseres på organisasjonsnummer i ELMA-registeret, ikke på e-postadresse. Et oppslag mot mottakerregisteret hører derfor hjemme i integrasjonen, ikke i en manuell rutine.
Rapportering går gjennom Altinn. Poenget for arkitekturen er at Altinn forholder seg til ERP-systemet, ikke til nettbutikken. Det gir en klar retningsregel: nettbutikken er ordrekilde, ERP er bokføringskilde, og ingen avgiftsberegning skal ha nettbutikken som fasit.
Ytelsesbenchmarking, overvåking og operasjonelle SLA-er
En pålitelig integrasjon forutsetter kontinuerlig telemetri for å oppdage ytelsesfall før kundene rammes.
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ OVERVÅKINGSSTAKK FOR INTEGRASJONEN │
└─────────────────────────────────────────────────────────────────────────────────────────┘
[WooCommerce PHP-arbeidere] ──▶ OpenTelemetry Traces ──▶ [Jaeger / Tempo / Datadog]
[Redis Streams Broker] ──▶ Prometheus Exporter ──▶ [Prometheus Server]
[WP-CLI Daemons] ──▶ Custom StatsD Gauges ──▶ │
▼
[Grafana Dashboards]
│
[Alertmanager Varsler]
│
┌─────────────────────┴─────────────────────┐
▼ ▼
[PagerDuty / Opsgenie] [Slack-kanal]
Kritiske beregninger og varslingsterskler
| Beregning | Beskrivelse | Mål-SLA | Advarselsterskel | Kritisk hendelsesterskel |
|---|---|---|---|---|
erp_queue_consumer_lag | Antall ubehandlede meldinger i orders.incoming | < 100 meldinger | > 500 meldinger i 5 min | > 2 500 meldinger eller alder > 15 min |
webhook_ingest_p95_ms | Responstid for inntaks-endepunkt (signatursjekk til 202) | < 25 millisekunder | > 75 millisekunder | > 250 millisekunder |
order_sync_latency_p99 | Tid fra kunden betaler til ordren er bokført i ERP | < 30 sekunder | > 120 sekunder | > 600 sekunder |
dlq_occupancy_count | Antall feilede meldinger i erp:stream:dlq | 0 meldinger | > 10 meldinger | > 50 meldinger |
db_deadlock_rate | Deadlock-frekvens per 1 000 gjennomførte kjøp | 0,00 % | > 0,10 % (1 per 1 000) | > 1,00 % (10 per 1 000) |
Veien videre for utviklingsteamet
Å bygge en robust WooCommerce ERP-integrasjon krever fokus på asynkron frakobling, deterministisk idempotens, transaksjonell databaselåsing og automatiserte avstemmingsprosesser.
Dersom du ønsker å evaluere din eksisterende infrastruktur eller etablere en skalerbar integrasjon med ditt forretningssystem, kan du lese mer om våre WooCommerce ERP-integrasjonstjenester eller kontakte våre erfarne WooCommerce-utviklere hos WPPoland.




