• Czas czytania ~7 min
  • 15.06.2023

Nieco ponad dwa lata temu napisałem samouczek na temat tego, jak powinieneś pracować z usługami innych firm w Laravel. Do dziś jest to najczęściej odwiedzana strona na mojej stronie. Jednak w ciągu ostatnich dwóch lat wiele się zmieniło i postanowiłem ponownie podejść do tego tematu.

Pracuję więc z usługami stron trzecich od tak dawna, że nie pamiętam, kiedy nie byłem. Jako młodszy programista zintegrowałem API z innymi platformami, takimi jak Joomla, Magento i WordPress. Teraz integruje się głównie z moimi aplikacjami Laravel, aby rozszerzyć logikę biznesową, opierając się na innych usługach.

Ten samouczek opisuje, jak zazwyczaj podchodzę do integracji z interfejsem API dzisiaj. Jeśli przeczytałeś mój poprzedni samouczek, czytaj dalej, ponieważ kilka rzeczy się zmieniło - z tego, co uważam za dobre powody.

Zacznijmy od API. Potrzebujemy API do integracji. Mój oryginalny samouczek integrował się z PingPing, doskonałym rozwiązaniem do monitorowania czasu pracy społeczności Laravel. Jednak tym razem chcę wypróbować inne API.

W tym samouczku użyjemy interfejsu API Planetscale.For this tutorial, we will use the Planetscale API. Planetscale to niesamowita usługa bazy danych, której używam, aby moje operacje odczytu i zapisu były bliższe moim użytkownikom w codziennej pracy.

Do czego przyczyni się nasza integracja? Wyobraźmy sobie, że mamy aplikację, która pozwala nam zarządzać naszą infrastrukturą. Nasze serwery działają przez Laravel Forge, a nasza baza danych jest skończona w Planetscale. Nie ma czystego sposobu zarządzania tym przepływem pracy, więc stworzyliśmy własny. W tym celu potrzebujemy integracji lub dwóch.

Początkowo trzymałem moje integracje pod ; app/Servicesjednak, gdy moje aplikacje stały się bardziej rozbudowane i skomplikowane, musiałem używać Services przestrzeni nazw dla usług wewnętrznych, co doprowadziło do zanieczyszczonej przestrzeni nazw. Przeniosłem moje integracje do app/Http/Integrations. Ma to sens i jest sztuczką, którą zaczerpnąłem z Saloon Sama Carrè.

Teraz mógłbym użyć Saloon do integracji API, ale chciałem wyjaśnić, jak to zrobić bez pakietu. Jeśli potrzebujesz integracji API w 2023 roku, gorąco polecam korzystanie z Saloon. To jest niesamowite!

Zacznijmy więc od stworzenia katalogu dla naszej integracji. Możesz użyć następującego polecenia bash:

mkdir app/Http/Integrations/Planetscale

Gdy mamy już katalog Planetscale, musimy stworzyć sposób na połączenie się z nim. Inną konwencją nazewnictwa, którą przejąłem z biblioteki Saloon, jest spojrzenie na te klasy bazowe jako łączniki - ponieważ ich celem jest umożliwienie połączenia się z określonym interfejsem API lub stroną trzecią.

Stwórz nową klasę o nazwie PlanetscaleConnector w app/Http/Integrations/Planetscale katalogu, a my możemy rozwinąć, czego ta klasa potrzebuje, co będzie świetną zabawą.

Musimy więc zarejestrować tę klasę w naszym kontenerze, aby ją rozwiązać lub zbudować wokół niej fasadę. Moglibyśmy zarejestrować to "długo" w dostawcy usług - ale moim najnowszym podejściem jest to, aby te łączniki same się rejestrowały - coś w rodzaju ...

declare(strict_types=1);

namespace App\Http\Integrations\Planetscale;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;

final readonly class PlanetscaleConnector
{
    public function __construct(
        private PendingRequest $request,
    ) {}
    public static function register(Application $app): void
    {
        $app->bind(
            abstract: PlanetscaleConnector::class,
            concrete: fn () => new PlanetscaleConnector(
                request: Http::baseUrl(
                    url: '',
                )->timeout(
                    seconds: 15,
                )->withHeaders(
                    headers: [],
                )->asJson()->acceptJson(),
            ),
        );
    }
}

Chodzi o to, że wszystkie informacje o tym, jak ta klasa jest rejestrowana w kontenerze, znajdują się w samej klasie. Wszystko, co usługodawca musi zrobić, to wywołać metodę rejestru statycznego na zajęciach! Zaoszczędziło mi to wiele czasu podczas integracji z wieloma interfejsami API, ponieważ nie muszę polować na dostawcę i znajdować właściwego wiązania, wśród wielu innych. Idę do klasy, o której mowa, która jest przede mną.

Zauważysz, że obecnie nic nie jest przekazywane do metod tokenu lub podstawowego adresu URL w żądaniu. Naprawmy to dalej. Możesz je zdobyć na swoim koncie Planetscale.

Utwórz następujące rekordy w .env pliku.

PLANETSCALE_SERVICE_ID="your-service-id-goes-here"
PLANETSCALE_SERVICE_TOKEN="your-token-goes-here"
PLANETSCALE_URL="https://api.planetscale.com/v1"

Następnie należy je pobrać do konfiguracji aplikacji. Wszystkie one należąconfig/services.php, ponieważ jest to miejsce, w którym zazwyczaj konfigurowane są usługi innych firm.

return [
    // the rest of your services config

    'planetscale' => [
        'id' => env('PLANETSCALE_SERVICE_ID'),
        'token' => env('PLANETSCALE_SERVICE_TOKEN'),
        'url' => env('PLANETSCALE_URL'),
    ],
];

Teraz możemy z nich korzystać w naszej PlanetscaleConnector metodzie pod rejestrem.

declare(strict_types=1);

namespace App\Http\Integrations\Planetscale;

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;

final readonly class PlanetscaleConnector
{
    public function __construct(
        private PendingRequest $request,
    ) {}
    public static function register(Application $app): void
    {
        $app->bind(
            abstract: PlanetscaleConnector::class,
            concrete: fn () => new PlanetscaleConnector(
                request: Http::baseUrl(
                    url: config('services.planetscale.url'),
                )->timeout(
                    seconds: 15,
                )->withHeaders(
                    headers: [
                'Authorization' => config('services.planetscale.id') . ':' . config('services.planetscale.token'),
            ],
                )->asJson()->acceptJson(),
            ),
        );
    }
}

Musisz wysłać tokeny do Planetscale w następującym formacie: , więc nie możemy użyć domyślnej withToken metody, service-id:service-tokenponieważ nie pozwala nam ona dostosować jej tak, jak potrzebujemy.

Teraz, gdy mamy już stworzoną podstawową klasę, możemy zacząć myśleć o stopniu naszej integracji. Musimy to zrobić podczas tworzenia naszego tokenu usługi, aby dodać poprawne uprawnienia. W naszej aplikacji chcemy mieć możliwość wykonania następujących czynności:
Lista baz danych.
Wyświetl listę regionów bazy danych.
Lista kopii zapasowych baz danych.
Utwórz kopię zapasową bazy danych.
Usuń kopię zapasową bazy danych.

Możemy więc pogrupować je w dwie kategorie:
Baz danych.
Kopie zapasowe.

Dodajmy dwie nowe metody do naszego łącznika, aby utworzyć to, databases czego potrzebujemy:

declare(strict_types=1);

namespace App\Http\Integrations\Planetscale;

use App\Http\Integrations\Planetscale\Resources\BackupResource;
use App\Http\Integrations\Planetscale\Resources\DatabaseResource;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;

final readonly class PlanetscaleConnector
{
    public function __construct(
        private PendingRequest $request,
    ) {}
    public function databases(): DatabaseResource
    {
        return new DatabaseResource(
            connector: $this,
        );
    }
    public function backups(): BackupResource
    {
        return new BackupResource(
            connector: $this,
        );
    }
    public static function register(Application $app): void
    {
        $app->bind(
            abstract: PlanetscaleConnector::class,
            concrete: fn () => new PlanetscaleConnector(
                request: Http::baseUrl(
                    url: config('services.planetscale.url'),
                )->timeout(
                    seconds: 15,
                )->withHeaders(
                    headers: [
                        'Authorization' => config('services.planetscale.id') . ':' . config('services.planetscale.token'),
                    ],
                )->asJson()->acceptJson(),
            ),
        );
    }
}

Jak widać, stworzyliśmy dwie nowe metody i backups. Zwrócą one nowe klasy zasobów, przechodząc przez łącznik. Logikę można teraz zaimplementować w klasach zasobów, ale później musimy dodać inną metodę do naszego łącznika.

<?php
declare(strict_types=1);

namespace App\Http\Integrations\Planetscale\Resources;

use App\Http\Integrations\Planetscale\PlanetscaleConnector;

final readonly class DatabaseResource
{
    public function __construct(
        private PlanetscaleConnector $connector,
    ) {}
    public function list()
    {
        //
    }
    public function regions()
    {
        //
    }
}

To jest nasze DatabaseResource; teraz wymyśliliśmy metody, które chcemy wdrożyć. Możesz zrobić to samo dla BackupResourcepliku . Będzie to wyglądać nieco podobnie.

Tak więc wyniki mogą być podzielone na strony na liście baz danych. Nie będę się jednak tym tutaj zajmował - skłaniałbym się do tego na Saloon, ponieważ jego wdrożenie dla wyników paginowanych jest fantastyczne. W tym przykładzie nie będziemy się martwić o paginację. Zanim wypełnimy DatabaseResource, musimy dodać jeszcze jedną metodę do PlanetscaleConnector ładnego wysyłania żądań. W tym celu używam mojego pakietu o nazwie juststeveking/http-helpers, który ma wyliczenie dla wszystkich typowych metod HTTP, których używam.

public function send(Method $method, string $uri, array $options = []): Response
{
    return $this->request->send(
        method: $method->value,
        url: $uri,
        options: $options,
    )->throw();
}

Teraz możemy wrócić do naszego DatabaseResource i zacząć wypełniać logikę metody list.

declare(strict_types=1);

namespace App\Http\Integrations\Planetscale\Resources;

use App\Http\Integrations\Planetscale\PlanetscaleConnector;
use Illuminate\Support\Collection;
use JustSteveKing\HttpHelpers\Enums\Method;
use Throwable;

final readonly class DatabaseResource
{
    public function __construct(
        private PlanetscaleConnector $connector,
    ) {}
    public function list(string $organization): Collection
    {
        try {
            $response = $this->connector->send(
                method: Method::GET,
                uri: "/organizations/{$organization}/databases"
            );
        } catch (Throwable $exception) {
            throw $exception;
        }
        return $response->collect('data');
    }
    public function regions()
    {
        //
    }
}

Nasza metoda list akceptuje parametr organization do przejścia przez organizację do listy baz danych. Następnie używamy tego do wysyłania żądania do określonego adresu URL za pośrednictwem łącznika. Opakowanie tego w instrukcję try-catch pozwala nam wychwycić potencjalne wyjątki od metody wysyłania łączników. Na koniec możemy zwrócić kolekcję z metody do pracy z nią w naszej aplikacji.

Możemy przejść do bardziej szczegółowych informacji w tym żądaniu, ponieważ możemy zacząć mapować dane z tablic na coś bardziej użytecznego kontekstowo za pomocą DTO. Pisałem o tym tutaj, więc nie będę powtarzał tego samego tutaj.

Spójrzmy szybko na BackupResource coś więcej niż tylko prośbę o uzyskanie.

declare(strict_types=1);

namespace App\Http\Integrations\Planetscale\Resources;

use App\Http\Integrations\Planetscale\Entities\CreateBackup;
use App\Http\Integrations\Planetscale\PlanetscaleConnector;
use JustSteveKing\HttpHelpers\Enums\Method;
use Throwable;

final readonly class BackupResource
{
    public function __construct(
        private PlanetscaleConnector $connector,
    ) {}
    public function create(CreateBackup $entity): array
    {
        try {
            $response = $this->connector->send(
                method: Method::POST,
                uri: "/organizations/{$entity->organization}/databases/{$entity->database}/branches/{$entity->branch}",
                options: $entity->toRequestBody(),
            );
        } catch (Throwable $exception) {
            throw $exception;
        }
        return $response->json('data');
    }
}

Nasza metoda tworzenia akceptuje klasę encji, której używam do przekazywania danych przez aplikację w razie potrzeby. Jest to przydatne, gdy adres URL wymaga zestawu parametrów i musimy wysłać treść żądania.

Nie omówiłem tutaj testowania, ale napisałem samouczek na temat testowania punktów końcowych JSON: API przy użyciu PestPHP tutaj, który będzie miał podobne koncepcje do testowania takiej integracji.

Mogę tworzyć niezawodne i rozszerzalne integracje z osobami trzecimi przy użyciu tego podejścia. Jest podzielony na logiczne części, więc mogę poradzić sobie z ilością logiki. Zazwyczaj miałbym więcej integracji, więc część tej logiki można udostępnić i wyodrębnić w cechy, aby odziedziczyć zachowanie między integracjami.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297