Menyimpan CRUD di IndexedDB

Katakanlah kita memiliki backend yang dapat menyimpan beberapa jenis entitas. Dan itu memiliki api untuk membuat, membaca, memodifikasi dan menghapus entitas ini, disingkat CRUD. Tetapi api ada di server, dan pengguna berada di suatu tempat yang dalam dan setengah dari permintaan jatuh pada waktu tunggu. Saya tidak ingin menampilkan prapemuat tanpa akhir dan biasanya memblokir tindakan pengguna. Offline pertama mengasumsikan memuat aplikasi dari cache, jadi mungkin data harus diambil dari sana?





Disarankan untuk menyimpan semua data di IndexedDB (misalkan jumlahnya tidak terlalu banyak), dan, jika memungkinkan, sinkronkan dengan server. Beberapa masalah muncul:





  1. Jika Id entitas dibuat di server, di database, lalu bagaimana hidup tanpa Id sementara server tidak tersedia?





  2. Saat menyinkronkan dengan server, bagaimana cara membedakan entitas yang dibuat di klien dari yang dihapus di server oleh pengguna lain?





  3. Bagaimana cara menyelesaikan konflik?





Identifikasi

Pengenal itu diperlukan, jadi kami akan membuatnya sendiri. GUID atau `+ new Date ()` baik-baik saja untuk ini, dengan beberapa peringatan. Hanya ketika respons datang dari server dengan Id yang sebenarnya, Anda perlu menggantinya di mana saja. Jika entitas yang baru dibuat ini sudah direferensikan oleh orang lain, maka tautan ini juga perlu diperbaiki.





Sinkronisasi

Kami tidak akan menemukan kembali roda, mari kita lihat replikasi database. Anda dapat melihatnya tanpa henti, seperti api, tetapi singkatnya, salah satu opsinya terlihat seperti ini: selain menyimpan entitas di IndexedDB, kami akan menulis log perubahan: [waktu, 'update', Id = 3 , Name = 'Ivan'], [time, 'create', Name = 'Ivan', Surname = 'Petrov'], [time, 'delete', Id = 3] ...





, . , , IndexedDB. Id.





- , , . , - , . - , , . , : , , , . Eventual Consistency.





, , . Operational Transformations (OT) Conflict-free Replicated Data Types (CRDT) . , CRDT : UpdatedAt . , .





, Id . , . , , . . , , Id , . - . . , . Last write win. Eventual Consistency: , . .





function mergeLogs(left, right){
    const ids = new Set([
        ...left.map(x => x.id),
        ...right.map(x => x.id)
    ]);
    return [...ids].map(id => mergeIdLogs(
        left.filter(x => x.id == id),
        right.filter(x => x.id ==id)
    )).reduce((a,b) => ({
        left: [...a.left, ...b.left],
        right: [...a.right, ...b.right]
    }), {left: [], right: []});
}

function mergeIdLogs(left,right){
    const isWin = log => log.some(x => ['create','delete'].includes(x.type));
    const getMaxUpdate = log => Math.max(...log.map(x => +x.updatedAt));

    if (isWin(left))
        return {left: [], right: left};
    if (isWin(right))
        return {left: right, right: []};
    if (getMaxUpdate(left) > getMaxUpdate(right))
        return {left: [], right: left};
    else
        return {left: right, right: []};
}
      
      



Tidak akan ada implementasi, karena dalam setiap kasus tertentu ada masalah dalam detailnya, dan, pada umumnya, tidak ada yang perlu diterapkan di sini - pembuatan pengenal dan penulisan ke indexedDB.





Tentu saja, CRDT atau OT akan lebih baik, tetapi jika Anda perlu melakukannya dengan cepat, tetapi mereka tidak diizinkan di backend, maka pekerjaan ini akan berhasil.








All Articles