Eine Geschichte über die Entwicklung eines weichen Typs mit Firestore + Python + OpenAPI + Typescript

Überblick

Wenn Sie einen Typ mit Pythons Own ORM (fsglue) in einem schemenlosen Firestore definieren und mit OpenAPI oder Typescript kombinieren, einer flexiblen, aber typisierten und komfortablen Entwicklungsumgebung Ich habe es geschafft, also habe ich versucht, es einfach zusammenzufassen

Hintergrund

Als Hobby entwickle ich die Low-Code-Geschäftsanwendungsplattform Bizglue, und dies ist eine Geschichte hinter den Kulissen. Die Motive für die Entwicklung des Dienstes finden Sie unter Anmerkung. Wenn Sie also interessiert sind, lesen Sie diese bitte auch durch.

Rauer Durchfluss bis zur endgültigen Konfiguration

Gesamtstruktur

Serverseite

--AppEngine (Standardumgebung)

Client-Seite

Weicher Fluss

  1. Erstellen Sie mit fsglue eine Firestore-Modelldefinition
  2. Registrieren Sie die Modelldefinition in flasgger
  3. Erstellen Sie eine Open API-Definition
  4. Generieren Sie einen API-Client mit openapi-generater
  5. Der API-Client kann mit dem Typescript-Typ verwendet werden
  6. (゚ д ゚) Ummer

Was "weich" ist, ist, dass Firestore selbst im Grunde genommen schemenlos ist, sodass das Hinzufügen von Feldern und abwärtskompatiblen Änderungen einfach durch Ändern der Modelldefinition erfolgen kann (keine mühsame Migrationsverarbeitung erforderlich). .. Es ist ein wenig mühsam, den API-Client manuell zu generieren, aber ich denke, es ist sehr praktisch, den Typ mit Typescript basierend auf der auf der Serverseite definierten Modelldefinition überprüfen zu können.

Spezifisches Codebeispiel

Ich werde es mit Beispielcode etwas konkreter erklären.

Firestore-Modelldefinition

Wenn Sie ein Modell wie dieses definieren

import fsglue

TAGS_SCHEMA = {
    "type": "array",
    "items": {
        "type": "string",
    },
}

class User(fsglue.BaseModel):
    COLLECTION_PATH = "users"
    COLLECTION_PATH_PARAMS = []

    name = fsglue.StringProperty(required=True)
    tags = fsglue.JsonProperty(schema=TAGS_SCHEMA, default=[])
    created_at = fsglue.TimestampProperty(auto_now=True)
    updated_at = fsglue.TimestampProperty(auto_now_add=True)

Sie können die folgende JsonSchema-Definition mit "User.to_schema ()" generieren.

{
  "type": "object",
  "required": [
    "name",
    "owner"
  ],
  "properties": {
    "id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "created_at": {
      "type": "number"
    },
    "updated_at": {
      "type": "number"
    }
  }
}

Registrieren Sie die Modelldefinition in Blinker

Übergeben Sie das Ausführungsergebnis von "User.to_schema ()" an flasgger, damit in der Definition für jeden Endpunkt darauf verwiesen werden kann.

from flasgger import Swagger
from xxx import models  #Modelldefinition
from xxx import app  #Kolben App

template = {
  "swagger": "2.0",
  ...Unterlassung...
  "definitions": {
      "User": models.User.to_schema(),  #Modell definieren
      ...Unterlassung...
  },
  ...Unterlassung...
}

swagger = Swagger(app, template=template)

Öffnen Sie die API-Definition

Implementieren Sie beispielsweise eine API, um eine Liste der Benutzer einer bestimmten Organisation mit dem folgenden Bild abzurufen.

from flask import Blueprint
from flasgger.utils import swag_from

app = Blueprint("user", __name__, url_prefix="/api/v1")

@app.route('/organization/<org_id>/user/', methods=['GET'])
@swag_from({
    "operationId": "getUserList",
    "summary": "getUserList",
    "parameters": [
        {"name": "org_id", "in": "path", "type": "string", "required": "true"},
    ],
    "responses": {
        "200": {
            "description": "users",
            "schema": {
                "type": "array",
                "items": {
                    "$ref": "#/definitions/User",  #Siehe in blinker registrierte Modelle
                }
            },
        }
    },
    "tags": ["user"],
})
@auth.xxx_required  #Autoritätsprüfer Dekorateur
def list_user(org_id, **kwargs):
    users = User.all(org_id, to_dict=True)
    return jsonify(users)

API-Client-Generierung

Sie können openapi-generator installieren und einen API-Client in der Entwicklungsumgebung mit dem folgenden Befehl generieren (diesmal typescript-fetch) Ich benutze einen Client)

#Holen Sie sich json der OpenAPI-Definition
curl http://localhost:8080/apispec_1.json > ./xxx/apispec.json 

# openapi-API-Client mit Generator generieren
./node_modules/.bin/openapi-generator \
    generate \
    -g typescript-fetch \
    -o ./yyy/api/ \
    -i ./xxx/apispec.json \
    --additional-properties modelPropertyNaming=snake_case \  #Optionen sind Ihre Wahl
    --additional-properties supportsES6=true \
    --additional-properties typescriptThreePlus=true \
    --additional-properties disallowAdditionalPropertiesIfNotPresent=false

Verwendung des API-Clients

Mit dem folgenden Bild können Sie den API-Client verwenden und gleichzeitig vom Typescript-Typ profitieren. (Bei der tatsächlichen Verwendung wird die generierte API über einen Wrapper aufgerufen, anstatt direkt aufgerufen zu werden, um Authentifizierungsinformationen bereitzustellen und eine allgemeine Verarbeitung zu implementieren.)

import { UserApi } from "xxx/UserApi";

const api = new OrganizationApi();
//Im Folgenden arbeiten das API-Argument von getUserList und der Rückgabetyp mit Typescript.
const users = await api.getUserList({ orgId: "test" });

Impressionen

Wo es subtil war

Detailliertes Verhalten der Typkonvertierung von Json Schema nach Typescript

Der openapi-Generator konvertiert die Json-Schemadefinition automatisch in einen TypeScript-Typ, aber es gab einige Stellen, an denen ich den subtilen Juckreiz in den Details nicht erreichen konnte. Es kann nicht geholfen werden, da es in Bezug auf Spezifikationen nicht vollständig kompatibel ist, sondern speziell Abhängigkeiten (#dependencies) wird nicht gut in einen Typ konvertiert, oder enum kann nicht in einen Typescript-Unionstyp konvertiert werden. Dieses Mal habe ich es mitten in der Entwicklung als Open API erstellt. Wenn Sie es also von Anfang an integrieren möchten, ist es möglicherweise besser, es zu erstellen, während Sie den Typ auf der Json-Schemaseite so anpassen, dass sich der Typ auf der Typescript-Seite gut anfühlt.

Von openapi-geneator generierte API-Client-Spezifikationen (Typoskript-Abruf)

Es war ein wenig schwierig, eine gemeinsame Verarbeitung (Übergabe eines Tokens beim Aufrufen einer API, Einbeziehung einer gemeinsamen Fehlerbehandlung usw.) in alle APIs zu integrieren. Es scheint praktisch, den generierten API-Client so zu verwenden, wie er ist, aber ich hatte das Gefühl, dass es nicht viele Schnittstellen gibt, um ihn später zu erweitern, und freue mich auf die zukünftige Entwicklung.

Praktischer Ort

Sie können eine Definition vom Server bis zur Vorderseite verwenden

Beim Hinzufügen oder Ändern eines Schemas auf der Serverseite zeigt die Typprüfung von Typescript in gewissem Maße den Einfluss der Front-End-Seite, sodass ich der Meinung bin, dass dieser Punkt zu einer Verbesserung der Entwicklungsgeschwindigkeit geführt hat. Das Überprüfen der Konsistenz der Implementierung auf der Serverseite und der Vorderseite ist eine häufige und mühsame Aufgabe, daher ist es schön, dies reduzieren zu können.

Es gibt eine Form, aber sie ist flexibel

Da Firestore selbst schemenlos ist, können Änderungen, die nicht mit vorhandenen Daten in Konflikt stehen, und Änderungen, die sich nicht auf die Indexumgebung auswirken, einfach durch Ändern der Modelldefinition entwickelt werden, sodass Sie von der schemenlosen Typprüfung von Firestore profitieren können Ich halte es für einen guten Mechanismus, durch den Sie auch von der Früherkennung von Fehlern profitieren können. (Ich denke nicht, dass es für eine groß angelegte Entwicklung geeignet ist, aber in diesem Fall habe ich überhaupt keine Lust, Firestore zu verwenden.)

Verwenden Sie das Open API-Ökosystem

Ich habe es diesmal nicht viel benutzt, aber es wird möglich sein, das von OpenAPI gepflegte Ökosystem zu nutzen. Wenn es also gut integriert ist, scheint es auch die folgenden Verwendungsmethoden zu geben.

schließlich

Ich persönlich entwickle einen Dienst namens Entwicklungsplattform für Low-Code-Anwendungen Bizglue. Bitte benutzen Sie es!

Recommended Posts

Eine Geschichte über die Entwicklung eines weichen Typs mit Firestore + Python + OpenAPI + Typescript
Die Geschichte, mit Python eine Hanon-ähnliche Partitur zu machen
Eine Geschichte über einen Amateur, der mit Python (Kivy) einen Blockbruch macht ②
Eine Geschichte über einen Amateur, der mit Python (Kivy) einen Blockbruch macht ①
Eine Geschichte über einen Python-Anfänger, der mit dem No-Modul'http.server 'feststeckt.
Eine Geschichte über das Hinzufügen einer REST-API zu einem mit Python erstellten Daemon
Ein Java-Programmierer studierte Python. (Über Typ)
Eine Geschichte über maschinelles Lernen mit Kyasuket
Eine Geschichte über Python Pop und Append
[Python3] Eine Geschichte, die bei der Zeitzonenkonvertierung steckt
Eine Geschichte über den Umgang mit Binärdaten in Python
Eine Geschichte über die Implementierung eines Anmeldebildschirms mit Django
Eine Geschichte über das Ausführen von Python auf PHP auf Heroku
Eine Geschichte über das Ändern von Python und das Hinzufügen von Funktionen
Geschichte der Verwendung von Resonas Software-Token mit 1Password
Eine Geschichte über die Vorhersage des Wechselkurses mit Deep Learning
Eine Geschichte darüber, wie Windows 10-Benutzer eine Umgebung für die Verwendung von OpenCV3 mit Python 3.5 erstellt haben
Die Geschichte, wie theano mit TSUBAME 2.0 verwaltet wurde
Ein Memo zum Erstellen einer Django (Python) -Anwendung mit Docker
[Hinweis] Eine Geschichte über den Versuch, eine Klassenmethode mit zwei Unterbalken in der Python 3-Serie zu überschreiben.
Maschinelles Lernen Eine Geschichte über Menschen, die mit GBDT in GBDT in Python nicht vertraut sind
Ein Hinweis zum Aufrufen der Facebook-API mit dem Python SDK
Eine Geschichte darüber, wie man einen relativen Pfad in Python angibt.
Eine Geschichte über den Wettbewerb mit einem Freund in Othello AI Preparation
Eine Geschichte über die Installation von matplotlib mit pip mit einem Fehler
Eine Geschichte über den Umgang mit dem CORS-Problem
Eine Geschichte über das zufällige Erstellen eines kurzen Songs mit Sudachi Py
Eine Geschichte über den Versuch, private Variablen in Python zu implementieren.
Stolpern Geschichte mit Python-Array
Memorandum über Korrelation [Python]
Machen Sie eine Lotterie mit Python
Ein Memorandum über den Python-Mock
Erstellen Sie ein Verzeichnis mit Python
Ein Hinweis zu [Python] __debug__
[Python, Selenium, PhantomJS] Eine Geschichte beim Scrapen einer Website mit fauler Last
Die Geschichte, einen Standardtreiber für db mit Python zu erstellen.
Eine Geschichte über den Versuch, mehrere Python-Versionen auszuführen (Mac Edition)
Die Geschichte, ein Modul zu erstellen, das E-Mails mit Python überspringt
[Python] Was ist eine with-Anweisung?
Löse ABC163 A ~ C mit Python
Python-Grafikhandbuch mit Matplotlib.
Eine erfrischende Geschichte über Slice in Python
Lassen Sie uns eine GUI mit Python erstellen.
Python: Ein Hinweis zu Klasse 1 "Abstract"
Eine launische Geschichte über Slice in Python
Erstellen Sie eine virtuelle Umgebung mit Python!
Ich habe mit Python eine Lotterie gemacht.
Erstellen einer virtuellen Umgebung mit Python 3
Löse ABC168 A ~ C mit Python
Erstellen Sie ein Empfehlungssystem mit Python
[Kleine Geschichte] Holen Sie sich mit Python einen Zeitstempel
[Python] Generiere ein Passwort mit Slackbot
Löse ABC162 A ~ C mit Python
Löse ABC167 A ~ C mit Python
Löse ABC158 A ~ C mit Python
Beherrsche den Typ mit Python [Python 3.9 kompatibel]
Lassen Sie uns ein Diagramm mit Python erstellen! !!
Die Geschichte der Verwendung von Python reduziert
[Python] Erbt eine Klasse mit Klassenvariablen