Normalisasi. Kami menderita karenanya atau menulis solusi kami sendiri dengan banyak pemeriksaan untuk keberadaan entitas dalam repositori umum. Mari kita coba mencari tahu dan menyelesaikan masalah ini!
Deskripsi masalah
Bayangkan urutan berikut:
Aplikasi klien meminta daftar pengguna dengan permintaan ke / pengguna dan mendapatkan pengguna dengan id dari 1 hingga 10
Pengguna dengan id 3 mengganti namanya
Aplikasi klien meminta pengguna dengan id 3 menggunakan permintaan ke / user / 3
Pertanyaan: Nama pengguna dengan id 3 apa yang akan ada di aplikasi?
Jawaban: Tergantung pada komponen yang meminta data tersebut. Komponen yang menggunakan data dari request ke / users akan menampilkan nama lama. Komponen yang menggunakan data dari request untuk / user / 3 akan menampilkan nama baru.
Kesimpulan : Dalam hal ini terdapat beberapa entitas yang memiliki arti yang sama dengan kumpulan data yang berbeda dalam sistem.
Pertanyaan: Mengapa buruk?
Jawaban: Dalam kasus terbaik, pengguna akan melihat nama yang berbeda dari satu orang di berbagai bagian situs, dalam kasus terburuk, dia akan mentransfer uang ke detail bank lama.
Opsi solusi
Saat ini, ada solusi berikut untuk masalah ini:
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 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