iresine, clientseitige Datennormalisierung

Normalisierung. Wir leiden entweder darunter oder schreiben unsere eigene Lösung mit vielen Überprüfungen auf die Existenz einer Entität in einem gemeinsamen Repository. Lassen Sie uns versuchen, es herauszufinden und dieses Problem zu lösen!





Beschreibung des Problems

Stellen wir uns folgende Sequenz vor:





  1. Die Clientanwendung fordert eine Liste von Benutzern mit einer Anfrage an / users an und ruft Benutzer mit einer ID von 1 bis 10 ab





  2. Benutzer mit ID 3 ändert seinen Namen





  3. Die Clientanwendung fordert den Benutzer mit der ID 3 unter Verwendung einer Anforderung an / user / 3 an





Frage:  Welcher Benutzername mit ID 3 wird in der Anwendung enthalten sein?

Antwort:  Hängt von der Komponente ab, die die Daten angefordert hat. Die Komponente, die die Daten aus der Anforderung an / Benutzer verwendet, zeigt den alten Namen an. Die Komponente, die die Daten aus der Anforderung für / user / 3 verwendet, zeigt den neuen Namen an.





Schlussfolgerung : In diesem Fall gibt es mehrere Entitäten derselben Bedeutung mit unterschiedlichen Datensätzen im System.





Frage:  Warum ist es schlecht?

Antwort:  Im besten Fall sieht der Benutzer verschiedene Namen einer Person in verschiedenen Bereichen der Website, im schlimmsten Fall überweist er Geld an die alten Bankdaten.





Lösungsoptionen

Derzeit gibt es die folgenden Lösungen für dieses Problem:













  • graphql (apollo relay)









. . , ? , ?









mobx:





class Store {
  users = new Map();

  async getUsers() {
    const users = await fetch(`/users`);
    users.forEach((user) => this.users.set(user.id, user));
  }

  async getUser(id) {
    const user = await fetch(`/user/${id}`);
    this.users.set(user.id, user);
  }
}
      
      



mobx , redux  .



graphql (apollo relay)



Apollo relay , . graphql apollo, , , .





graphql ? apollo! apollo :





...normalizes query response objects before it saves them to its internal data store.





 normalize?





Normalization involves the following steps:





1. The cache generates a unique ID for every identifiable object included in the response.

2. The cache stores the objects by ID in a flat lookup table.





apollo , . Apollo . :





const store = new Map();

const user = {
  id: '0',
  type: 'user',
  name: 'alex',
  age: 24,
};

const id = `${user.type}:${user.id}`;

store.set(id, user);
      
      



id - . , id, .





Apollo , __typename, graphql?





, . :





  • id





















:





const store = new Map();

const user = {
  id: '0',
};

const comment = {
  id: '1',
};

store.set(user.id, user);
store.set(comment.id, comment);

// ...

store.get('0'); // user
store.get('1'); // comment
      
      



, id . , id / ( - ). , id , .









:





const store = new Map();

const user = {
  id: '0',
  type: 'user', // <-- new field
};

const comment = {
  id: '1',
  type: 'comment', // <-- new field
};

function getStoreId(entity) {
  return `${entity.type}:${entity.id}`;
}

store.set(getStoreId(user), user);
store.set(getStoreId(comment), comment);

// ...

store.get('user:0'); // user
store.get('comment:1'); // comment
      
      



- , . . .





?

. - . .





  • , :





app.get('/users', (req, res) => {
  const users = db.get('users');
  const typedUsers = users.map((user) => ({
    ...user,
    type: 'user',
  }));
  res.json(typedUsers);
});
      
      



  • , :





function getUsers() {
  const users = fetch('/users');
  const typedUsers = users.map((user) => ({
    ...user,
    type: 'user',
  }));
  return typedUsers;
}
      
      



. Api, , . , .





.





iresine

iresine  .





iresine :





  • @iresine/core





  • @iresine/react-query





iresine react-query:





@iresine/core

, , .





const iresine = new Iresine();
const oldRequest = {
  users: [oldUser],
  comments: {
    0: oldComment,
  },
};
// new request data have new structure, but it is OK to iresine
const newRequest = {
  users: {
    0: newUser,
  },
  comments: [newComment],
};

iresine.parse(oldRequest);
iresine.parse(newRequest);

iresine.get('user:0' /*identifier for old and new user*/) === newRequest.users['0']; // true
iresine.get('comment:0' /*identifier for old and new comment*/) === newRequest.comments['0']; // true
      
      



, , @iresine/core :





entityType + ':' + entityId;
      
      



@iresine/core  type



, id  id



. , . apollo:





const iresine = new Iresine({
  getId: (entity) => {
    if (!entity) {
      return null;
    }
    if (!entity.id) {
      return null;
    }
    if (!entity.__typename) {
      return null;
    }
    return `${entity.__typename}:${entity.id}`;
  },
});
      
      



id:





const iresine = new Iresine({
  getId: (entity) => {
    if (!entity) {
      return null;
    }
    if (!entity.id) {
      return null;
    }
    return entity.id;
  },
});
      
      



@iresine/core , ? :





const user = {
  id: '0',
  type: 'user',
  jobs: [
    {
      name: 'milkman',
      salary: '1$',
    },
    {
      name: 'woodcutter',
      salary: '2$',
    },
  ],
};
      
      



user , jobs? type id! @iresine/core : , .





@iresine/core , . ! .





@iresine/react-query

react-query , . , iresine.





@iresine/react-query react-query. @iresine/core react-query. react-query , iresine.





import Iresine from '@iresine/core';
import IresineReactQuery from '@iresone/react-query';
import {QueryClient} from 'react-query';

const iresineStore = new IresineStore();
const queryClient = new QueryClient();
new IresineReactQueryWrapper(iresineStore, queryClient);
// now any updates in react-query store will be consumbed by @iresine/core
      
      



( ):





. . . ,   ,  iresine








All Articles