Vodiči

Native PHP 8.3: Enrich Quote Requests with Company Data Seamlessly

Native PHP 8.3: Besprijekorno obogatite zahtjeve za ponude podacima o tvrtki

Obrazac za ponudu trebao bi djelovati trenutačno, čak i kada korisni posao započinje nakon što kupac pritisne Pošalji. Freelancer ili mali razvojni tim možda želi znati koja tvrtka traži ponudu, ali upućivanje vanjskog zahtjeva za obogaćivanje podataka unutar HTTP životnog ciklusa obrasca uvodi nepotrebnu latenciju i još jedan razlog zbog kojeg slanje može ne uspjeti.

Ovaj projekt rješava tu napetost malom, za produkciju osmišljenom Native PHP 8.3 aplikacijom. Javni krajnji punkt provjerava i pohranjuje zahtjev za ponudu, stavlja posao obogaćivanja u red čekanja u SQLiteu i odmah vraća 202 Accepted. Zaseban radnik pretvara poslanu javnu web-stranicu u strukturirane podatke o tvrtki i kontaktu putem API-ja Website to Company data.

Rezultat je namjerno skromna arhitektura: PHP, PDO, izvorni cURL i radnik nadziran operacijskim sustavom. Nema okvira ni posrednika poruka kojima treba upravljati, no važne granice ostaju jasne i provjerljive.

Dobijte pristup i kopirajte servisni token

  1. Izradite račun na https://ai.mihajlo.mk/register ili upotrijebite https://ai.mihajlo.mk/login ako ga već imate.
  2. Otvorite stranicu usluge Website to Company data.
  3. Odaberite dostupni paket Free, Plus ili Pro i dovršite njegovu aktivaciju.
  4. Otvorite službenu dokumentaciju usluge.
  5. Pronađite ploču Service token i kopirajte ondje prikazani token za tu uslugu.

Ova usluga nije bez tokena. Svaki zahtjev mora poslati vjerodajnicu putem parametra upita token={serviceToken}. Ponovno generiranje servisnog tokena opoziva prethodno aktivni token, stoga rotaciju tretirajte kao usklađenu promjenu pri implementaciji, a ne kao usputnu radnju na kontrolnoj ploči.

Potvrdite točan API poziv

Integracija koristi GET https://ai.mihajlo.mk/api/website-to-company-data/v1/extract. Šalje i token i website kao parametre upita. Prije pisanja aplikacijskog koda izvršite jedan minimalan zahtjev iz pouzdanog terminala:

read -sr -p "Service token: " SERVICE_TOKEN
curl --fail-with-body --silent --show-error --get \
  "https://ai.mihajlo.mk/api/website-to-company-data/v1/extract" \
  --data-urlencode "token=${SERVICE_TOKEN}" \
  --data-urlencode "website=https://example.com"
unset SERVICE_TOKEN

--data-urlencode sprječava oštećivanje niza upita. Izbjegavajte izravno upisivanje tokena u naredbu jer povijest ljuske, pregled procesa i kopirani izlaz terminala mogu ga otkriti.

Sada izradite .env.local izvan javnog direktorija:

SERVICE_TOKEN="YOUR_SERVICE_TOKEN"
DATABASE_PATH="/var/lib/quote-enricher/quotes.sqlite"

Dodajte .env.local, SQLite bazu podataka i njezine popratne datoteke u .gitignore. Pri implementaciji preferirajte stvarne varijable okruženja koje pruža upravitelj usluge ili spremište tajni. Lokalna datoteka je pogodnost za razvoj, a ne nešto što treba predati u repozitorij ili smjestiti ispod web korijena.

Arhitektura i struktura projekta

Put zahtjeva i put obogaćivanja imaju različite odgovornosti:

  • HTTP krajnji punkt provjerava ponudu, umeće je i stvara posao u istoj transakciji baze podataka.
  • Radnik preuzima jedan spreman posao, poziva vanjsku uslugu, preslikava njezin odgovor u domenske podatke i ažurira ponudu.
  • Privremeni neuspjesi vraćaju se u red čekanja uz ograničeno eksponencijalno odgađanje. Neuspjesi autentikacije, zahtjeva i sheme označavaju se kao konačni.

Ovaj dizajn u stilu transakcijskog outboxa sprječava da pohranjena ponuda izgubi odgovarajući posao. SQLite je prikladan za jednu malu implementaciju kada se baza podataka nalazi na trajnoj lokalnoj pohrani i postoji samo nekoliko radnika. Više aplikacijskih čvorova trebalo bi koristiti zajedničku transakcijsku bazu podataka i isti obrazac uvjetnog preuzimanja.

quote-enricher/
  bin/migrate.php
  bin/worker.php
  public/index.php
  src/CompanyData.php
  src/Http.php
  src/WebsiteCompanyClient.php
  tests/WebsiteCompanyClientTest.php
  bootstrap.php
  composer.json
  phpunit.xml
  .env.local

Izvršno okruženje treba PHP 8.3 s curl, pdo_sqlite i json. Composer se koristi samo za automatsko učitavanje i PHPUnit:

{
  "require": {
    "php": "^8.3",
    "ext-curl": "*",
    "ext-json": "*",
    "ext-pdo": "*",
    "ext-pdo_sqlite": "*"
  },
  "require-dev": {
    "phpunit/phpunit": "^11.0"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}
composer install
composer dump-autoload --optimize
php bin/migrate.php

Izradite bazu podataka i pokretanje konfiguracije

Pokretanje najprije učitava lokalnu datoteku, a zatim dopušta da je stvarne varijable okruženja nadjačaju. Također konfigurira SQLite za istodobna čitanja i ograničeno čekanje zaključavanja.

<?php
// bootstrap.php
declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

$local = [];
$file = __DIR__ . '/.env.local';
if (is_file($file)) {
    $local = parse_ini_file($file, false, INI_SCANNER_RAW);
    if ($local === false) {
        throw new RuntimeException('Cannot parse .env.local');
    }
}

$config = [
    'token' => getenv('SERVICE_TOKEN') ?: ($local['SERVICE_TOKEN'] ?? ''),
    'database' => getenv('DATABASE_PATH')
        ?: ($local['DATABASE_PATH'] ?? __DIR__ . '/var/quotes.sqlite'),
];

if ($config['token'] === '') {
    throw new RuntimeException('SERVICE_TOKEN is required');
}

$pdo = new PDO('sqlite:' . $config['database'], options: [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$pdo->exec('PRAGMA journal_mode=WAL');
$pdo->exec('PRAGMA busy_timeout=5000');

return [$config, $pdo];
<?php
// bin/migrate.php
declare(strict_types=1);

[, $pdo] = require dirname(__DIR__) . '/bootstrap.php';

$pdo->exec(<<<'SQL'
CREATE TABLE IF NOT EXISTS quote_requests (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    website TEXT NOT NULL,
    brief TEXT NOT NULL,
    enrichment_status TEXT NOT NULL DEFAULT 'queued',
    company_data TEXT,
    created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS enrichment_jobs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    quote_id INTEGER NOT NULL UNIQUE,
    status TEXT NOT NULL DEFAULT 'queued',
    attempts INTEGER NOT NULL DEFAULT 0,
    available_at TEXT NOT NULL,
    last_error TEXT,
    FOREIGN KEY (quote_id) REFERENCES quote_requests(id)
);
CREATE INDEX IF NOT EXISTS enrichment_ready
    ON enrichment_jobs(status, available_at);
SQL);

echo "Database ready\n";

Izolirajte HTTP i preslikajte vanjski odgovor

Transport upravlja mehanikom povezivanja; klijent upravlja API pravilima. Odvajanje tih odgovornosti omogućuje determinističke testove bez mrežnih poziva.

<?php
// src/Http.php
namespace App;

final readonly class HttpResponse
{
    public function __construct(
        public int $status,
        public string $body,
        public ?string $networkError = null,
    ) {}
}

interface Transport
{
    public function get(string $url, array $query): HttpResponse;
}

final class CurlTransport implements Transport
{
    public function get(string $url, array $query): HttpResponse
    {
        $requestUrl = $url . '?' . http_build_query(
            $query, '', '&', PHP_QUERY_RFC3986
        );
        $handle = curl_init($requestUrl);
        if ($handle === false) {
            return new HttpResponse(0, '', 'initialization_failed');
        }

        curl_setopt_array($handle, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_CONNECTTIMEOUT_MS => 1500,
            CURLOPT_TIMEOUT_MS => 5000,
            CURLOPT_USERAGENT => 'quote-enricher/1.0',
        ]);

        $body = curl_exec($handle);
        $error = $body === false ? curl_error($handle) : null;
        $status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
        curl_close($handle);

        return new HttpResponse($status, $body === false ? '' : $body, $error);
    }
}

Dostavljeni ugovor navodi company, contact, email, phone i people, ali ne opravdava pretpostavku da će svaka vrijednost uvijek biti popunjena ili skalarna. Granica u nastavku normalizira nedostajuće vrijednosti i odbacuje nekompatibilne oblike umjesto da dopušta širenje proizvoljnih podataka odgovora kroz aplikaciju.

<?php
// src/CompanyData.php
namespace App;

use JsonSerializable;
use UnexpectedValueException;

final readonly class CompanyData implements JsonSerializable
{
    public function __construct(
        public array $company,
        public array $contact,
        public array $email,
        public array $phone,
        public array $people,
    ) {}

    public static function fromPayload(array $payload): self
    {
        return new self(
            self::object($payload['company'] ?? null, 'company'),
            self::object($payload['contact'] ?? null, 'contact'),
            self::strings($payload['email'] ?? null, 'email'),
            self::strings($payload['phone'] ?? null, 'phone'),
            self::people($payload['people'] ?? null),
        );
    }

    private static function object(mixed $value, string $field): array
    {
        if ($value === null) return [];
        if (is_string($value)) return ['value' => $value];
        if (is_array($value)) return $value;
        throw new UnexpectedValueException("Invalid {$field} data");
    }

    private static function strings(mixed $value, string $field): array
    {
        if ($value === null || $value === '') return [];
        if (is_string($value)) return [$value];
        if (is_array($value)) {
            foreach ($value as $item) {
                if (!is_string($item)) {
                    throw new UnexpectedValueException("Invalid {$field} data");
                }
            }
            return array_values($value);
        }
        throw new UnexpectedValueException("Invalid {$field} data");
    }

    private static function people(mixed $value): array
    {
        if ($value === null) return [];
        if (!is_array($value)) {
            throw new UnexpectedValueException('Invalid people data');
        }

        return array_values(array_map(
            static function (mixed $person): array {
                if (is_string($person)) return ['name' => $person];
                if (is_array($person)) return $person;
                throw new UnexpectedValueException('Invalid person data');
            },
            $value
        ));
    }

    public function jsonSerialize(): array
    {
        return get_object_vars($this);
    }
}

Primijenite ograničene ponovne pokušaje i strukturirane neuspjehe

Mrežni neuspjesi, odgovori 429 i pogreške poslužitelja mogu biti privremeni. Autentikacija i druge pogreške klijenta uglavnom nisu. Klijent izvodi najviše tri kratka pokušaja; trajni red poslova upravlja duljim odgodama.

<?php
// src/WebsiteCompanyClient.php
namespace App;

use Closure;
use JsonException;
use RuntimeException;
use Throwable;

final class EnrichmentFailure extends RuntimeException
{
    public function __construct(
        public readonly string $kind,
        public readonly bool $retryable,
    ) {
        parent::__construct($kind);
    }
}

final class WebsiteCompanyClient
{
    private const ENDPOINT =
        'https://ai.mihajlo.mk/api/website-to-company-data/v1/extract';

    private Closure $sleep;

    public function __construct(
        private readonly Transport $transport,
        private readonly string $token,
        ?callable $sleep = null,
    ) {
        $this->sleep = $sleep === null
            ? static fn(int $milliseconds) => usleep($milliseconds * 1000)
            : Closure::fromCallable($sleep);
    }

    public function extract(string $website): CompanyData
    {
        foreach ([200, 600, 0] as $attempt => $delay) {
            $response = $this->transport->get(self::ENDPOINT, [
                'token' => $this->token,
                'website' => $website,
            ]);

            $temporary = $response->networkError !== null
                || $response->status === 429
                || $response->status >= 500;

            if ($temporary && $attempt < 2) {
                ($this->sleep)($delay);
                continue;
            }
            if ($temporary) {
                throw new EnrichmentFailure('temporary_api_failure', true);
            }
            if (in_array($response->status, [401, 403], true)) {
                throw new EnrichmentFailure('authentication_failure', false);
            }
            if ($response->status < 200 || $response->status >= 300) {
                throw new EnrichmentFailure('request_rejected', false);
            }

            try {
                $payload = json_decode(
                    $response->body, true, flags: JSON_THROW_ON_ERROR
                );
                if (!is_array($payload)) {
                    throw new JsonException('Expected an object');
                }
                return CompanyData::fromPayload($payload);
            } catch (Throwable) {
                throw new EnrichmentFailure('invalid_response_schema', false);
            }
        }

        throw new EnrichmentFailure('retry_exhausted', true);
    }
}

Prihvatite ponudu bez čekanja na obogaćivanje

Javni krajnji punkt provjerava veličinu i oblik prije otvaranja transakcije. Tijekom zahtjeva ne pohranjuje udaljene podatke, stoga prekid rada API-ja ne može spriječiti prihvaćanje ponude.

<?php
// public/index.php
declare(strict_types=1);

[, $pdo] = require dirname(__DIR__) . '/bootstrap.php';
header('Content-Type: application/json');

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    header('Allow: POST');
    echo json_encode(['error' => 'method_not_allowed']);
    exit;
}

$name = trim((string) ($_POST['name'] ?? ''));
$email = trim((string) ($_POST['email'] ?? ''));
$website = trim((string) ($_POST['website'] ?? ''));
$brief = trim((string) ($_POST['brief'] ?? ''));
$scheme = strtolower((string) parse_url($website, PHP_URL_SCHEME));

$valid = $name !== ''
    && strlen($name) <= 120
    && filter_var($email, FILTER_VALIDATE_EMAIL)
    && strlen($email) <= 254
    && filter_var($website, FILTER_VALIDATE_URL)
    && in_array($scheme, ['http', 'https'], true)
    && parse_url($website, PHP_URL_HOST) !== null
    && $brief !== ''
    && strlen($brief) <= 5000;

if (!$valid) {
    http_response_code(422);
    echo json_encode(['error' => 'invalid_quote']);
    exit;
}

$now = gmdate('Y-m-d\TH:i:s\Z');

try {
    $pdo->beginTransaction();

    $statement = $pdo->prepare(
        'INSERT INTO quote_requests
         (name, email, website, brief, created_at)
         VALUES (?, ?, ?, ?, ?)'
    );
    $statement->execute([$name, $email, $website, $brief, $now]);
    $quoteId = (int) $pdo->lastInsertId();

    $job = $pdo->prepare(
        'INSERT INTO enrichment_jobs (quote_id, available_at)
         VALUES (?, ?)'
    );
    $job->execute([$quoteId, $now]);
    $pdo->commit();

    http_response_code(202);
    echo json_encode(['quote_id' => $quoteId, 'status' => 'accepted']);
} catch (Throwable $exception) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    error_log(json_encode(['event' => 'quote_store_failed']));
    http_response_code(503);
    echo json_encode(['error' => 'temporarily_unavailable']);
}

Implementacija usmjerena na preglednik trebala bi također primijeniti CSRF zaštitu, ograničavanje učestalosti, promišljenu CORS politiku i kontrole botova primjerene obrascu. Ako su dvostruka slanja važna, prihvatite ključ idempotentnosti i provedite ga jedinstvenim ograničenjem baze podataka.

Pokrenite radnika za obogaćivanje

Radnik preuzima posao uvjetnim ažuriranjem, tako da dva procesa ne mogu namjerno izvršiti isti red u čekanju. Uspjeh pohranjuje preslikani JSON. Privremeni neuspjeh zakazuje drugi pokušaj; konačni neuspjeh zadržava strukturirani status za podršku i izvještavanje.

<?php
// bin/worker.php
declare(strict_types=1);

use App\CurlTransport;
use App\EnrichmentFailure;
use App\WebsiteCompanyClient;

[$config, $pdo] = require dirname(__DIR__) . '/bootstrap.php';
$now = gmdate('Y-m-d\TH:i:s\Z');

$pdo->beginTransaction();
$select = $pdo->prepare(
    "SELECT j.*, q.website
     FROM enrichment_jobs j
     JOIN quote_requests q ON q.id = j.quote_id
     WHERE j.status = 'queued' AND j.available_at <= ?
     ORDER BY j.id LIMIT 1"
);
$select->execute([$now]);
$job = $select->fetch();

if (!$job) {
    $pdo->commit();
    exit(0);
}

$claim = $pdo->prepare(
    "UPDATE enrichment_jobs SET status = 'running'
     WHERE id = ? AND status = 'queued'"
);
$claim->execute([$job['id']]);
if ($claim->rowCount() !== 1) {
    $pdo->rollBack();
    exit(0);
}
$pdo->commit();

$client = new WebsiteCompanyClient(
    new CurlTransport(),
    $config['token']
);

try {
    $data = $client->extract($job['website']);
    $pdo->beginTransaction();
    $update = $pdo->prepare(
        "UPDATE quote_requests
         SET company_data = ?, enrichment_status = 'complete'
         WHERE id = ?"
    );
    $update->execute([
        json_encode($data, JSON_THROW_ON_ERROR),
        $job['quote_id'],
    ]);
    $pdo->prepare('DELETE FROM enrichment_jobs WHERE id = ?')
        ->execute([$job['id']]);
    $pdo->commit();

    error_log(json_encode([
        'event' => 'enrichment_complete',
        'quote_id' => (int) $job['quote_id'],
    ]));
} catch (EnrichmentFailure $failure) {
    $attempts = (int) $job['attempts'] + 1;
    $retry = $failure->retryable && $attempts < 5;
    $delay = min(3600, 60 * (2 ** $attempts));
    $available = gmdate('Y-m-d\TH:i:s\Z', time() + $delay);

    $pdo->prepare(
        'UPDATE enrichment_jobs
         SET status = ?, attempts = ?, available_at = ?, last_error = ?
         WHERE id = ?'
    )->execute([
        $retry ? 'queued' : 'failed',
        $attempts,
        $available,
        $failure->kind,
        $job['id'],
    ]);

    if (!$retry) {
        $pdo->prepare(
            "UPDATE quote_requests SET enrichment_status = 'failed'
             WHERE id = ?"
        )->execute([$job['quote_id']]);
    }

    error_log(json_encode([
        'event' => 'enrichment_failed',
        'quote_id' => (int) $job['quote_id'],
        'kind' => $failure->kind,
        'retrying' => $retry,
    ]));
    exit($retry ? 75 : 1);
}

Zapisnici namjerno izostavljaju token, web-stranicu, e-poštu, tijelo odgovora i izdvojene osobe. Umjesto toga bilježite identifikatore i strukturirane vrste neuspjeha. Budući da autentikacija koristi parametar upita, nikada ne zapisujte konstruirani URL zahtjeva.

Pokrećite radnika više puta putem systemd timera, crona ili nadzirane petlje. Produkcijski nadzornik trebao bi pokrenuti više procesa tek nakon provjere SQLite sukobljavanja pri očekivanom radnom opterećenju. Također bi trebao oporaviti poslove koji su nakon rušenja procesa ostali u stanju running, koristeći vremensku oznaku preuzimanja i konzervativni prag zastarjelog posla.

Testirajte granicu bez pozivanja usluge

Deterministički lažni transport provjerava pravila preslikavanja i ponovnih pokušaja. Nijedna testna datoteka ne sadrži stvarnu vjerodajnicu.

<?php
// tests/WebsiteCompanyClientTest.php
declare(strict_types=1);

use App\EnrichmentFailure;
use App\HttpResponse;
use App\Transport;
use App\WebsiteCompanyClient;
use PHPUnit\Framework\TestCase;

final class FakeTransport implements Transport
{
    public int $calls = 0;

    public function __construct(private array $responses) {}

    public function get(string $url, array $query): HttpResponse
    {
        $this->calls++;
        return array_shift($this->responses);
    }
}

final class WebsiteCompanyClientTest extends TestCase
{
    public function testMapsAValidResponse(): void
    {
        $fake = new FakeTransport([new HttpResponse(200, json_encode([
            'company' => ['name' => 'Example Ltd'],
            'contact' => ['city' => 'Skopje'],
            'email' => '[email protected]',
            'phone' => ['+389000000'],
            'people' => [['name' => 'Alex', 'role' => 'Owner']],
        ]))]);

        $data = (new WebsiteCompanyClient(
            $fake, 'test-token', static fn(int $ms) => null
        ))->extract('https://example.com');

        self::assertSame(['[email protected]'], $data->email);
        self::assertSame('Example Ltd', $data->company['name']);
        self::assertSame(1, $fake->calls);
    }

    public function testRetriesRateLimitThenSucceeds(): void
    {
        $fake = new FakeTransport([
            new HttpResponse(429, '{}'),
            new HttpResponse(200, json_encode([
                'company' => [], 'contact' => [],
                'email' => [], 'phone' => [], 'people' => [],
            ])),
        ]);

        (new WebsiteCompanyClient(
            $fake, 'test-token', static fn(int $ms) => null
        ))->extract('https://example.com');

        self::assertSame(2, $fake->calls);
    }

    public function testDoesNotRetryAuthenticationFailure(): void
    {
        $fake = new FakeTransport([new HttpResponse(401, '{}')]);

        try {
            (new WebsiteCompanyClient(
                $fake, 'bad-token', static fn(int $ms) => null
            ))->extract('https://example.com');
            self::fail('Expected EnrichmentFailure');
        } catch (EnrichmentFailure $failure) {
            self::assertSame('authentication_failure', $failure->kind);
            self::assertFalse($failure->retryable);
            self::assertSame(1, $fake->calls);
        }
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php" colors="true">
  <testsuites>
    <testsuite name="unit">
      <directory>tests</directory>
    </testsuite>
  </testsuites>
</phpunit>

Pokrenite paket testova pomoću vendor/bin/phpunit. Dodajte testove krajnjih točaka nad privremenom SQLite datotekom prije proširivanja ponašanja provjere ili idempotentnosti.

Implementirajte, promatrajte i dijagnosticirajte

Poslužujte samo public/ putem PHP-FPM-a ili razvojnog poslužitelja. Pobrinite se da web proces i radnik mogu pristupiti direktoriju baze podataka, dok ga drugi korisnici sustava ne mogu čitati. Držite bazu podataka na trajnoj pohrani, dosljedno je sigurnosno kopirajte i nemojte smještati aktivnu SQLite bazu podataka na neprikladni mrežni datotečni sustav.

Pratite broj poslova u čekanju, pokrenutih, neuspjelih i neuobičajeno starih poslova. Zasebno upozoravajte na authentication_failure, jer to često ukazuje na nedostajuću varijablu okruženja ili ponovno generirani token čija zamjena nije stigla do svakog radnika. Dugotrajni temporary_api_failure može predstavljati poteškoće s povezivanjem, pogreške usluge, pritisak kvote ili ograničavanje učestalosti; red čekanja trebao bi ga apsorbirati bez utjecaja na prihvaćanje ponude.

Uobičajeni obrasci neuspjeha su konkretni:

  • Neposredni 401 ili 403: provjerite token za tu uslugu i ponovno pokrenite radnike nakon ažuriranja okruženja. Nemojte naslijepo ponavljati pokušaje.
  • Ponovljeni odgovori 429: smanjite istodobnost radnika, zadržite trajno odgađanje i provjerite kapacitet paketa.
  • Nevaljana shema odgovora: zadržite ponudu, označite obogaćivanje neuspjelim i usporedite preslikavač granice sa službenom dokumentacijom prije nego što ga promijenite.
  • Baza podataka zaključana: skratite transakcije, osigurajte da se mrežni pozivi nikada ne događaju unutar njih i smanjite broj istodobnih SQLite pisaca.
  • Poslovi ostaju pokrenuti: dodajte vremensku oznaku preuzimanja i politiku oporavka zastarjelih poslova prije oslanjanja na automatski oporavak nakon rušenja.

Završni kontrolni popis za provjeru

  • Dovršeni su tijek registracije, aktivacije paketa, dokumentacije i servisnog tokena.
  • Token postoji samo u konfiguraciji podržanoj okruženjem i ne nalazi se u izvornom kodu, testnim datotekama, zapisnicima ni povijesti naredbi.
  • Valjano slanje obrasca vraća 202 prije početka bilo kojeg zahtjeva za obogaćivanje.
  • Ponuda i posao stvaraju se atomski.
  • Radnik poziva točan GET krajnji punkt s parametrima upita token i website.
  • Podaci o tvrtki, kontaktu, e-pošti, telefonu i osobama provjeravaju se i preslikavaju na API granici.
  • Vremenska ograničenja su ograničena, privremeni neuspjesi se odgađaju, a neuspjesi autentikacije ili provjere ne ponavljaju se naslijepo.
  • PHPUnit prolazi s determinističkim lažnim transportom.
  • Produkcijski nadzor može razlikovati kašnjenje reda čekanja, ograničavanje učestalosti, neuspjeh autentikacije i neuspjeh sheme.

Važna značajka nije samo to što web-stranica postaje podatak o tvrtki. Važno je da ponuda ostaje pouzdana kada je obogaćivanje sporo, nedostupno, ograničeno učestalošću ili neispravno oblikovano. Brzo prihvaćanje, trajni posao, stroga aplikacijska granica i suzdržano zapisivanje pretvaraju koristan API poziv u produkcijsku integraciju kojoj se može vjerovati.

Portret autora bloga

Mihajlo

Ja sam Mihajlo — programer vođen znatiželjom, disciplinom i stalnom željom da stvorim nešto smisleno. Dijelim uvide, tutorijale i besplatne usluge kako bih pomogao drugima da pojednostave svoj rad i rastu u svijetu softvera i umjetne inteligencije koji se neprestano razvija.