entity_manager.md 7.3 KB

Entity Manager

L'entity manager est la classe au coeur du requêtage des données. Il assure la liaison entre l'api et le store pinia-orm.

Modèles et entités

Les modèles d'entités sont définis dans le répertoire ~/models, sous forme de modèles Pinia-ORM et en correspondance avec leur définition côté API.

ApiResource et ApiModel

Les ApiResources représentent toute entité pouvant être obtenue via une requête à l'API. C'est la classe de base gérée par l'entity manager.

Les ApiModel sont des ApiResources qui supportent toutes les opérations de type CRUD.

Ces deux classes correspondent aux ApiResources et aux Entities de Api-Platform.

Format Json-Ld (Hydra) et normalizer

L'API renvoie ses données au format Json-Ld. La classe hydraNormalizer s'occupe ensuite de transformer ces données dans un format compréhensible par l'entity manager.

Les entités simples sont castées en une instance de leur modèle. Les collections sont parsées en une liste d'instances de leur modèle.

Les métadonnées, en particulier en ce qui concerne la pagination, sont aussi extraites.

Le résultat est retourné sous la forme d'un objet de la forme :

{
  data: [...],
  metadata: {...},
}

Les champs IriEncoded

Les relations entre les entités sont fournies par l'API sous forme d'IRI. De même, lors des opérations PUT/POST/PATCH, celle-ci attend aussi des IRI pour représenter ces relations.

Hors le choix a été fait de stocker les ids des entités sous leurs formes numériques dans les stores Pinia-ORM.

Pour permettre cette inter-opérabilité dans les deux sens, on utilise le décorateur IriEncoded, qui va permettre de signaler au normalizer qu'une propriété doit être décodée en entrée (son id sera extrait au format numérique), et qu'elle doit être retransformée en IRI dans l'autre sens.

Exemple d'utilisation (classe Access.ts):

@IriEncoded(Organization)
declare organization: number | null

Entity Manager

Créer une nouvelle entité (non persistée)

On créé une nouvelle instance d'entité en se servant de la méthode newInstance, à laquelle on peut passer les propriétés du nouvel objet sous forme d'objet JS.

const newOrganizationInstance = em.newInstance(Organization)

const newAccessInstance = em.newInstance(Access, { id: 1 })

La nouvelle instance sera crée et enregistrée dans le store Pinia-ORM (mais pas persistée côté API)

Les nouvelles entités (non persistée) se voient attribuer un id temporaire sous forme d'uuid.

Persister une entité

Une entité existante ou nouvellement crée peut-être persistée côté API. Pour ce faire, on utilise la méthode persist à laquelle on passe le modèle et l'instance de l'entité à persister.

em.persist(Organization, myOrganization)

C'est entre autres la méthode utilisée par les formulaires lors de l'action "enregistrer".

Attention : Lorsqu'on persiste une entité, l'entity manager va utiliser les données de retour de la requête envoyée à l'API, et mettre à jour le store en fonction (et ce afin d'éviter des écarts involontaires entre les données front et back). Ce qui veut dire que selon le traitement effectué par l'API, l'entité envoyée et celle qui résulte de l'opération peuvent différer.

Fetch une entité simple

Si l'entité a déjà été fetchée, et que l'argument forceRefresh est faux, l'entité est simplement retournée depuis le store.

Sinon, l'opération fetch se déroule en deux temps.

D'abord, une requête GET est envoyée à l'API au moyen de la classe ApiRequestService (surcouche de la librairie ohfetch). Le résultat de cette requête est converti en ApiResource par le normalizer, puis enregistrée dans le store.

On la récupère ensuite dans le store sous forme de référence avant de la retourner. En effet, si on retournait directement le résultat de l'appel à l'API plutôt qu'une référence au store Pinia-ORM, on perdrait la réactivité.

Fetch une Collection

Fetcher une collection est plus compliqué. La requête implique des conditions de filtre, de tri, de pagination. Afin de pouvoir garder un lien entre les résultats retournés par l'API et les résultats à aller ensuite chercher dans le store, il faut un système permettant de passer les mêmes conditions à ces deux interfaces (API / PiniaOrm). C'est le rôle de la classe Query et des Filters (voir plus bas).

Une fois définies les conditions au moyen de l'objet Query, l'appel à fetchCollection se passe de la même manière que lors de l'appel à la méthode fetch (appel, puis récupération des résultats dans le store). La seule nuance est que le résultat est un objet Collection, incluant les résultats sous la forme d'une référence calculée (ComputedRef), et les données de pagination :

{
  items,
  totalItems: 100,
  pagination: {
    first: 1,
    last: 10,
    next: 5,
    previous: 3,
  },
}

L'objet Query et les Filters

Les filtres, les tris et la pagination sont donc empaquetés dans un objet Query :

const query = new Query()

On pourra ensuite ajouter des filtres à cette query :

query.add(new SearchFilter('name', searchFilter, SEARCH_STRATEGY.IPARTIAL))
query.add(new OrderBy('name', ORDER_BY_DIRECTION.ASC))
query.add(new PageFilter(page, itemsPerPage))

Cette query traduira ensuite ces conditions en URL query pour l'appel à l'API d'une part, et en query PiniaORM d'autre part.

NB : les filtres de type 'where' seront toujours appliqués en premier, puis les tris, et enfin la pagination.

Reset une entité

L'entity manager stocke dans le store un clone non modifié de chaque entité fetchée depuis l'API. Ces clones sont enregistrés avec des ids préfixés par __clone__, et ils sont automatiquement exclus des requêtes Pinia-ORM lorsque celles-ci sont construites au moyen de la méthode getQuery de l'entity manager.

La méthode reset permettra de réinitialiser une entité modifiée dans le store pour la ramener à l'état qu'elle avait la dernière fois que l'API l'a renvoyée.

Supprimer une entité

On peut supprimer une entité au moyen de la méthode delete de l'entity manager. Si l'entité existe dans l'API, une requête de suppression sera envoyée à celle ci.

EnumManager et ImageManager

EnumManager

La classe EnumManager permettra de fetcher auprès de l'API des Enum, et de les retourner sous forme de listes JS.

ImageManager

La classe ImageManger donnera accès à des méthodes permettant de télécharger une image depuis l'API et de la retourner sous forme de Base64, ou d'uploader une image.

Composables et useAsyncData

Utiliser les services dans Vue

Les différentes classes de manager, ainsi que le service de requête à l'API, sont disponibles sous forme de composables : useEntityManger, useImageManager, ...etc.

Exemple :

const ap2iRequestService = useAp2iRequestService()

Fetch avec useAsyncData

Les composables useEntityFetch, useEnumFetch et useImageFetch permettent d'accéder aux différentes méthodes fetch des managers, sous la forme d'un appel à useAsyncData.

Exemple d'utilisation :

const { data: parameters, pending, refresh } = fetch(Parameters, 123)