Ya vimos en el blog una introducción a GraphQL y cómo crear nuestro propio GraphQL Server en Java. Pero, para los amantes de hacer “magia” con 3 líneas de código, mostraremos cómo crear nuestro propio GraphQL Server en Python y si podemos alcanzar el “Uno para todos y todos para uno”.

Para ello, trabajaremos dos ejemplos con los frameworks para desarrollo web más populares actualmente: Django y Flask.

En todos ellos, utilizaremos la librería para Python Graphene, que justamente hace poco ha publicado su versión 2.0.

GraphQL + Django

El ejemplo que vamos a ver a continuación, puedes descargarlo aquí.

Stack

¡Manos a la obra!

Una vez ejecutemos nuestro código (ver el repositorio para ver cómo) podremos empezar a realizar consultas con GraphiQL. Como en los ejemplos anteriores en Java, podemos realizar múltiples consultas con una petición.

Bien, hasta aquí nuestro servidor es igual que en las otras implementaciones, pero ¿qué está pasando por detrás?

En nuestro caso, hemos emulado los diferentes orígenes de datos con dos bases de datos de SQLite. Si hablamos de entornos productivos, la recomendación sería:


DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.sqlite3',
       'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
   },
   'brands_ddbb': {
       'ENGINE': 'django.db.backends.sqlite3',
       'NAME': os.path.join(BASE_DIR, 'db.sqlite3_brands'),
   }
}

Aprovechando las propias funcionalidades de Django, delegamos en su ORM la resolución de las queries contra diferentes orígenes. Por ejemplo, cuando recuperamos un objeto con todas sus relaciones, tal que:


{
    "data": {
        "car": {
            "color": "Red",
            "id": 1,
            "model": {
                "brand": {
                    "id": "1",
                    "name": "Seat"
                },
                "id": "4",
                "name": "Alhambra"
            }
        }
    }
}

Internamente se ataca a dos tablas, cada una en una base de datos distinta:

Esto es, gracias a un resolver definido como “resolve_car” en nuestro schema.py:


def resolve_car(self, info, **kwargs):
   id = kwargs.get('id')
   color = kwargs.get('color')
   if id is not None:
       return CarModel.objects.get(pk=id)

Si analizamos cuando recuperamos muchos objetos, como suele pasar en este tipo de implementaciones, vemos que nos encontramos con 1+N Queries Problem. Es decir, tiene que realizar una query adicional por cada N objetos que recuperamos.

Pero con los propios recursos de Django, como prefetch related, a pesar de las múltiples bases de datos, podemos solucionar el problema.


def resolve_cars(self, info, **kwargs):
   return CarModel.objects.all().prefetch_related('model', 'model__brand')

¡OJO! Django 1.x permite atacar múltiples bases de datos pero no es oro todo lo que reluce. En este caso, para poder insertar las relaciones entre la tabla "models", como tiene una dependencia "brands" que pertenece a otro origen, hemos tenido que lanzar INSERTS y UPDATES a mano porque este framework no lo permite directamente.

Si quisiéramos añadir una nueva mutación que fuese CreateModel, nos encontraríamos con un problema.

Como conclusión en esta implementación en Django, podemos concluir:

Pros:

Contras:

Podríamos decir entonces que el lema en este caso sería “Todos para uno y uno para todos… si todos cumplen las reglas de uno”.

Para que nuestro GraphQL server, gracias a sus resolvers, nos permita definir el origen de cada uno de nuestros objetos, lo veremos mejor en el siguiente ejemplos con Flask.

GraphQL + Flask

Puedes descargarte y probar este ejemplo desde este enlace.

Stack

En este caso, nuestro proyecto tiene una estructura muy parecida (con la salvedad que gracias a la simpleza de Flask, todo nuestro código no pasa de 150 líneas).

Como vemos a continuación, en nuestro esquema hemos utilizado dos implementaciones diferentes.

La primera, la definimos con la clase más “pura” de Graphene para recuperar nuestros datos contra una MongoDB, sin apoyarnos en librerías de terceros.

# schema.py
class Brand(graphene.ObjectType):
   name = graphene.String()
   class Meta:
       interfaces = (relay.Node,)
   @classmethod
   def get_node(cls, info, id):
       return get_brand(id)
# models.py
def get_brand(id):
   from schema import Brand
   result = brands.find_one({"_id": bson.ObjectId(str(id))})
   brand = Brand(id=str(result["_id"]), name=str(result["name"]))
   return brand

En este ejemplo vemos muy claramente la forma de trabajar con GraphQL, definimos nuestros datos y la respuesta, sin importar el origen, desacoplando de tal manera que nos da igual si esos datos vienen de una MongoDB o una API REST.

El segundo, nos apoyamos en graphene-sqlalchemy para relacionar el modelo de base de datos con nuestro esquema.

#schema.py
class Model(SQLAlchemyObjectType):
   brand = graphene.Field(Brand)
   class Meta:
       model = ModelModel
       interfaces = (relay.Node,)
   def resolve_brand(self, info, *args, **kwargs):
       return get_brand(self.brand_id)
class Car(SQLAlchemyObjectType):
   class Meta:
       model = CarModel
       interfaces = (relay.Node,)

Nos permite más nivel de desacoplamiento que en Django, pero seguimos atados. En este caso el lema sí sería “Todos para uno y uno para todos… pero uno no puede hacer que algo ocurra antes de que sea su hora”.

Como conclusión final de Python y Graphene:

Pros:

Contras:

Cuéntanos qué te parece.

Enviar.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.