Une histoire sur le développement d'un type logiciel avec Firestore + Python + OpenAPI + Typescript

Aperçu

Si vous définissez un type avec Python Own ORM (fsglue) dans un Firestore sans schéma et le combinez avec OpenAPI ou Typescript, un environnement de développement flexible mais typé et confortable Je l'ai fait, alors j'ai essayé de le résumer facilement

Contexte

En tant que passe-temps, je développe la plate-forme d'application commerciale à faible code Bizglue, et c'est une histoire en coulisse. Pour connaître les motifs de développement du service, voir note, donc si vous êtes intéressé, veuillez également le vérifier.

Écoulement brutal jusqu'à la configuration finale

structure globale

Du côté serveur

--AppEngine (environnement standard)

Côté client

Flux de type doux

  1. Créez une définition de modèle Firestore avec fsglue
  2. Enregistrez la définition du modèle dans flasgger
  3. Créez une définition d'API ouverte
  4. Générez un client API avec openapi-generater
  5. Le client API peut être utilisé avec le type Typescript
  6. (゚ д ゚) Ummer

Ce qui est "doux", c'est que Firestore lui-même est fondamentalement sans schéma, donc l'ajout de champs et de modifications rétrocompatibles peut être fait simplement en changeant la définition du modèle (pas besoin de traitement de migration gênant). .. Il est un peu gênant de générer manuellement le client API, mais je pense qu'il est assez pratique de pouvoir vérifier le type avec Typescript en fonction de la définition du modèle définie côté serveur.

Exemple de code spécifique

Je vais l'expliquer un peu plus concrètement avec un exemple de code.

Définition du modèle Firestore

Si vous définissez un modèle comme celui-ci

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)

Vous pouvez générer la définition JsonSchema suivante avec ʻUser.to_schema () `.

{
  "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"
    }
  }
}

Enregistrer la définition du modèle dans Flasgger

ʻPasser le résultat de l'exécution de User.to_schema () `à flasgger afin qu'il puisse être référencé dans la définition de chaque point final.

from flasgger import Swagger
from xxx import models  #Définition du modèle
from xxx import app  #application flacon

template = {
  "swagger": "2.0",
  ...Omission...
  "definitions": {
      "User": models.User.to_schema(),  #Définir le modèle
      ...Omission...
  },
  ...Omission...
}

swagger = Swagger(app, template=template)

Définition d'API ouverte

Par exemple, implémentez une API pour obtenir une liste d'utilisateurs appartenant à une certaine organisation avec l'image suivante.

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",  #Voir les modèles enregistrés dans flasgger
                }
            },
        }
    },
    "tags": ["user"],
})
@auth.xxx_required  #Décorateur de contrôle d'autorité
def list_user(org_id, **kwargs):
    users = User.all(org_id, to_dict=True)
    return jsonify(users)

Génération de client API

Vous pouvez installer openapi-generator et générer un client API dans l'environnement de développement avec la commande suivante (cette fois, typescript-fetch J'utilise un client)

#Obtenir json de la définition OpenAPI
curl http://localhost:8080/apispec_1.json > ./xxx/apispec.json 

# openapi-Générer un client API avec un générateur
./node_modules/.bin/openapi-generator \
    generate \
    -g typescript-fetch \
    -o ./yyy/api/ \
    -i ./xxx/apispec.json \
    --additional-properties modelPropertyNaming=snake_case \  #Les options sont votre choix
    --additional-properties supportsES6=true \
    --additional-properties typescriptThreePlus=true \
    --additional-properties disallowAdditionalPropertiesIfNotPresent=false

Utilisation du client API

Avec l'image ci-dessous, vous pouvez utiliser le client API tout en bénéficiant du type Typescript. (Lors de son utilisation, afin de fournir des informations d'authentification et d'implémenter un traitement commun, l'API générée est appelée via un wrapper au lieu d'être appelée directement.)

import { UserApi } from "xxx/UserApi";

const api = new OrganizationApi();
//Ci-dessous, l'argument API de getUserList et le type de retour fonctionnent avec Typescript.
const users = await api.getUserList({ orgId: "test" });

Impressions

Où c'était subtil

Comportement détaillé de la conversion de type de Json Schema vers Typescript

Le générateur openapi convertit automatiquement la définition de schéma Json en un type TypeScript, mais il y avait des endroits où je ne pouvais pas atteindre les démangeaisons subtiles dans les détails. Cela ne peut pas être aidé car il n'est pas complètement compatible en termes de spécifications, mais spécifiquement [dépendances](https://json-schema.org/understanding-json-schema/reference/object.html (#dependencies) n'est pas converti en un type correctement, ou enum ne peut pas être converti en un type d'union Typescript. Cette fois, je l'ai fait Open API au milieu du développement, donc si vous voulez l'intégrer depuis le début, il peut être préférable de le faire en ajustant le type du côté Json Schema afin que le type du côté Typescript se sente bien.

Spécifications du client API générées par openapi-geneator (typescript-fetch)

Il était un peu difficile d'incorporer un traitement commun (passer un jeton lors de la frappe d'une API, incorporer une gestion d'erreur commune, etc.) dans toutes les API. Il semble pratique d'utiliser le client API généré tel quel, mais j'ai senti qu'il n'y avait pas beaucoup d'interfaces pour l'étendre plus tard, donc j'attends avec impatience le développement futur.

Place convenable

Vous pouvez utiliser une définition du serveur vers l'avant

Lors de l'ajout ou de la modification d'un schéma côté serveur, la vérification de type de Typescript montre dans une certaine mesure l'influence du front-end, donc je pense que ce point a conduit à une amélioration de la vitesse de développement. Vérifier la cohérence de l'implémentation côté serveur et côté frontal est une tâche courante et fastidieuse, c'est donc bien de pouvoir réduire cela.

Il y a un moule mais il est flexible

Puisque Firestore lui-même est sans schéma, les modifications qui n'entrent pas en conflit avec les données existantes et les modifications qui n'affectent pas l'environnement de l'index peuvent être développées simplement en modifiant la définition du modèle, donc tout en bénéficiant de la vérification de type sans schéma de Firestore Je pense que c'est un bon mécanisme par lequel vous pouvez également bénéficier de la détection précoce des bogues. (Je ne pense pas que ce soit adapté au développement à grande échelle, mais dans ce cas, je n'ai pas envie d'utiliser Firestore en premier lieu.)

Utilisez l'écosystème Open API

Je ne l'ai pas beaucoup utilisé cette fois, mais il sera possible d'utiliser l'écosystème maintenu par OpenAPI, donc s'il est bien incorporé, il semble qu'il y ait aussi les méthodes d'utilisation suivantes.

--Génération automatique de documents API --Validation des demandes et réponses --Génération de Mock / Stub pour les tests

enfin

Je développe personnellement un service appelé plateforme de développement d'applications Low-code Bizglue. Veuillez l'utiliser!

Recommended Posts

Une histoire sur le développement d'un type logiciel avec Firestore + Python + OpenAPI + Typescript
L'histoire de la création d'une partition de type Hanon avec Python
Une histoire sur un amateur faisant une rupture de bloc avec python (kivy) ②
Une histoire sur un amateur faisant une rupture de bloc avec python (kivy) ①
Une histoire à propos d'un débutant en python coincé avec aucun module nommé'ttp.server '
Une histoire sur l'ajout d'une API REST à un démon créé avec Python
Un programmeur Java a étudié Python. (À propos du type)
Une histoire sur l'apprentissage automatique avec Kyasuket
Une histoire sur Python pop and append
[Python3] Une histoire bloquée avec la conversion du fuseau horaire
Une histoire sur la gestion des données binaires en Python
Une histoire sur l'implémentation d'un écran de connexion avec django
Une histoire sur l'exécution de Python sur PHP sur Heroku
Une histoire sur la modification de Python et l'ajout de fonctions
Histoire de l'utilisation du jeton logiciel de Resona avec 1Password
Une histoire de prédiction du taux de change avec Deep Learning
Une histoire sur la façon dont les utilisateurs de Windows 10 ont créé un environnement pour utiliser OpenCV3 avec Python 3.5
L'histoire de la gestion de theano avec TSUBAME 2.0
Un mémo sur la création d'une application Django (Python) avec Docker
[Note] Une histoire sur la tentative de remplacer une méthode de classe avec deux barres inférieures dans la série Python 3.
Apprentissage automatique Une histoire sur des personnes qui ne sont pas familiarisées avec GBDT utilisant GBDT en Python
Une note sur l'utilisation de l'API Facebook avec le SDK Python
Une histoire sur la façon de spécifier un chemin relatif en python.
Une histoire de compétition avec un ami dans Othello AI Preparation
Une histoire sur l'installation de matplotlib à l'aide de pip avec une erreur
Une histoire sur la façon de traiter le problème CORS
Une histoire sur la création d'une courte chanson par hasard avec Sudachi Py
Une histoire sur la tentative d'implémentation de variables privées en Python.
Histoire de trébucher avec le tableau Python
Mémorandum sur la corrélation [Python]
Faites une loterie avec Python
Un mémorandum sur le simulacre de Python
Créer un répertoire avec python
Une note sur [python] __debug__
[Python, Selenium, PhantomJS] Une histoire lors de la capture d'un site Web avec une charge paresseuse
L'histoire de la création d'un pilote standard pour db avec python.
Une histoire sur la tentative d'exécuter plusieurs versions de Python (édition Mac)
L'histoire de la création d'un module qui ignore le courrier avec python
[Python] Qu'est-ce qu'une instruction with?
Résoudre ABC163 A ~ C avec Python
Manuel de graphisme Python avec Matplotlib.
Une histoire rafraîchissante sur Slice en Python
Faisons une interface graphique avec python.
Python: une note sur les classes 1 "Résumé"
Une histoire de mauvaise humeur sur Slice en Python
Créez un environnement virtuel avec Python!
J'ai fait une loterie avec Python.
Créer un environnement virtuel avec Python 3
Résoudre ABC168 A ~ C avec Python
Créer un système de recommandation avec python
[Petite histoire] Obtenez l'horodatage avec Python
[Python] Générer un mot de passe avec Slackbot
Résoudre ABC162 A ~ C avec Python
Résoudre ABC167 A ~ C avec Python
Résoudre ABC158 A ~ C avec Python
Maîtriser le type avec Python [compatible Python 3.9]
Faisons un graphe avec python! !!
L'histoire de l'utilisation de la réduction de Python
[Python] Hériter d'une classe avec des variables de classe