-
Quentin CHEVALIER authoredQuentin CHEVALIER authored
- Structure du dossier src/graphql
- "Points d'entrée"
- La définition du schéma : src/graphql/typeDefs
- Export des resolvers graphql/resolvers.ts
- Resolvers/Models/Connectors
- Connectors
- Les resolvers des actions : graphql/resolvers.ts
- Les resolvers des objets : graphql/object_resolvers/
- Models : graphql/models
- Implémentation de la gestion des authorizations
- authorization.ts
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}.
src/graphql
Structure du dossier 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 = ...
).
src/graphql/typeDefs
La définition du schéma : C'est assez straightforward quand on connaît les bases de GraphQL...
queries.d.ts
définit l'interface Typescript Context
.
graphql/resolvers.ts
Export des resolvers 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:
- Return the property from parent with the relevant field name
- Calls a function on parent with the relevant field name and provide the remaining resolver parameters as arguments https://www.apollographql.com/docs/apollo-server/v2/essentials/data.html#default
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.
graphql/resolvers.ts
Les resolvers des actions :
graphql/object_resolvers/
Les resolvers des objets : 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
etresolvers.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.
graphql/models
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
).