Skip to content
Snippets Groups Projects
Forked from an inaccessible project.

Remarques sur l'implémentation, explication de choix de design...

Pour des commentaires sur la définition du schéma, se reporter plutôt aux commentaires en en-tête des fichiers actions.graphql et objects.graphql.

Pour des explications plus high-level sur la gestion des droits et les authorizations sur Sigma, voir le memo idoine {@tutorial hist_rights}.

Structure du dossier src/graphql

graphql
├── models
│   ├── authorization.ts
│   ├── groupModel.ts
│   ├── messageModel.ts
│   ├── requestModel.ts
│   ├── tools.ts
│   └── userModel.ts
├── object_resolvers
│   ├── groups.ts
│   ├── messages.ts
│   ├── requests.ts
│   └── users.ts
├── resolvers.ts
├── schema.js
└── typeDefs
    ├── actions.graphql
    ├── objects.graphql
    └── queries.d.ts

Les sections suivantes expliqueront la logique des models et object_resolvers ; pour l'instant,expliquons quelques points simples.

"Points d'entrée"

graphql/schema.js exporte tout le schéma GraphQL, utilisé pour configurer ApolloServer dans app.ts.

A la réception d'une requête GraphQL, avant de la resolver, une instance de chaque object model de graphql/models/ est créée, et passée dans le context. Ceci est aussi effectué dans app.ts (const context = ...).

La définition du schéma : src/graphql/typeDefs

C'est assez straightforward quand on connaît les bases de GraphQL...

queries.d.ts définit l'interface Typescript Context.

Export des resolvers graphql/resolvers.ts

Du point de vue de graphql/schema.js, qui les importe, le fichier graphql/resolvers.ts exporte bien tous les resolvers de tous les types du schéma GraphQL. Pourtant, on ne définit dans graphql/resolvers.ts que les resolvers pour les types Query et Mutations. En effet on exploite les default resolvers fournis par Apollo Server (le deuxième point) :

Explicit resolvers are not needed for every type, since Apollo Server provides a default that can perform two actions depending on the contents of parent:

Dans notre cas, le type de retour de chaque resolver est un objet JS (Group, Message, Request ou User) défini dans un des fichiers de graphql/object_resolvers/ ; donc les méthodes des classes Group, Message, Request et User dont le prototype est (parent, args, context: Context, info) sont utilisées comme resolvers de l'objet GraphQL respectif.

(Les sections suivantes donnent plus de précisions sur la logique derrière ce dernier paragraphe.)

Resolvers/Models/Connectors

Les notions de models et de connectors sont assez communes en développement web. Une définition informelle appliquée au contexte de GraphQL est donnée ici :

https://blog.apollographql.com/how-to-build-graphql-servers-87587591ded5

Model: a set of functions to read/write data of a certain GraphQL type by using various connectors. Models contain additional business logic, such as permission checks, and are usually application-specific.

Connector: a layer on top of a database/backend driver that has GraphQL-specific error handling, logging, caching and batching. Only needs to be implemented once for each backend, and can be reused in many apps

By using models and connectors, my resolvers turned into simple switches that map inputs and arguments to a function call in the underlying model layer, where the actual business logic resides.

Note: To be clear, models and connectors aren’t abstractions that are built into GraphQL, but they emerge as a natural way to structure code in a GraphQL server.

Connectors

Dans notre cas, on utilise à la fois une base de données propre à Sigma (PostgreSQL, manipulée avec Knexjs) et un LDAP (manipulé avec les fonctions de src/ldap/export/, développées par Quentin Chevalier, qu'il appelle son "API LDAP").

Knexjs et l'"API LDAP" sont les équivalents des Connectors pour notre projet. Pas besoin de se forcer à faire une couche supplémentaire, ça ne serait pas utile.

Les resolvers des actions : graphql/resolvers.ts

Les resolvers des objets : graphql/object_resolvers/

Les classes JS définies dans les fichiers de graphql/object_resolvers/ jouent à la fois le rôle de resolvers (plus précisément, ont des méthodes qui sont utilisées comme resolvers), et de return type pour les resolvers. C'est ceci qui permet d'exploiter à fond les defaults resolvers d'Apollo Server.

Une bonne façon de le voir est de dire que :

  • du point de vue de l'export du schéma (donc de schema.js et resolvers.ts), ces fichiers servent à définir les resolvers, tout simplement
  • du point de vue des autres fichiers qui les importent, ces fichiers définissent des return types de resolvers.

Models : graphql/models

Comme indiqué plus haut, une instance de chaque object model est créée et passée dans le context, à chaque réception d'une requête GraphQL. En fait, puisque les constructeurs ne prennent en paramètre que l'uid de l'utilisateur qui fait la requête, on peut dire qu'un set de models est créé pour chaque utilisateur.

Autrement dit, chaque utilisateur a ses propres "data accessors", sa propre "vue" de la BDD, personnalisée pour prendre en compte ses memberOfs.

Implémentation de la gestion des authorizations

En anglais, authorisation est une orthographe valide, mais pour plus d'homogénéité et pour reprendre l'usage habituel sur internet, on utilisera l'orthographe américaine authorization.

Comme déjà indiqué plus haut, pour des explications plus high-level sur la gestion des droits et les authorizations sur Sigma, voir le memo idoine (memo_sigma_rights.md).

authorization.ts