Bagaimana seseorang dapat memisahkan logika bisnis dari panggilan API.

Motivasi dan gambaran umum

Tujuan kami adalah mengotomatiskan permintaan API tertentu dengan menggunakan bahasa kueri tertentu. Alih-alih permintaan API langsung, seseorang mengirimkan kueri yang berisi panggilan metode. Setiap metode dapat ditangani secara langsung atau didelegasikan ke instansi lain. Jika metode didelegasikan, bagian kueri yang berisi pemanggilan metode akan dikirim ke instance lain. Fitur utama dari pendekatan ini adalah kemampuan untuk membagi kueri menjadi beberapa bagian dan mengeksekusi setiap bagian secara terpisah seperti kueri lainnya. Masing-masing potongan tersebut mendapat satu penyelesai. Resolver ditugaskan ke potongan secara otomatis berdasarkan metode yang didelegasikan. Kueri juga dapat berisi pernyataan penetapan variabel, kondisi, dan pengembalian, yang memungkinkan kita menggunakan logika dasar dalam kueri. Kami telah mengembangkan bukti konsep pada JavaScript, perpustakaannya disebut limbodan tersedia di npm.

Seperti apa pertanyaan tersebut?

Kueri terdiri dari baris-baris, setiap baris harus diakhiri dengan ; . Baris-baris tersebut dapat mengoperasikan data menggunakan kumpulan operator yang ditentukan. Data dapat disajikan sebagai primitif (string, angka, Boolean), array, atau objek. Untuk mendefinisikan array dan objek, kami menggunakan format JSON. Operator berikutnya tersedia:

  1. Panggilan metode. Sebenarnya memanggil suatu metode, Satu parameter dapat dikirim. Parameter ini dapat berupa string, boolean, angka, array atau objek.
    methodName ~ {"key" : "val"};
  2. Kondisi. Mengeksekusi subkueri berdasarkan suatu kondisi. Subkueri harus dimulai dengan @{;dan diakhiri dengan };
    ? $varName == "value" @{;
    $result = method ~ $varName;
    } : @{;
    $result = method2 ~ $varName;
    };
  3. Menetapkan. Menetapkan nilai ke variabel. Begitulah cara kami menyimpan data untuk digunakan lebih lanjut. Setiap nama variabel harus dimulai dengan “$”.
    $varName = "value"
  4. Nilai kembalian. Mengakhiri kueri, mengembalikan nilainya.
    =>$reuslt;

Operator dalam satu baris dieksekusi sesuai urutan yang disajikan di atas. Kita dapat menggunakan tanda kurung () untuk mengubah urutannya. Kita juga harus menggunakan tanda kurung saat memanggil operator dalam definisi objek JSON:
=>{"key" : (method ~ "val")};

Contoh:

$result = method ~ {"key" : "val"}; 
? $result.success == true @{;
    =>$result;
} : @{;
    writeErrorLog ~ $result.error;
    => {
        "success" : false, 
        "error" : "Error during calling /"method/""
   };
};

Metode penanganan dan pendelegasian. Menjalankan kueri

Satu instance dapat menangani beberapa metode dan mendelegasikan metode lainnya. Jika metode didelegasikan, callback akan mendapatkan potongan berdasarkan metode yang didelegasikan. Jika tidak, callback hanya akan mendapatkan parameter yang ditentukan dalam pemanggilan metode dan nama metode.

Mendelegasikan

Untuk mendelegasikan suatu metode, kita harus memanggil metode "delegasi" dari instance tersebut.

delegasi(pilihan : objek) : batal
pilihan.regExp : RegExp. Metode yang cocok dengan regExp akan didelegasikan.
options.handle : Function(query : String). Fungsi callback.

Penanganan

Jika kita ingin menangani metode dengan instance saat ini, kita harus memanggil metode addHandler dari instance tersebut.

addHandler(options : objek) : void
options.regExp : RegExp. Metode yang cocok dengan regExp akan diproses oleh handler saat ini.
options.handle : Function(param, methodName). Fungsi callback.

addHandlers(Handlers : Array) : void
Menambahkan beberapa penangan.

Menjalankan kueri

Untuk mengeksekusi query, kita memanggil metode “call” dari instance tersebut.

panggilan(kueri : String) : Janji
kueri : String. Permintaan yang ingin kita jalankan.

Alur eksekusi kueri

Sebelum dieksekusi, algoritme menjalankan kueri dan menentukan bagian mana yang harus benar-benar dieksekusi oleh instance saat ini dan bagian mana yang harus didelegasikan ke instance lain. Proses ini terdiri dari langkah-langkah selanjutnya:

  1. Bangun potongan
  2. Jalankan potongan secara lokal atau kirim ke instance lain
  3. Hasil proses
  4. Lanjutkan ke item 1 jika belum selesai

Memisahkan dan menugaskan penyelesai

Selama pemrosesan, kueri dipecah menjadi beberapa bagian berdasarkan metode yang didelegasikan. saat memisahkan, kami mencoba mengurangi jumlah potongan dan sebagai hasilnya potensi jumlah permintaan API. Karena kueri mungkin berisi kondisi, mungkin ada blok bersarang dan kebutuhan untuk menangani kasus ketika sebuah potongan berakhir di blok bersarang tersebut. Untuk mengatasi tantangan ini, kami menambahkan operator lain ->@_bookmark Yang kembali ke bookmark. Operator ini ditempatkan secara otomatis selama pemisahan bongkahan. Jika potongan saat ini mengembalikan sebuah bookmark, eksekusi kode selanjutnya harus dimulai dari baris yang ditentukan dalam bookmark. Misalnya:

$res1 = resolver1.method1 ~ "string";
? $res1.success == true @{;
    resolver1.method2 ~ "anotherString";
    $res2 = resolver2.method1 ~ $res1;
    ? res2.success @{;
        resolver1.method3 ~ $res2;
    };
    =>resolver2.method2 ~ $res2;
};
=>resolver1.method4 ~ $res1;

Untuk permintaan seperti itu, solver1 akan mendapatkan yang berikutnya:

$res1 = resolver1.method1 ~ "string";
? $res1.success == true @{;
    resolver1.method2 ~ "anotherString";
    ->@_0;
};
=>resolver1.method3 ~ $res1;

Kemudian jika $res1.success akan menjadi penyelesai palsu2 tidak akan pernah dipanggil. Jika tidak, maka akan didapat:

$res2 = resolver2.method1 ~ $res1;
? res2.success == true @{;
    ->@_1;
};
=>resolver2.method2 ~ $res2;

Dan jika $res2.success salah, Resolver1 akan menerima panggilan terakhir:

resolver1.method3 ~ $res2;
=>resolver1.method4 ~ $res1;

$res1 dan $res2 akan diganti dengan data sebenarnya.

Algoritma pembagian dan penyelesaian kueri secara keseluruhan terlihat seperti ini:

Kasus penggunaan

  1. Kami memiliki klien yang berinteraksi dengan banyak API. API tersebut direpresentasikan sebagai titik akhir REST dan/atau GraphQL. Klien kami, dalam hal ini, menggunakan limbo sebagai satu titik untuk mendapatkan dan memanipulasi data dari API tersebut.

2. Kami memiliki server yang menerima pertanyaan dan memprosesnya.

Kasus penggunaan tersebut dapat digabungkan. Satu klien dapat memanggil API yang mengimplementasikan limbo dan yang tidak. Pada saat yang sama, server dapat memanggil API lain dan mendelegasikan kueri kepada mereka.

Kesimpulan

Pendekatan seperti ini mungkin cocok untuk arsitektur layanan mikro karena pendekatan ini memisahkan logika bisnis dari panggilan API dan memungkinkan satu kueri dieksekusi oleh beberapa instance berdasarkan kebutuhan. Kueri itu sendiri tidak boleh diubah ketika layanan memungkinkan solusi dapat diskalakan.

Karena ini hanyalah bukti konsep, masih ada ruang untuk perbaikan seperti:

  1. loop. Sekarang bahasa kueri tidak mendukung loop, Ini akan segera diperbaiki.
  2. Alur eksekusi kueri asinkron. Karena ini hanya bukti konsep, aliran asinkron tidak sepenuhnya optimal (garis dijalankan satu demi satu meskipun independen). Kami akan mengizinkan eksekusi paralel beberapa baris atau potongan.

Meskipun demikian, perpustakaan tersebut siap digunakan dan dalam waktu dekat akan menjadi lebih tangguh. Pendekatan ini sendiri berpotensi meningkatkan proses pengembangan layanan mikro.