cozy / cozy-drive

Cozy Drive Web App for Cozy Cloud
GNU Affero General Public License v3.0
171 stars 65 forks source link

[cozy-client] Relations/associations between doctypes #693

Closed kosssi closed 4 years ago

kosssi commented 6 years ago

Comment standardiser la récupération automatique des liens entre doctypes ?

Par exemple du coté de cozy bank nous avons différents liens entre les opérations les factures ainsi que les fichiers.

Nous avons une many-to-many entre les opérations et les factures : operation.bills <-> bill.creditOperations

Comment cozy-client va pouvoir récupérer automatiquement ses liaisons ?

ptbrowne commented 6 years ago

My 2 cents : j'ai utilisé Backbone Relational sur un précédent projet et j'avais apprecié le fait qu'on puisse déclarer les relations d'un modèle déclarativement. Peut être qu'il faudrait voir s'il y a pas des bonnes choses à prendre du côté Backbone : déclaration des attributs du modèle, parse du modèle quand il arrive du serveur pour mettre dans cozy-client. J'ai l'impression qu'à Cozy on part plus sur le standard JSONAPI, pour les relations, il faudrait peut être voir si l'approche BackboneRelational est compatible avec notre structure de données en JSON API ?

goldoraf commented 6 years ago

Il y a effectivement besoin de pouvoir déclarer les relations/associations entre doctypes de façon à ce que cozy-client soit capable de les fetcher "automagiquement". L'équipe back, qui comme le souligne @ptbrowne, a opté pour JSONAPI afin de standardiser ses réponses, préconiserait un format de déclaration JSON afin qu'il soit utilisable en JS comme en Go. Je verrais donc bien qqch comme ça :

export const BILL_SCHEMA = {
  attributes: {
    amount: {
      type: 'number'
    },
    vendor: {
      type: 'number'
    },
    date: {
      type: 'date'
    }
    // ...
  },
  relationships: {
    operations: {
      type: 'has-many',
      doctype: 'io.cozy.bank.operations'
    },
    invoice: {
       type: 'has-one',
       doctype: 'io.cozy.files'
    }
}

Côté cozy-client, ce schéma serait déclaré à l'initialisation :

const client = new CozyClient()
client.registerDoctype('io.cozy.bank.bills', BILL_SCHEMA)

Et une requête ressemblerait à :

const ConnectedComponent = cozyConnect(
  find('io.cozy.bank.bills')
    .where({ date: { $gt: new Date(2018, 01, 01) })
    .include([ 'operations', 'invoice' ])
)(Component)

Qu'en pensez-vous ? @nono @aenario qu'aviez-vous en tête concernant la définition du schéma d'un doctype ? Ma proposition ci-dessus vous convient-elle comme base de départ ?

nono commented 6 years ago

Oui, ca parait bien comme point de départ. Est-ce que tu as regardé https://github.com/pouchdb-community/relational-pouch ? Il y a surement des idées à prendre de là-bas.

aenario commented 6 years ago

Ca me paraît bien.

Par contre, il y a beaucoup de possibilités au niveau du stockage des relations dans couchDB, qu'il faut que ce langage décrive si on veut pouvoir gérer pareil et intelligemment en go et JS.

En mode optimal (target final), je verrais bien :

Par contre, pas sûr de quelles sont les étapes.

nono commented 6 years ago

À ne pas oublier, on a également le cas des relations polymorphiques. Par exemple, on va vouloir attacher un commentaire à plein d'objets différents, et comme c'est une relation de type 1-N, on va sûrement vouloir stocker ça sur le commentaire.

{ "type": "io.cozy.notes", "parent": { "type": "io.cozy.contacts", "id": "123456789" } }

Mais c'est impossible en l'état actuel car on ne peut pas le faire via mango. C'est ce qu'on a fait coté stack pour la notion de références des fichiers et ça demande une requête mapreduce pour trouver les fichiers référencé par un document donné.

Je pense que c'est faisable avec les partial indexes. Là où ça a coincé pour les relations avec les fichiers pour la stack, c'est que 1. les partial indexes n'étaient pas encore là quand on a implémenté ça, et 2. c'est une relation polymorphique, donc avec une structure plus compliquée ({type: "io.cozy.files", referenced_by: [{type: t1, id: a1}, {type: t2, id: a2}]})

goldoraf commented 6 years ago

CR réu 5/3/18

L'ajout de routes dédiées à la gestion des relations côté stack serait intéressante à terme, mais il faudrait dans un premier temps pouvoir se contenter d'une implé côté client. L'implé des relations 1 -> N côté client ne pose pas de pb particulier, donc GO. L'implé des relations N -> N serait possible côté client avec les partial indexes et donc des requêtes Mango, mais A CONFIRMER. Sinon, il faudra attendre une implé côté stack.

Les schémas seront exportés par cozy-doctypes.

Migrations

Les devs d'applis ayant besoin de pouvoir implémenter facilement des migrations, il parait plus simple d'écrire les migrations en JS et de les stocker dans cozy-doctypes. Cela permettra dans un premier temps de les exécuter côté client, puis plus tard de les faire exécuter par la stack de la même façon que les connecteurs.

aenario commented 6 years ago

L'implé des relations N -> N serait possible côté client avec les partial indexes et donc des requêtes Mango, mais A CONFIRMER. Sinon, il faudra attendre une implé côté stack.

On peut (théoriquement) faire avec les partial indexes en créant un index pour chaque catégorie.

{
  "index": {
    "partial_filter_selector": {
      "categories": {
        "$elemMatch": "my-category"
      }
    },
    "fields": ["name"]
  },
  "ddoc" : "by-name-in-my-category",
  "type" : "json"
}

Par contre, coté stack il faudra du coup au moins une route pour supprimer les indexes liés à des catégories mortes (proxy vers http://docs.couchdb.org/en/2.1.1/api/database/find.html#delete--db-_index-designdoc-json-name)

y-lohse commented 6 years ago

Je confirme que les partial indexes fonctionnent:

const indexDef = {
      index: {
        partial_filter_selector: {
          groups: {
            $elemMatch: {
              "$eq": "groupidfamily"
            }
          }
        },
        fields: ['groups']
      }
    }

    const index = await cozy.client.fetchJSON('POST', '/data/io.cozy.contacts/_index', indexDef)

    const query = {
      selector: {
        groups: {
          $elemMatch: {
            "$eq": "groupidfamily"
          }
        }
      },
      "use_index": index.id
    }

    const results = await cozy.client.fetchJSON('POST', '/data/io.cozy.contacts/_find', query)

Ce code fonctionne comme on s'y attend \o/

edas commented 6 years ago

Là pour des relations N-N entre des documents A et des documents B, vous créez un index partiel pour chaque valeur possible de A. Me trompe-je ?

Si oui ça relève plus du cas particulier que de la solution au problème. Me trompe-je ?

Tel que je le comprends, le relational-pouch a décide de faire de faire simplement des sous requêtes (collecter tous les ID des documents enfants de la première requête, puis faire une seconde requête pour collecter les documents enfants en question)

nono commented 6 years ago

Il faut un index par type de relations. Si tu as des contacts et des groupes de contacts, et que l'on met dans le contact la liste des identifiants des groupes aux quels ce contact appartient (dans le champ groupidfamily), on peut depuis le contact savoir très facilement à quels groupes il appartient. Et via l'index défini par Yannick ci-dessus, on peut récupérer tous les contacts d'un groupe donné. Ça me semble être une piste viable pour nous, pas juste un cas particulier.

edas commented 6 years ago

Ok, sorry. I misunderstood groupidfamily as a variable holding a value, not as a field name.

From a discussion with bruno, with documents like { name: "…" , groups: [ 34, 56, 78] } Couch is able to index them in a way to retreive easily all documents having "34" in the array "groups".

Then it's ok for me.

edas commented 6 years ago

For reference : Réflexions techniques sur les relations entre documents

y-lohse commented 6 years ago

Ok, sorry. I misunderstood groupidfamily as a variable holding a value, not as a field name.

That's correct, but the $elemMatch operator means "any document where the field groups contains at least groupidfamily".

From a discussion with bruno, with documents like { name: "…" , groups: [ 34, 56, 78] } Couch is able to index them in a way to retreive easily all documents having "34" in the array "groups".

So yes, that is correct and what I tried in the example above. The partial index would look like this:

     index: {
        partial_filter_selector: {
          groups: {
            $elemMatch: {
              "$eq": 34
            }
          }
        },
        fields: ['groups']
      }

You could also do more complex queries, for example documents containing 34, 35 and 36 (still in one single index)

edas commented 6 years ago

Hell no! (or you lost me again). With your index, it means you have to create an index for every possible value in the field "groups".

When you have 3 or 4 values (like different statuses) it may be acceptable, but this will never be a solution for a N-N relation because if you have 1000 documents you will end up creating 1000 indexes.

Did I misunderstood again ?

y-lohse commented 6 years ago

Repassons en français, vu le sujet ca sera pas du luxe pour tout le monde^^

Hell no! (or you lost me again). With your index, it means you have to create an index for every possible value in the field "groups".

C'est correct.

Un exemple concret:

io.cozy.contacts
{
  _id: abc,
  name: "…",
  groups: [34, 35, 36]
}
io.cozy.contacts.groups
{
  _id: 34,
  name: "…"
}

Pour récupérer les groupes d'un contact, pas besoin de partial index, un index mango normal fait l'affaire.

Par contre, pour récupérer tous les contacts rattachés à un groupe, il faut un partial index sur io.cozy.contacts:

index: {
  partial_filter_selector: {
    groups: {
      $elemMatch: {
        "$eq": 34
      }
    }
  },
  fields: ['groups']
}

… et la même pour tous les autres groupes.

——————

Donc oui, selon moi, si N=1000, dans un sens il faudra 1000 indexes. Mais quel est le coût de ces index et partials indexes ? Je connais plus MongoDB que Couch, mais dans Mongo faire ce genre de requêtes n'est pas siiiii couteux que ca.

edas commented 6 years ago

Repassons en français, vu le sujet ca sera pas du luxe pour tout le monde^^

Argh. Mes excuses. Je devais sortir d'une revue de code, ou du site de backbone-relational, et je ne me suis même pas rendu compte que je changeais de langue.

Crash-- commented 4 years ago

Je ferme l'issue car c'est maintenant géré dans cozyc-leint