Kontrak publik, bagaimana memastikan konsistensinya

  • Apakah sistem Anda terdiri dari banyak layanan yang saling berhubungan?
  • masih memperbarui kode layanan secara manual saat mengubah API publik?
  • perubahan layanan Anda sering merusak pekerjaan orang lain, dan pengembang lain membenci Anda karena ini?

Jika Anda menjawab ya setidaknya sekali, selamat datang!

Ketentuan


Kontrak publik, spesifikasi - antarmuka publik tempat Anda dapat berinteraksi dengan layanan. Dalam teks mereka memiliki arti yang sama.

Tentang apa artikel itu


Pelajari cara mengurangi waktu yang dihabiskan untuk mengembangkan layanan web menggunakan alat untuk deskripsi kontrak dan pembuatan kode otomatis.

Penggunaan teknik dan alat yang dijelaskan di bawah ini akan memungkinkan Anda untuk dengan cepat meluncurkan fitur baru dan tidak merusak yang lama.

Seperti apa masalahnya


Ada sistem yang terdiri dari beberapa layanan. Layanan ini ditugaskan untuk tim yang berbeda.



Layanan konsumen tergantung pada penyedia layanan.
Sistem berkembang, dan suatu hari penyedia layanan mengubah kontrak publiknya.



Jika layanan konsumen tidak siap untuk perubahan, maka sistem berhenti bekerja sepenuhnya.



Bagaimana mengatasi masalah ini


Tim layanan pemasok akan memperbaiki semuanya


Ini dapat dilakukan jika tim pemasok memiliki area subjek layanan lain dan memiliki akses ke repositori git mereka. Ini akan bekerja hanya dalam proyek-proyek kecil ketika ada beberapa layanan dependen. Ini adalah opsi termurah. Jika memungkinkan, Anda harus menggunakannya.

Perbarui kode layanan Anda ke tim konsumen


Mengapa ada yang rusak, tetapi apakah kita sedang memperbaiki?

Namun, pertanyaan utamanya adalah bagaimana cara memperbaiki layanan Anda, seperti apa kontraknya sekarang? Anda perlu mempelajari kode layanan penyedia baru atau menghubungi tim mereka. Kami menghabiskan waktu mempelajari kode dan berinteraksi dengan tim lain.

Pikirkan apa yang harus dilakukan untuk mencegah masalah muncul


Opsi paling masuk akal dalam jangka panjang. Pertimbangkan itu di bagian selanjutnya.

Bagaimana mencegah manifestasi suatu masalah


Siklus hidup pengembangan perangkat lunak dapat direpresentasikan dalam tiga tahap: desain, implementasi dan pengujian.

Setiap langkah perlu diperluas sebagai berikut:

  1. pada tahap desain secara deklaratif mendefinisikan kontrak;
  2. selama implementasi, kami menghasilkan kode server dan klien di bawah kontrak;
  3. saat menguji, kami memeriksa kontrak dan mencoba mempertimbangkan kebutuhan pelanggan (CDC).

Setiap langkah dijelaskan lebih lanjut sebagai contoh dari masalah kita.

Bagaimana masalah terlihat dengan kita




Seperti inilah ekosistem kita.
Lingkaran adalah layanan, dan panah adalah saluran komunikasi di antara mereka.

Frontend adalah aplikasi klien berbasis web.

Sebagian besar panah mengarah ke Layanan Penyimpanan. Ini menyimpan dokumen. Ini adalah layanan yang paling penting. Bagaimanapun, produk kami adalah sistem manajemen dokumen elektronik.

Jika layanan ini mengubah kontraknya, sistem akan segera berhenti bekerja.



Sumber sistem kami sebagian besar ditulis dalam c #, tetapi ada juga layanan di Go dan Python. Dalam konteks ini, tidak masalah apa yang dilakukan layanan lain dalam gambar.



Setiap layanan memiliki implementasi klien sendiri untuk bekerja dengan layanan penyimpanan. Saat mengganti kontrak, Anda harus memperbarui kode secara manual di setiap proyek.

Saya ingin menghindari pembaruan manual ke otomatis. Ini akan membantu meningkatkan laju perubahan kode klien dan mengurangi kesalahan. Kesalahan adalah kesalahan ketik pada URL, kesalahan karena kecerobohan, dll.

Namun, pendekatan ini tidak memperbaiki kesalahan dalam logika bisnis klien. Anda hanya dapat menyesuaikannya secara manual.

Dari masalah ke tugas


Dalam kasus kami, diperlukan untuk menerapkan pembuatan kode klien secara otomatis.
Dengan demikian, hal-hal berikut harus dipertimbangkan:

  • sisi server - pengendali sudah ditulis;
  • browser adalah klien dari layanan;
  • Layanan berkomunikasi melalui HTTP.
  • generasi harus disetel. Misalnya, untuk mendukung JWT.

Pertanyaan


Dalam penyelesaian masalah, muncul pertanyaan:

  • alat mana yang harus dipilih;
  • cara mendapatkan kontrak;
  • di mana menempatkan kontrak;
  • di mana menempatkan kode klien;
  • pada titik apa yang harus dilakukan generasi.

Berikut ini adalah jawaban untuk pertanyaan-pertanyaan ini.

Alat mana yang harus dipilih


Alat untuk bekerja dengan kontrak disajikan dalam dua arah - RPC dan REST.



RPC dapat dipahami hanya sebagai panggilan jarak jauh, sementara REST membutuhkan ketentuan tambahan untuk kata kerja dan URL HTTP.

Perbedaan dalam panggilan RPC dan REST disajikan di sini.
RPC - Panggilan prosedur jarak jauhREST Representational State Transfer
, HTTP- URL
Restaurant:8080/Orders/PlaceOrderPOSTRestaurant:8080/Orders
Restaurant:8080/Orders/GetOrder?OrderNumber=1GETRestaurant:8080/Orders/1
Restaurant:8080/Orders/UpdateOrderPUTRestaurant:8080/Orders/1


Alat


Tabel menunjukkan perbandingan alat untuk bekerja dengan REST dan RPC.
PropertiOpenapiWsdlPenghematangRPC
Sebuah tipeBERISTIRAHATRpc
PeronTidak tergantung
LidahTidak tergantung
Urutan pengembangan *kode terlebih dahulu, spek dulukode terlebih dahulu, spek duluspec pertamakode terlebih dahulu, spek dulu
Protokol transportasiHTTP / 1.1any (REST membutuhkan HTTP)sendiriHTTP / 2
Melihatspesifikasikerangka
KomentarAmbang entri rendah, banyak dokumentasiRedundansi XML, SABUN, dll.Ambang entri tinggi, sedikit dokumentasiAmbang entri rata-rata, dokumentasi yang lebih baik
Kode pertama - pertama kita menulis bagian server, dan kemudian kita mendapatkan kontrak untuk itu. Lebih mudah ketika sisi server sudah ditulis. Tidak perlu menggambarkan kontrak secara manual.

Spec pertama - pertama kita mendefinisikan kontrak, lalu kita mendapatkan bagian klien dan bagian server dari mereka. Lebih mudah di awal pengembangan ketika belum ada kode.

Output

WSDL tidak cocok karena redundansi.

Apache Thrift terlalu eksotis dan sulit dipelajari.

GRPC membutuhkan net Core 3.0 dan net Standard 2.1. Pada saat analisis, net Core 2.2 dan net Standard 2.0 digunakan. Tidak ada dukungan GRPC di browser di luar kotak, solusi tambahan diperlukan. GRPC menggunakan serialisasi biner Protobuf dan HTTP / 2. Karena itu, berbagai utilitas untuk menguji API seperti tukang pos, dll. Semakin menyempit. Pengujian beban melalui beberapa JMeter mungkin memerlukan upaya tambahan. Tidak cocok, beralih ke GRPC membutuhkan banyak sumber daya.

OpenAPI tidak memerlukan pembaruan tambahan. Ini memikat banyak alat yang mendukung bekerja dengan REST dan spesifikasi ini. Kami memilihnya.

Alat untuk bekerja dengan OpenAPI


Tabel menunjukkan perbandingan alat untuk bekerja dengan OpenAPI.
AlatswashbuckleNSwagOpenapitools
Versi Spesifikasi yang DidukungDapat menghasilkan spesifikasi dalam format OpenApi v2, v3
Dukungan kode pertamaadaadaTidak
Bahasa Server yang DidukungTidakC #Banyak
Bahasa pelanggan yang didukungTidakC #, TypeScript, AngularJS, Angular (v2 +), window.fetch APIBanyak
Pengaturan GenerasiTidakadaada
MelihatPaket nugetPaket nuget + utilitas terpisahUtilitas terpisah
Kesimpulan dari

Swashbuckle tidak cocok, karena memungkinkan Anda untuk hanya mendapatkan spesifikasinya. Untuk menghasilkan kode klien, Anda perlu menggunakan solusi tambahan.

OpenApiTools adalah alat yang menarik dengan banyak pengaturan, tetapi tidak mendukung kode terlebih dahulu. Keuntungannya adalah kemampuan untuk menghasilkan kode server dalam banyak bahasa.

NSwag nyaman karena merupakan paket Nuget. Sangat mudah untuk terhubung saat membangun proyek. Mendukung semua yang kami butuhkan: pendekatan kode pertama dan pembuatan kode klien dalam c #. Kami memilihnya.

Di mana mengatur kontrak. Cara mengakses layanan ke kontrak


Berikut adalah solusi untuk mengatur penyimpanan kontrak. Solusinya terdaftar dalam urutan meningkatnya kompleksitas.

  • Folder proyek layanan penyedia adalah pilihan termudah. Jika Anda perlu menjalankan pendekatan, maka pilihlah.
  • folder bersama adalah opsi yang valid jika proyek yang diinginkan dalam repositori yang sama. Dalam jangka panjang, akan sulit untuk mempertahankan integritas kontrak dalam folder tersebut. Ini mungkin memerlukan alat tambahan untuk menjelaskan berbagai versi kontrak, dll.
  • repositori terpisah untuk spesifikasi - jika proyek berada dalam repositori yang berbeda, maka kontrak harus ditempatkan di tempat umum. Kerugiannya sama dengan folder bersama.
  • melalui API layanan (swagger.ui, swaggerhub) - layanan terpisah yang berhubungan dengan manajemen spesifikasi.

Kami memutuskan untuk menggunakan opsi paling sederhana - untuk menyimpan kontrak di folder proyek penyedia layanan. Ini cukup bagi kita pada tahap ini, jadi mengapa membayar lebih?

Pada titik apa yang Anda hasilkan


Sekarang Anda perlu memutuskan pada titik apa untuk melakukan pembuatan kode.
Jika kontrak dibagikan, layanan konsumen dapat menerima kontrak dan menghasilkan kode sendiri jika perlu.

Kami memutuskan untuk menempatkan kontrak dalam folder dengan proyek penyedia layanan. Ini berarti bahwa pembangkitan dapat dilakukan setelah perakitan proyek layanan pemasok itu sendiri.

Di mana menempatkan kode klien


Kode klien akan dihasilkan oleh kontrak. Masih mencari tahu di mana menempatkannya.
Sepertinya ide yang bagus untuk meletakkan kode klien dalam proyek StorageServiceClientProxy terpisah. Setiap proyek akan dapat menghubungkan perakitan ini.

Manfaat dari solusi ini:

  • kode klien dekat dengan layanannya dan selalu terbaru;
  • konsumen dapat menggunakan tautan ke proyek dalam satu gudang.

Kekurangan:

  • tidak akan berfungsi jika Anda perlu membuat klien di bagian lain dari sistem, misalnya, repositori yang berbeda. Itu dipecahkan menggunakan setidaknya folder bersama untuk kontrak;
  • konsumen harus ditulis dalam bahasa yang sama. Jika Anda membutuhkan klien dalam bahasa lain, Anda perlu menggunakan OpenApiTools.

Kami kencangkan NSwag


Atribut Kontroler


Perlu memberi tahu NSwag cara membuat spesifikasi yang benar untuk pengontrol kami.

Untuk melakukan ini, Anda perlu mengatur atributnya.

[Microsoft.AspNetCore.Mvc.Routing.Route("[controller]")]  //  url
[Microsoft.AspNetCore.Mvc.ApiController] //     
public class DescriptionController : ControllerBase {
[NSwag.Annotations.OpenApiOperation("GetDescription")] //    
[Microsoft.AspNetCore.Mvc.ProducesResponseType(typeof(ConversionDescription), 200)] //    200  
[Microsoft.AspNetCore.Mvc.ProducesResponseType(401)] //    401
[Microsoft.AspNetCore.Mvc.ProducesResponseType(403)] //    403
[Microsoft.AspNetCore.Mvc.HttpGet("{pluginName}/{binaryDataId}")] //  url
public ActionResult<ConversionDescription> GetDescription(string pluginName, Guid binaryDataId) { 
 // ... 
}

Secara default, NSwag tidak dapat menghasilkan spesifikasi yang benar untuk aplikasi tipe MIME / octet-stream. Misalnya, ini bisa terjadi ketika file ditransfer. Untuk memperbaikinya, Anda perlu menulis atribut dan prosesor Anda untuk membuat spesifikasi.

[Microsoft.AspNetCore.Mvc.Route("[controller]")]
[Microsoft.AspNetCore.Mvc.ApiController]
public class FileController : ControllerBase {
[NSwag.Annotations.OpenApiOperation("SaveFile")]
[Microsoft.AspNetCore.Mvc.ProducesResponseType(401)]
[Microsoft.AspNetCore.Mvc.ProducesResponseType(403)]
[Microsoft.AspNetCore.Mvc.HttpPost("{pluginName}/{binaryDataId}/{fileName}")]
[OurNamespace.FileUploadOperation] //  
public async Task SaveFile() { // ... }

Prosesor untuk menghasilkan spesifikasi untuk operasi file


Idenya adalah Anda dapat menulis atribut dan prosesor Anda untuk menangani atribut ini.

Kami menggantung atribut pada controller, dan ketika NSwag memenuhinya, itu akan memprosesnya menggunakan prosesor kami.

Untuk mengimplementasikan ini, NSwag menyediakan kelas OpenApiOperationProcessorAttribute dan IOperationProcessor.

Dalam proyek kami, kami membuat ahli waris kami:

  • FileUploadOperationAttribute: OpenApiOperationProcessorAttribute
  • FileUploadOperationProcessor: IOperationProcessor

Baca lebih lanjut tentang menggunakan prosesor di sini.

Konfigurasi NSwag untuk pembuatan spec dan kode


Dalam konfigurasi 3 bagian utama:

  • runtime - Menentukan runtime .net. Misalnya, NetCore22;
  • documentGenerator - menjelaskan cara membuat spesifikasi;
  • codeGenerators - mendefinisikan cara membuat kode sesuai dengan spesifikasi.

NSwag berisi banyak pengaturan, yang pada awalnya membingungkan.

Untuk kenyamanan, Anda dapat menggunakan NSwag Studio. Dengan menggunakannya, Anda dapat melihat secara real time bagaimana berbagai pengaturan memengaruhi hasil pembuatan atau spesifikasi kode. Setelah itu, secara manual pilih pengaturan yang dipilih dalam file konfigurasi.

Baca lebih lanjut tentang pengaturan konfigurasi di sini

Kami menghasilkan spesifikasi dan kode klien ketika merakit proyek penyedia layanan


Sehingga setelah perakitan proyek penyedia layanan, spesifikasi dan kode dihasilkan, lakukan hal berikut:

  1. Kami membuat proyek WebApi untuk klien.
  2. Kami menulis konfigurasi untuk Nswag CLI - Nswag.json (dijelaskan di bagian sebelumnya).
  3. Kami menulis Target PostBuild di dalam proyek penyedia layanan csproj.

<Target Name="GenerateWebApiProxyClient“ AfterTargets="PostBuildEvent">
<Exec Command="$(NSwagExe_Core22) run nswag.json”/>

  • $ (NSwagExe_Core22) jalankan nswag.json - jalankan utilitas NSwag di bawah .bet runtine netCore 2.2 dengan konfigurasi nswag.json

Target melakukan hal berikut:

  1. NSwag menghasilkan spesifikasi dari unit layanan vendor.
  2. NSwag menghasilkan kode klien sesuai spesifikasi.

Setelah setiap perakitan proyek penyedia layanan, proyek klien diperbarui.
Proyek klien dan penyedia layanan berada dalam solusi yang sama.
Majelis berlangsung sebagai bagian dari solusi. Solusinya dikonfigurasi bahwa proyek klien harus dirakit setelah proyek layanan pemasok.

NSwag juga memungkinkan Anda untuk menyesuaikan spesifikasi / pembuatan kode secara imperatif melalui API perangkat lunak.

Bagaimana cara menambahkan dukungan untuk JWT


Kami perlu melindungi layanan kami dari permintaan yang tidak sah. Untuk ini, kami akan menggunakan token JWT. Mereka harus dikirim dalam tajuk dari setiap permintaan HTTP sehingga penyedia layanan dapat memeriksanya dan memutuskan apakah akan memenuhi permintaan atau tidak.

Informasi lebih lanjut tentang JWT sini jwt.io .

Tugas bermuara pada kebutuhan untuk memodifikasi header permintaan HTTP keluar.
Untuk melakukan ini, generator kode NSwag dapat menghasilkan titik ekstensi - metode CreateHttpRequestMessageAsync. Di dalam metode ini ada akses ke permintaan HTTP sebelum dikirim.

Contoh kode
protected Task<HttpRequestMessage> CreateHttpRequestMessageAsync(CancellationToken cancellationToken) {
      var message = new HttpRequestMessage();

      if (!string.IsNullOrWhiteSpace(this.AuthorizationToken)) {
        message.Headers.Authorization =
          new System.Net.Http.Headers.AuthenticationHeaderValue(BearerScheme, this.AuthorizationToken);
      }

      return Task.FromResult(message);
    }


Kesimpulan


Kami memilih opsi dengan OpenAPI, karena Mudah diimplementasikan, dan alat untuk bekerja dengan spesifikasi ini sangat berkembang.

Kesimpulan tentang OpenAPI dan GRPC:

OpenAPI

  • spesifikasinya adalah verbose;
  • , ;
  • ;
  • .

GRPC

  • , URL, HTTP ..;
  • OpenAPI;
  • ;
  • ;
  • HTTP/2.

Dengan demikian, kami menerima spesifikasi berdasarkan kode pengendali yang sudah tertulis. Untuk melakukan ini, perlu untuk menggantung atribut khusus pada pengontrol.

Kemudian, berdasarkan spesifikasi yang diterima, kami menerapkan pembuatan kode klien. Sekarang kita tidak perlu memperbarui kode klien secara manual.

Studi telah dilakukan di bidang pembuatan versi dan pengujian kontrak. Namun, itu tidak mungkin untuk menguji semuanya dalam praktik karena kurangnya sumber daya.

Kontrak Versi Publik


Mengapa mengubah kontrak publik?


Setelah perubahan penyedia layanan, seluruh sistem harus tetap dalam keadaan operasional yang konsisten.

Melanggar perubahan dalam API publik harus dihindari agar tidak merusak klien.

Opsi solusi


Tanpa versi kontrak publik


Tim penyedia layanan itu sendiri memperbaiki layanan konsumen.



Pendekatan ini tidak akan berfungsi jika tim penyedia layanan tidak memiliki akses ke repositori layanan konsumen atau tidak memiliki kompetensi. Jika tidak ada masalah seperti itu, maka Anda bisa melakukannya tanpa versi.



Gunakan versi kontrak publik


Tim penyedia layanan meninggalkan versi kontrak sebelumnya.



Pendekatan ini tidak memiliki kelemahan dari yang sebelumnya, tetapi menambah kesulitan lain.
Anda harus memutuskan yang berikut ini:

  • alat mana yang digunakan;
  • kapan harus memperkenalkan versi baru;
  • berapa lama mempertahankan versi lama.

Alat mana yang digunakan


Tabel tersebut menunjukkan fitur-fitur OpeanAPI dan GRPC yang terkait dengan versi.
gRPCOpenapi
Atribut versiPada tingkat protobuf, ada paket atribut [packageName]. [Versi]Pada tingkat spesifikasi, ada atribut basePath (untuk URL) dan Versi
Atribut usang untuk metodeYa, tetapi tidak diperhitungkan oleh pembuat kode di bawah C #Ya, ini ditandai sebagai Usang
. NSwag tidak didukung dengan kode terlebih dahulu, Anda harus menulis prosesor Anda
Atribut usang untuk parameterDitandai sebagai UsangYa, ini ditandai sebagai Usang
. NSwag tidak didukung dengan kode terlebih dahulu, Anda harus menulis prosesor Anda
Dihentikan berarti API ini tidak layak lagi digunakan.

Kedua alat mendukung versi dan atribut yang tidak digunakan lagi.

Jika Anda menggunakan OpenAPI dan pendekatan kode pertama, sekali lagi Anda perlu menulis prosesor untuk membuat spesifikasi yang tepat.

Kapan memperkenalkan versi baru


Versi baru harus diperkenalkan ketika perubahan kontrak tidak mempertahankan kompatibilitas ke belakang.

Bagaimana cara memverifikasi bahwa perubahan melanggar kompatibilitas antara versi kontrak baru dan lama?


Berapa lama untuk mempertahankan versi


Tidak ada jawaban yang tepat untuk pertanyaan ini.

Untuk menghapus dukungan untuk versi lama, Anda perlu tahu siapa yang menggunakan layanan Anda.

Ini akan menjadi buruk jika versi dihapus, dan orang lain menggunakannya. Terutama sulit jika Anda tidak mengendalikan pelanggan Anda.

Jadi apa yang bisa dilakukan dalam situasi ini?

  • beri tahu pelanggan bahwa versi lama tidak akan lagi didukung. Dalam hal ini, kami dapat kehilangan pendapatan pelanggan;
  • mendukung seluruh rangkaian versi. Biaya dukungan perangkat lunak meningkat;
  • untuk menjawab pertanyaan ini, Anda perlu menanyakan bisnis - apakah pendapatan dari pelanggan lama melebihi biaya untuk mendukung versi perangkat lunak yang lebih lama? Mungkinkah lebih menguntungkan untuk meminta pelanggan memperbarui?

Satu-satunya saran dalam situasi ini adalah lebih memperhatikan kontrak publik untuk mengurangi frekuensi perubahan mereka.

Jika kontrak publik digunakan dalam sistem tertutup, Anda dapat menggunakan pendekatan CDC. Jadi kita bisa mengetahui kapan pelanggan berhenti menggunakan versi perangkat lunak yang lebih lama. Setelah itu, Anda dapat menghapus dukungan dari versi lama.

Kesimpulan


Gunakan versi hanya jika Anda tidak bisa melakukannya tanpa itu. Jika Anda memutuskan untuk menggunakan versi, maka saat merancang kontrak, pertimbangkan kompatibilitas versi. Keseimbangan harus dicapai antara biaya mendukung versi yang lebih lama dan manfaat yang diberikannya. Anda juga perlu memutuskan kapan Anda bisa berhenti mendukung versi yang lama.

Kontrak Pengujian dan CDC


Bagian ini diterangi secara dangkal, seperti Tidak ada prasyarat serius untuk penerapan pendekatan ini.

Kontrak yang digerakkan konsumen (CDC)


CDC adalah jawaban untuk pertanyaan tentang bagaimana memastikan bahwa pemasok dan konsumen menggunakan kontrak yang sama. Ini adalah semacam tes integrasi yang bertujuan memeriksa kontrak.
Idenya adalah sebagai berikut:

  1. Konsumen menggambarkan kontrak.
  2. Pemasok mengimplementasikan kontrak ini di rumah.
  3. Kontrak ini digunakan dalam proses CI di konsumen dan pemasok. Jika prosesnya dilanggar, maka seseorang telah berhenti mematuhi kontrak.

Pakta


PACT adalah alat yang mengimplementasikan ide ini.

  1. Konsumen menulis tes menggunakan perpustakaan PACT.
  2. Tes ini dikonversi ke file pakta artefak. Ini berisi informasi tentang kontrak.
  3. Penyedia dan konsumen menggunakan file pakta untuk menjalankan tes.

Selama tes klien, rintisan penyedia dibuat, dan selama tes pemasok, rintisan klien dibuat. Kedua bertopik ini menggunakan file pakta.

Perilaku penciptaan stub sama dapat dicapai melalui menyombongkan Mock Validator bitbucket.org/atlassian/swagger-mock-validator/src/master .

Tautan yang bermanfaat tentang Pakta





Bagaimana CDC dapat tertanam dalam CI


  • menyebarkan Pakta + Pakta broker sendiri;
  • membeli solusi SaaS Pact Flow yang sudah jadi.

Kesimpulan


Pakta dibutuhkan untuk memastikan kepatuhan kontrak. Ini akan ditampilkan ketika perubahan kontrak melanggar harapan layanan konsumen.

Alat ini cocok ketika pemasok beradaptasi dengan pelanggan - pelanggan. Ini hanya mungkin di dalam sistem yang terisolasi.

Jika Anda membuat layanan untuk dunia luar dan tidak tahu siapa pelanggan Anda, maka Pact bukan untuk Anda.

All Articles