API CRUD: Bagaimana Anda menentukan bidang mana yang akan diperbarui?


9

Katakanlah Anda memiliki semacam struktur data, yang bertahan dalam beberapa jenis database. Untuk kesederhanaan, sebut saja struktur data ini Person. Anda sekarang ditugaskan untuk merancang API CRUD, yang memungkinkan aplikasi lain untuk membuat, membaca, memperbarui, dan menghapus Person. Untuk mempermudah, mari kita asumsikan bahwa API ini diakses melalui beberapa jenis layanan web.

Untuk bagian C, R dan D dari CRUD, desainnya sederhana. Saya akan menggunakan notasi fungsional mirip C # - implementasinya bisa SOAP, REST / JSON, atau yang lain:

class Person {
    string Name;
    DateTime? DateOfBirth;
    ...
}

Identifier CreatePerson(Person);
Person GetPerson(Identifier);
void DeletePerson(Identifier);

Bagaimana dengan pembaruan? Hal yang wajar untuk dilakukan adalah

void UpdatePerson(Identifier, Person);

tetapi bagaimana Anda menentukan bidang mana yangPerson akan diperbarui?


Solusi yang dapat saya berikan:

  • Anda selalu dapat meminta Orang yang lengkap untuk lulus, yaitu klien akan melakukan sesuatu seperti ini untuk memperbarui tanggal lahir:

    p = GetPerson(id);
    p.DateOfBirth = ...;
    UpdatePerson(id, p);
    

    Namun, itu akan membutuhkan semacam konsistensi transaksional atau penguncian antara Dapatkan dan Pembaruan; jika tidak, Anda bisa menimpa beberapa perubahan lain yang dilakukan secara paralel oleh beberapa klien lain. Ini akan membuat API jauh lebih rumit. Selain itu, ini rawan kesalahan, karena pseudo-code berikut (dengan asumsi bahasa klien dengan dukungan JSON)

    UpdatePerson(id, { "DateOfBirth": "2015-01-01" });
    

    - yang terlihat benar - tidak hanya akan mengubah DateOfBirth tetapi juga mengatur ulang semua bidang lainnya menjadi nol.

  • Anda dapat mengabaikan semua bidang yang ada null. Namun, bagaimana Anda kemudian membuat perbedaan antara tidak berubah DateOfBirth dan dengan sengaja mengubahnya menjadi nol ?

  • Ubah tanda tangan ke void UpdatePerson(Identifier, Person, ListOfFieldNamesToUpdate).

  • Ubah tanda tangan ke void UpdatePerson(Identifier, ListOfFieldValuePairs).

  • Gunakan beberapa fitur protokol transmisi: Misalnya, Anda bisa mengabaikan semua bidang yang tidak terkandung dalam representasi JSON dari Orang tersebut. Namun, itu biasanya membutuhkan penguraian JSON sendiri dan tidak dapat menggunakan fitur built-in perpustakaan Anda (misalnya WCF).

Tidak ada solusi yang tampak sangat elegan bagi saya. Tentunya, ini adalah masalah umum, jadi apa solusi praktik terbaik yang digunakan oleh semua orang?


Mengapa pengenal bukan bagian dari orang tersebut? Untuk Personcontoh yang baru dibuat yang masih belum ada, dan dalam hal pengidentifikasi diputuskan sebagai bagian dari mekanisme ketekunan, biarkan saja nol. Adapun jawabannya, JPA menggunakan nomor versi; jika Anda membaca versi 23, ketika Anda memperbarui item jika versi dalam DB adalah 24 penulisan gagal.
SJuan76

Izinkan dan komunikasikan keduanya PUTdan PATCHmetode. Saat menggunakan PATCH, hanya mengirim kunci yang harus diganti, dengan PUTseluruh objek diganti.
Lode

Jawaban:


8

Jika Anda tidak memiliki pelacakan perubahan sebagai persyaratan pada objek ini (mis. "Pengguna John mengubah nama dan tanggal lahir"), yang paling sederhana adalah menimpa seluruh objek dalam DB dengan yang Anda terima dari konsumen. Pendekatan ini akan melibatkan sedikit lebih banyak data yang dikirim melalui kabel, tetapi Anda menghindari membaca sebelum memperbarui.

Jika Anda memiliki persyaratan pelacakan aktivitas. Dunia Anda jauh lebih rumit dan Anda perlu merancang cara menyimpan informasi tentang tindakan CRUD dan cara mencegatnya. Itu adalah dunia yang tidak ingin Anda selami jika Anda tidak memiliki persyaratan seperti itu.

Sesuai nilai-nilai utama dengan transaksi terpisah, saya akan menyarankan untuk melakukan riset seputar penguncian optimis dan pesimistis . Mereka mengurangi skenario umum ini:

  1. Objek dibaca oleh pengguna1
  2. Objek dibaca oleh user2
  3. Objek yang ditulis oleh pengguna1
  4. Objek ditulis oleh user2 dan perubahan ditimpa oleh user1

Setiap pengguna memiliki transaksi yang berbeda, oleh karena itu standar SQL dengan ini. Paling umum adalah penguncian optimis (juga disebutkan oleh @ SJuan76 dalam komentar tentang versi). Versi Anda adalah catatan Anda dalam DB dan saat menulis, pertama-tama Anda melihat ke DB jika versi cocok. Jika versi tidak cocok, Anda tahu seseorang memperbarui objek sementara itu, dan Anda perlu merespons dengan pesan kesalahan kepada konsumen tentang situasi ini. Ya, Anda harus menunjukkan situasi ini kepada pengguna.

Perhatikan bahwa Anda perlu membaca catatan aktual dari DB sebelum menulisnya (untuk perbandingan versi penguncian yang optimis), jadi menerapkan logika delta (hanya menulis nilai yang diubah) mungkin tidak memerlukan kueri baca tambahan sebelum menulis.

Memasukkan ke dalam logika delta sangat tergantung pada kontrak dengan konsumen, tetapi perhatikan bahwa yang termudah bagi konsumen adalah membangun muatan penuh alih-alih delta juga.


2

Kami memiliki API PHP yang berfungsi. Untuk pembaruan jika bidang tidak dikirim dalam objek JSON, ia akan disetel ke NULL. Kemudian semuanya diteruskan ke prosedur tersimpan. Prosedur tersimpan mencoba untuk memperbarui setiap bidang dengan bidang = IFNULL (input, bidang). Jadi jika hanya 1 bidang yang ada di objek JSON hanya bidang itu yang diperbarui. Untuk secara eksplisit mengosongkan bidang yang ditetapkan, kita harus memiliki bidang = '', DB kemudian memperbarui bidang dengan string kosong atau nilai default kolom itu.


3
Bagaimana Anda dengan sengaja mengatur bidang ke nol yang belum nol?
Robert Harvey

Semua bidang ditetapkan TIDAK NULL, jadi default bidang CHAR mendapatkan '' dan semua bidang bilangan bulat mendapatkan 0.
Jared Bernacchi

1

Tentukan daftar bidang yang diperbarui dalam String Kueri.

PUT /resource/:id?fields=name,address,dob Body { //resource body }

Terapkan penggabungan data yang tersimpan dengan model dari badan permintaan:

private ResourceModel MergeResourceModel(ResourceModel original, ResourceModel updated, List<string> fields)
{
    var comparer = new FieldComparer();

    foreach (
            var item in
            typeof (ResourceModel).GetProperties()
                    .Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof (JsonIgnoreAttribute))))
    {
        if (fields.Contains(item.Name, comparer))
        {
            var property = typeof (ResourceModel).GetProperty(item.Name);
            property.SetValue(original, property.GetValue(updated));
        }
    }

    return original;
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.