Moje konto

Swagger – czyli jak pisać dokumentację API?

25 kwietnia, 2019
Swagger – czyli jak pisać dokumentację API?

Tworzenie REST API. W dobie szerokiej popularności frontendowych frameworków to chyba najpopularniejsze zadanie backendowca. Dotyczy ono jednak całego zespołu, a w produkcji może okazać się zaskakująco… problematyczne.

Wyobraźmy sobie nowy projekt. Zespół jest kompletny. Grafik, frontendowiec, backendowiec a nawet SCRUM Master. Wszystko wygląda pięknie i zapowiada się dobrze. Wtem! Klient wpada na genialny pomysł – zmieńmy nieco architekturę: od teraz to, co zostało już zrobione, ma działać w inny sposób. Aha, mamy na to 3 dni i musimy sie wyrobić! Brzmi znajomo? Cóż zdarza się.

I tu pojawia się Swagger – cały na biało. Kompletny framework do projektowania, prototypowania i dokumentowania API. Za pomocą prostego pliku yaml, lub json możesz znacząco przyśpieszyć i zwiększyć jakość swojej pracy. Zapisanie endpointu ogranicza się więc do zapisania kilku linijek kodu. Podajemy adres, wejściowe dane, możliwe odpowiedzi z serwera i… dokumentacja gotowa. Klika kliknięć więcej i otrzymujemy działający endpoint, zawierający zmockowane dane.

Tak oto frontend może działać bez ostatecznego rozwiązania, a backend bez presji tworzyć wysokiej jakości kod. Na koniec klient nie tylko dostaje to czego chciał, ale też w pełni profesjonalną dokumentację, która… napisała się sama!

Brzmi dobrze? To przejdźmy do konkretów i napiszmy swój pierwszy projekt w Swaggerze.

Konfiguracja Swaggera i Swagger Editor

Do napisania naszego pierwszego projektu wykorzystamy Swagger Editor https://swagger.io/tools/swagger-editor/. Dla ułatwienia, śmiało można korzystać z jego webowej wersji dostępnej pod adresem https://editor.swagger.io/. Oczywiście trzeba wspomnieć o dodatkowych rozwiązaniach. W PHP np. cały kod można pisać w komentarzach, z wykorzystaniem biblioteki swagger-php.

Gdy odpalisz już edytor, zobaczysz w nim przykładowo opisane API. Wykorzystuje ono jednak aktualnie wersję 2.0, więc nas nie interesuje. Od czasu gdy Swagger korzysta z Open API, pojawiło się wiele zmian i jeśli byłeś zaznajomiony z poprzednią wersją – zerknij na to video:

By jednak nie zaczynać od zera, pobierz plik z przykładową dokumentacją napisaną w najnowszej wersji.

Pierwsza linia pliku yaml, opisuje specyfikację, w ramach której będziemy się poruszać – jest to openapi 3.0.0. W kolejnej linii, podajemy już informacje dotyczące bezpośrednio naszego API:

info:
  version: 1.0.0
  title: Swagger Petstore
  description: This is example descripton of our documentation in Swagger.
  license:
    name: MIT

W naszej dokumentacji, zgodnie z tym, jak skonstruowany jest YAML, bardzo ważną sprawą są wcięcia. Elementy zależne od rodzica zawsze są wcięte tabulatorem. Powyższy kod zawiera informacje, że wersja naszego API to 1.0.0. Że nazwa projektu to „Swagger Petstore”, oraz że jest ona licencjonowana na zasadach MIT. Kilka prostych linii automatyczne dodało bardzo ładne informacje w podglądzie, po prawej stronie. Zero kodu HTML, czy tym gorzej, ręcznej zmiany czcionek w jakimś edytorze tekstu. Wszystko robi się automatycznie. 

Gdy podstawa już gotowa, dodajmy informacje o serwerze pod którym dostępne będzie opisywane API. Nie jesteśmy tu ograniczani do jednego adresu. Dodanie kilku spowoduje możliwość wyboru konkretnego serwera do testów. Oczywiście możemy je z łatwością opisać by wiedzieć do czego służą.

servers:
  - url: http://mock.example.com
    description: mock of data
  - url: http://api.example.com 
  - url: https://{customerId}.example.com:{port}
    variables:
      customerId:
        default: demo
        description: Customer ID assigned by the service provider
      port:
        enum:
          - '443'
          - '8443'
        default: '443'

Z prawidłowo podanymi serwerami Swagger będzie potrafił się połączyć. co umożliwi nam realizowanie zapytań w poszczególnych endpointach. Jeśli interesuje Cię to nieco bardziej – zerknij na dokumentację: (https://swagger.io/docs/specification/api-host-and-base-path/).

Pierwszy endpoint w Swaggerze

To, co interesuje nas z pewnością najbardziej to endpointy. Zakładam, że rozumiesz zasady REST API i nie muszę Ci wyjaśniać różnicy między POST, PUT, GET i DELETE. Swagger dba o to, byś stosował standardy, a poszczególne rodzaje metody HTTP rozróżnia kolorami. 

Napiszmy sobie teraz prostego CRUD’a dla wymyślonego sklepu ze zwierzętami. Będziemy do tego potrzebować:

  • listy wszystkich zwierząt, 
  • wyświetlenia pojedynczego zwierzaka, 
  • dodania nowego zwierzaka, 
  • aktualizacji
  • usuwania

W przypadku trzech ostatnich operacji, wymagać będziemy dodatkowo uwierzytelnienia. Nie każdy może dodać, usunąć czy zaktualizować dane w sklepie. Swagger umożliwia wprowadzanie autoryzacji na wiele różnych sposobów.

Zacznijmy od listy.

paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A paged array of pets
        default:
          description: unexpected error

 Zaczynając od zdefiniowania klucza paths, opisujemy kolejne ścieżki na naszym serwerze. I tak dla przykładu podanego wcześniej, spodziewanym adresem listy zwierzaków, będzie http://mock.example.com/pets po wykonaniu metody GET. 

Jedynym parametrem naszego zapytania będzie limit. Określamy tutaj, że zależy nam na liczbie całkowitej w formacie int32. W odpowiedzi możemy dostać albo kod 200, albo błąd. Zobacz, co ten prosty kawałek kodu zrobił nam w podglądzie. Wszystko jasno opisane, wszyscy wiedzą czego oczekujemy. 

Poza kodem odpowiedzi, możemy również stosunkowo łatwo zdefiniować jakie dane odpowiedź będzie zawierała:

paths:
  /pets:
    get:
     ...
     responses:
        '200':
          description: A paged array of pets
          content:
            application/json:    
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

Nasze odpowiedzi rozszerzyły się o content. W formacie application/json będzie on przekazywał dane opisane w schemacie.

Schematy danych w Swagger

Schematy danych w Swaggerze, to po prostu obiekty zawierające odpowiednie dane. Jeśli programujesz w Laravel lub Symfony, będą to odpowiedniki klas Response.

W naszym przykładzie użyliśmy schematów #/components/schemas/Pets oraz #/components/schemas/Error. Zerknijmy więc na kod, schematów:

components:
  schemas:
    Pet:
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Pets:
      type: array
      items:
        $ref: "#/components/schemas/Pet"
    Error:
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string

Mamy tu opisane 3 klasy, czyli Pet – jedno zwierze, Pets, czyli tablicę zawierającą wiele obiektów Pet oraz Error.

Schemat jednego obiektu Pet zawiera parametry: 

  • id – liczbę całkowitą zapisaną w int64, 
  • name – string 
  • tag – string. 

Mamy również określone, że pola, takie jak id oraz name zawsze są wymagane.

Analogicznie odpowiedź błędu, wymaga wszystkich swoich pól code (int32) oraz message (string).

Po dodaniu powyższego kodu, nie tylko będzie on zwracany przy testowym wywołaniu, ale również pojawi się one jako Schemas w dokumentacji. W ten sposób szybko będziesz w stanie odnaleźć obiekty gdy będziesz chciał je opisać, lub z łatwością rozszerzysz je o wymagane pola.

Endpoint dla dodania nowego zwierzęcia – POST dla pets

Swaggera mamy opanowanego, czas więc na konkrety i pozostałe, zaplanowane endpointy.

Dodajmy teraz możliwość dodania zwierzaka. Adresem pod którym chcielibyśmy realizować tą akcję powinien być /pets, z tym że z wykorzystaniem metody POST.

paths:
  /pets:
    ...
    post:
      summary: Add a pet
      operationId: createPet
      requestBody:
        description: Pet to add to the store
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Pet'
      responses:
        '200':
          description: A new added pet
          content:
            application/json:    
              schema:
                $ref: "#/components/schemas/Pet"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

Kod mniej lub bardziej powinien być już dla Ciebie zrozumiały. Dodaliśmy zapytanie – oczywiście pamiętając o odpowiednich wcięciach, czyli tab od paths, tab od /pets i konkretna metoda. Można zauważyć, że nie podajemy tutaj parametrów osobno, przyjmując jako requestBody również schemat. To może się przydać, jeśli nie jesteśmy pewni jakie dokładnie dane będą potrzebne, ale trzeba mieć na uwadze, że w tym wypadku nie otrzymamy ładnego formularza.

Ważne jest również zwrócenie uwagi na to, że każdy endpoint musi mieć indywidualny identyfikator (createPet).

Pobieranie poj. zwierzaka, czyli parametry w URL

Po dodaniu tych 2 metod, dodanie kolejnych będzie już pestką. Przejdźmy do metody umożliwiającej pobranie konkretnego zwierzaka. Teraz chcemy dodać /pets/1 dla ID: 1 /pets/2 dla ID: 2 itd. Można to osiągnąć w prosty sposób – dodając inny typ parametru.

paths:
  ...
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

Do tej pory nasze parametry zawsze trafiały do query. Dla  parametrów, za pośrednictwem klucza w in możemy podać również inne wartości, takie jak:

  • path – ścieżka w adresie URL – jak w przykładzie
  • query – zawartość zapytania 
  • header – czyli nagłówek HTTP
  • cookie – ciasteczka

Oczywiście trzeba zwrócić uwagę, że w przypadku path nazwa parametru musi być taka sama, jak podana w klamrach, czyli {petId}.

Teraz chcemy, by dany obiekt Pet był edytowalny – chcemy wyedytować konkretnego zwierzaka więc również w URL podamy identyfikator.

paths:
  ...
  /pets/{petId}:
    put:
      summary: Info for a specific pet
      operationId: showPetById
      parameters:
        - name: petId
          in: path
          required: true
          description: The id of the pet to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Expected response to a valid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Pets"
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

Wszystkie te elementy już wcześniej były omówione. Jedyne, co zmieniliśmy to dodanie metody PUT – proste? Proste!

Korzyści, korzyści i jeszcze raz korzyści!

Dokumentacja załatwiona, wygląda całkiem nieźle. Za pomocą przycisku generate client w kilka sekund możemy wygenerować plik HTML z porządną dokumentacją. 

Wyobraźmy sobie jednak, że nie mamy tego pliku i uparcie pisaliśmy wszystko w html’u, lub Wordzie. Klient prosi o dodanie do zwierzaka parametru price, którego wcześniej nie było. Kłopot?

Kłopot. Musimy znaleźć wszystkie endpointy, wszystkie je zaktualizować i dokładnie sprawdzić czy aby na pewno nie popełniliśmy błędu. W przypadku Swaggera wystarczą dosłownie 2 linie kodu i sprawa załatwiona.

       price:
          type: number

Swaggera jak widzisz, jest stosunkowo łatwy do zrozumienia. Dodatkowo dzięki zastosowaniu narzędzi takich jak mockowanie i generowanie kodu (https://github.com/swagger-api/swagger-codegen) nie musimy blokować swojej wspólnej pracy. Jeśli zaczniesz od projektowania, dostarczysz rozwiązanie, które pozwoli usatysfakcjonować wszystkich i nie spowodujesz niepotrzebnych opóźnień. Udostępnij ten artykuł znajomym i zacznij zabawę Swaggerem, naprawdę warto!

Posted in Dokumentacja, Programowanie
Related Posts