mengapa orang melakukan REST API, bukan DBAL?


32

Di dua perusahaan terakhir, saya pernah berada di REST API untuk meminta data melalui aplikasi web. yaitu. alih-alih meminta aplikasi web melakukan SQL secara langsung, ia memanggil REST API dan yang melakukan SQL dan mengembalikan hasilnya.

Pertanyaan saya adalah ... mengapa ini dilakukan?

Jika itu akan diekspos ke pihak ketiga saya bisa mengerti. Lebih baik membuka API REST terbatas daripada DB lengkap. Tetapi di kedua perusahaan ini bukan itu masalahnya.

Disarankan kepada saya bahwa REST API ini membuatnya lebih mudah untuk beralih antara DBMS. Tetapi bukankah itu titik dari lapisan abstraksi basis data (DBAL)? Mungkin Anda menggunakan ORM sebagai DBAL Anda atau mungkin Anda bisa menulis SQL mentah dan meminta DBAL Anda menerjemahkan hal-hal spesifik DB jika perlu (mis. Menerjemahkan LIMIT untuk MySQL ke TOP untuk MSSQL).

Either way sepertinya tidak perlu bagi saya. Dan saya pikir itu membuat masalah diagnosa lebih sulit juga. Jika laporan di aplikasi web memberikan angka yang salah, Anda tidak bisa hanya membuang kueri SQL - Anda harus membuang URL REST dan kemudian masuk ke proyek yang berfungsi sebagai REST API dan mengeluarkan SQL dari situ. Jadi ini merupakan lapisan tipuan tambahan yang memperlambat proses diagnostik.


3
Sepertinya Anda hanya bekerja dengan aplikasi yang pada dasarnya CRUD - beberapa pengguna memasukkan data dengan formulir dan pengguna lain membaca data dengan formulir yang sama atau dengan print out laporan? Jika Anda belum pernah bekerja dengan sistem yang membutuhkan model domain yang kompleks dan canggih, maka saya dapat melihat bagaimana Anda akan memiliki pola pikir tertentu. Banyak aplikasi membutuhkan lapisan tipuan ekstra untuk melakukan hal-hal.
RibaldEddie

1
Saya telah bekerja dengan API (tidak harus REST) ​​yang (antara lain) melakukan perhitungan pada parameter yang diteruskan ke sana. Mungkin DBMS digunakan dalam perhitungan tersebut tetapi mungkin banyak logika tidak hidup dalam DB. Namun, API internal di perusahaan tempat saya bekerja tidak melakukan ini. Mereka hanya meminta DBMS dan memuntahkan hasil dari query SQL kata demi kata. Sepertinya saya bahwa REST API sering (tidak selalu - sering) ditulis untuk menjadi trendi - tidak praktis.
neubert

1
Pasti ada kebiasaan desain REST API yang membuatnya sulit untuk mendesain domain yang rumit dengan baik - sebagian besar pengembang yang saya temui selama bertahun-tahun tidak peduli dengan desain. Mereka ingin membuat kode secepat mungkin sehingga bos mereka akan mencintai mereka dan berpikir bahwa mereka adalah bintang rock. Saat Anda menggabungkan fakta itu dengan tren seperti REST, Anda mendapatkan API spageti yang trendi tetapi tidak praktis. Itu tidak ada hubungannya dengan REST sendiri.
RibaldEddie

3
pernah bertanya-tanya bagaimana beberapa perusahaan web melaporkan bahwa seluruh catatan pengguna mereka dicuri oleh seorang hacker? Pernah bertanya-tanya bagaimana peretas melakukannya? Ketika Anda berpikir bahwa server web memiliki koneksi langsung ke DB, Anda menyadari begitu server web diretas, penyerang memiliki akses penuh dan tidak terbatas untuk memilih apa pun dari DB yang disukainya. Tempelkannya di belakang tingkat menengah dan penyerang hanya dapat memanggil metode pada tingkat menengah. Saya tidak akan mengatakan itu keamanan instan, tetapi secara signifikan lebih baik.
gbjbaanb

1
@ gbjbaanb: Maksud saya adalah server web dapat mengakses data melalui server sisanya, jadi jika server web diretas, penyerang juga dapat mengakses data melalui server lain tanpa harus meretas server sisanya.
JacquesB

Jawaban:


28

Jika Anda mengizinkan klien untuk mengakses database secara langsung - yang akan mereka lakukan, bahkan dengan lapisan abstraksi basis data, maka:

  • Anda mendapatkan sambungan antara kode mereka dan kode Anda - khususnya, ada sambungan yang sangat kuat antara struktur database Anda dan kode mereka;
  • Klien Anda dapat melakukan beberapa hal yang tidak diinginkan pada basis data Anda - apakah itu memperbarui data yang seharusnya tidak mereka buat, menulis kueri yang membutuhkan terlalu banyak waktu, menemui jalan buntu karena mereka tidak mendapatkan kunci dengan bersih ...
  • Jika Anda telah membuat beberapa pilihan yang kurang optimal dalam struktur database Anda, maka keluar dari pilihan itu mungkin sangat sulit, terutama jika Anda tidak memiliki cara yang baik untuk membuat klien Anda bermigrasi ke struktur baru.

Artinya, saya tidak menyentuh sama sekali pada bagian REST - mengisolasi basis data Anda di balik API adalah pilihan yang lebih masuk akal jika tim yang mengelola basis data dan tim yang menggunakannya tidak sinkron, karena memungkinkan bagian ini untuk berkembang dengan kecepatan mereka sendiri.


24

Anda benar, tidak ada manfaat yang jelas untuk memperkenalkan lapisan REST API antara aplikasi web dan database, dan ini memiliki biaya dalam kompleksitas dan overhead kinerja.

Alasan Anda mendapatkan jawaban yang bertentangan adalah kebingungan tentang apa 'klien' dalam arsitektur Anda.

Dalam arsitektur Anda (jika saya memahaminya benar), Anda memiliki browser yang berinteraksi dengan satu aplikasi web, yang pada gilirannya berinteraksi dengan database. Memperkenalkan lapisan REST API antara aplikasi web dan database tidak memiliki manfaat. Semua manfaat yang dinyatakan (caching, isolasi basis data dll) dapat dicapai dengan lapisan akses data dalam kode.

Tetapi ada beberapa arsitektur lain di mana API REST masuk akal:

  • Jika Anda memiliki banyak klien yang mengakses database - yaitu, bukan satu aplikasi web tetapi beberapa aplikasi web independen yang mengakses database yang sama. Mungkin memiliki manfaat untuk membuat antarmuka REST umum untuk memungkinkan berbagi model data, caching dll. Tentu Anda bisa mendapatkan beberapa manfaat dengan berbagi pustaka DAL yang sama, tetapi itu tidak akan berfungsi jika aplikasi dikembangkan dalam berbagai bahasa dan pada berbagai platform. Ini umum dalam sistem perusahaan.

  • Jika Anda memiliki beberapa aplikasi desktop yang mengakses database secara langsung. Ini adalah arsitektur "dua tingkat" klasik, yang tidak disukai dibandingkan dengan aplikasi web. Memperkenalkan lapisan REST memungkinkan Anda untuk memusatkan logika akses data dan terutama memungkinkan kontrol keamanan yang lebih ketat, karena berisiko memiliki banyak klien terdistribusi yang mengakses database yang sama secara langsung.

  • Jika Anda memiliki kode JavaScript yang secara langsung mengambil data dari server, maka Anda memerlukan sesuatu seperti REST API.


1
Saya menyukai jawaban Anda, tetapi saya memiliki beberapa pertanyaan lagi. Seperti bagaimana dengan kehilangan kinerja dengan pengenalan lapisan abstraksi lain? Juga, tidakkah menjadikannya satu titik kegagalan (jika ini turun, semua yang lain turun) dan kemungkinan hambatan (setiap aplikasi menunggu koneksi DB dari pool)?
sactiw

@satich: Saya tidak mengerti persis apa yang Anda tanyakan, dapatkah Anda lebih spesifik? Apakah Anda bertanya tentang satu-titik-kegagalan dengan atau tanpa tingkat REST?
JacquesB

Lapisan tambahan dapat digunakan jika Anda memiliki lebih dari satu aplikasi yang berkomunikasi dengannya
Ewan

@ Ewan: Ya, inilah yang saya nyatakan di poin pertama.
JacquesB

1
@ JacquesB Asumsikan beberapa aplikasi web berbagi DB yang sama tetapi tidak data yang sama yaitu masing-masing melakukan operasi CRUD pada set data terpisah dalam DB itu, pada dasarnya tidak ada pembagian data dalam arti sebenarnya. Jadi apakah menempatkan aplikasi di balik kerangka kerja Restful masuk akal (juga menganggap DB memberikan tingkat konkurensi yang baik dalam pertanyaan)? Selain itu, bukankah kerangka kerja itu akan menjadi penghambat serta satu titik kegagalan untuk begitu banyak aplikasi web berinteraksi melalui itu?
sactiw

12

Peringatan: pos besar, beberapa pendapat, tidak jelas kesimpulan 'lakukan yang terbaik untuk Anda'

Secara umum, ini dilakukan sebagai sarana untuk mengimplementasikan 'arsitektur heksagonal' di sekitar basis data Anda. Anda dapat memiliki aplikasi web, aplikasi seluler, aplikasi desktop, importir massal, dan pemrosesan latar belakang semua menggunakan database Anda dengan cara yang seragam. Tentu saja Anda dapat mencapai hal yang sama sampai batas tertentu dengan menulis perpustakaan yang kaya untuk mengakses database Anda, dan meminta semua proses Anda menggunakan perpustakaan itu. Dan memang, jika Anda berada di sebuah toko kecil dengan sistem yang sangat sederhana, itu sebenarnya mungkin rute yang lebih baik untuk ditempuh; Ini adalah pendekatan yang lebih sederhana dan jika Anda tidak membutuhkan kemampuan canggih dari sistem yang lebih rumit, mengapa harus membayar kompleksitasnya? Namun, jika Anda bekerja dengan satu set besar, sistem canggih yang semuanya perlu berinteraksi dengan basis data Anda dalam skala besar, ada

Kemandirian & pemeliharaan platform

Jika Anda memiliki database, dan Anda menulis perpustakaan Python untuk berinteraksi dengan database itu, dan semua orang menarik di perpustakaan itu untuk berinteraksi dengan database, itu hebat. Tapi katakanlah tiba-tiba Anda perlu menulis aplikasi seluler, dan aplikasi seluler itu sekarang perlu berbicara dengan basis data. Dan insinyur iOS Anda tidak menggunakan Python, dan insinyur Android Anda tidak menggunakan Python. Mungkin orang-orang iOS ingin menggunakan bahasa Apple dan para insinyur Android ingin menggunakan Java. Maka Anda akan terjebak menulis dan memelihara perpustakaan akses data Anda dalam 3 bahasa yang berbeda. Mungkin iOS dan Android devs memutuskan untuk menggunakan sesuatu seperti Xamarin untuk memaksimalkan kode yang dapat mereka bagikan. Sempurna, kecuali Anda mungkin masih harus mem-port pustaka akses data Anda ke .NET. Dan kemudian perusahaan Anda baru saja membeli perusahaan lain yang Aplikasi web adalah produk yang berbeda tetapi terkait, dan bisnis ingin mengintegrasikan beberapa data dari platform perusahaan Anda ke dalam platform anak perusahaan yang baru diakuisisi. Hanya ada satu masalah: Anak perusahaan adalah perusahaan baru dan memutuskan untuk menulis sebagian besar aplikasi mereka di Dart. Plus, untuk alasan apa pun (alasan mungkin di luar kendali Anda) tim seluler yang mengemudikan Xamarin memutuskan itu bukan untuk mereka, dan bahwa mereka lebih suka menggunakan alat dan bahasa khusus untuk perangkat seluler yang akan mereka kembangkan. Tetapi ketika Anda berada dalam fase itu, tim Anda sudah mengirimkan sebagian besar pustaka akses data Anda di .NET, dan tim lain di perusahaan itu sedang menulis beberapa hal integrasi Salesforce yang gila dan memutuskan untuk melakukan semua itu di .NET karena ada sudah menjadi perpustakaan akses data untuk.

Jadi sekarang, karena pergantian peristiwa yang sangat realistis, Anda memiliki pustaka akses data Anda yang ditulis dalam Python, .NET, Swift, Java, dan Dart. Mereka juga tidak sebaik yang Anda inginkan. Anda tidak dapat menggunakan ORM seefektif yang Anda inginkan, karena setiap bahasa memiliki alat ORM yang berbeda, jadi Anda harus menulis lebih banyak kode daripada yang Anda inginkan. Dan Anda belum dapat mencurahkan banyak waktu untuk setiap inkarnasi seperti yang Anda inginkan, karena ada 5 dari mereka. Dan versi Dart perpustakaan sangat berbulu karena Anda harus menggulung sendiri barang-barang transaksional Anda untuk sebagian karena perpustakaan dan dukungan tidak benar-benar ada. Anda mencoba menjelaskan bahwa karena hal ini, aplikasi Dart seharusnya hanya memiliki fungsi baca-saja untuk basis data Anda, tetapi bisnis itu telah memutuskan bahwa fitur apa pun yang mereka rencanakan pantas dilakukan. Dan ternyata ada bug dalam beberapa logika validasi yang ada di semua inkarnasi pustaka akses data Anda ini. Sekarang Anda harus menulis tes dan kode untuk memperbaiki bug ini di semua pustaka ini, dapatkan ulasan kode untuk perubahan Anda ke semua pustaka ini, dapatkan QA pada semua pustaka ini, dan lepaskan perubahan Anda ke semua sistem menggunakan semua perpustakaan ini. Sementara itu, pelanggan Anda tidak senang dan telah dibawa ke Twitter, merangkai kombinasi vulgar yang tidak pernah Anda bayangkan bisa dipahami, apalagi ditargetkan pada produk andalan perusahaan Anda. Dan pemilik produk memutuskan untuk tidak memahami situasi sama sekali.

Harap dipahami bahwa di beberapa lingkungan, contoh di atas tidak ada yang dibuat. Juga pertimbangkan bahwa urutan peristiwa ini dapat berlangsung selama beberapa tahun. Secara umum, ketika Anda sampai pada titik di mana arsitek dan pebisnis mulai berbicara tentang menghubungkan sistem lain ke database Anda, saat itulah Anda akan ingin mendapatkan 'menempatkan REST API di depan database' ke peta jalan Anda. Pertimbangkan jika sejak awal, ketika sudah jelas bahwa database ini akan mulai dibagikan oleh beberapa sistem, bahwa layanan web / REST API diletakkan di depannya. Memperbaiki bug validasi Anda akan jauh lebih cepat dan mudah karena Anda melakukannya sekali, bukan 5 kali. Dan melepaskan perbaikannya akan jauh lebih mudah untuk dikoordinasikan, karena Anda

TLDR; Lebih mudah untuk memusatkan logika akses data dan mempertahankan klien HTTP yang sangat tipis daripada mendistribusikan logika akses data ke setiap aplikasi yang perlu mengakses data. Bahkan, klien HTTP Anda bahkan dapat dihasilkan dari meta-data. Dalam sistem besar, REST API memungkinkan Anda mempertahankan lebih sedikit kode

Performa dan skalabilitas

Beberapa orang mungkin percaya bahwa berbicara dengan basis data secara langsung alih-alih melalui layanan web lebih cepat. Jika Anda hanya memiliki satu aplikasi, itu tentu benar. Tetapi dalam sistem yang lebih besar, saya tidak setuju dengan sentimen. Pada akhirnya, pada tingkat tertentu, akan sangat bermanfaat jika menaruh semacam cache di depan basis data. Mungkin Anda menggunakan Hibernate, dan ingin menginstal kisi Infinispan sebagai cache L2. Jika Anda memiliki sekelompok 4 server gemuk untuk meng-host layanan web Anda terpisah dari aplikasi Anda, Anda dapat memiliki topologi tertanam dengan replikasi sinkron dihidupkan. Jika Anda mencoba meletakkannya di 30 server aplikasi, overhead untuk mengaktifkan replikasi dalam pengaturan itu akan terlalu banyak, jadi Anda harus Anda harus menjalankan Infinispan dalam mode terdistribusi atau dalam beberapa jenis topologi khusus, dan tiba-tiba Hibernate harus keluar melalui jaringan untuk membaca dari cache. Plus, Infinispan hanya berfungsi di Jawa. Jika Anda memiliki bahasa lain, Anda membutuhkan solusi caching lainnya. Overhead jaringan karena harus pergi dari aplikasi Anda ke layanan web Anda sebelum mencapai database dengan cepat diimbangi dengan kebutuhan untuk menggunakan solusi caching yang jauh lebih rumit yang umumnya datang dengan overhead mereka sendiri.

Selain itu, lapisan HTTP API REST Anda menyediakan mekanisme caching lain yang berharga. Server Anda untuk REST API Anda dapat menempatkan header caching pada respons mereka, dan respons ini dapat di-cache di lapisan jaringan, yang skalanya sangat baik. Dalam pengaturan kecil, dengan satu atau dua server, taruhan terbaik Anda adalah dengan hanya menggunakan cache memori dalam aplikasi ketika berbicara ke database, tetapi dalam platform besar dengan banyak aplikasi yang berjalan di banyak server, Anda ingin memanfaatkan jaringan untuk menangani caching Anda, karena ketika dikonfigurasi dengan benar sesuatu seperti squid atau pernis atau nginx dapat keluar ke tingkat gila pada perangkat keras yang relatif kecil. Ratusan ribu atau jutaan permintaan per detik dari throughput jauh lebih murah untuk dilakukan dari cache HTTP daripada dari server aplikasi atau database.

Selain itu, memiliki banyak klien yang menunjuk ke basis data Anda, alih-alih mengarahkan semua ke beberapa server yang pada gilirannya menunjuk ke basis data, dapat membuat penyetelan basis data dan koneksi menyatukan lebih sulit. Secara umum, sebagian besar beban kerja aktual pada server aplikasi adalah hal-hal aplikasi; menunggu data untuk kembali dari database seringkali memakan waktu, tetapi umumnya tidak terlalu mahal secara komputasi. Anda mungkin memerlukan 40 server untuk menangani beban kerja aplikasi Anda, tetapi Anda mungkin tidak perlu 40 server untuk mengatur pengambilan data dari database. Jika Anda mendedikasikan tugas itu untuk layanan web, layanan web mungkin akan berjalan pada server yang jauh lebih sedikit daripada aplikasi lainnya, yang berarti Anda akan memerlukan koneksi jauh lebih sedikit ke database. Yang penting, karena basis data umumnya tidak

TLDR; Lebih mudah untuk mengatur, mengukur, dan menyimpan akses data Anda ketika itu adalah sesuatu yang terjadi di dalam satu layanan web khusus daripada ketika itu adalah sesuatu yang terjadi di banyak aplikasi yang berbeda menggunakan berbagai bahasa dan teknologi

Pikiran terakhir

Tolong jangan menyimpang dari pemikiran ini "Oh wow, saya harus selalu menggunakan REST API untuk mendapatkan data saya" atau "Idiot ini mencoba mengatakan kami melakukan kesalahan karena aplikasi web kami berbicara langsung ke database, tetapi barang-barang kami bekerja dengan baik! " . Poin utama yang saya coba sampaikan adalah bahwa sistem dan bisnis yang berbeda memiliki persyaratan yang berbeda; Dalam banyak kasus, menempatkan REST API di depan basis data Anda benar-benar tidak masuk akal. Ini adalah arsitektur yang lebih rumit yang membutuhkan pembenaran kompleksitas itu. Tetapi ketika kompleksitas dibenarkan, ada banyak manfaat untuk memiliki REST API. Mampu menimbang masalah yang berbeda dan memilih pendekatan yang tepat untuk sistem Anda adalah apa yang membuat seorang insinyur yang baik.

Selain itu, jika REST API menghalangi debugging sesuatu, kemungkinan ada sesuatu yang salah atau hilang dalam gambar itu. Saya tidak percaya memiliki lapisan abstraksi yang ditambahkan secara intrinsik membuat debugging lebih sulit. Ketika saya bekerja dengan sistem n-tier yang besar, saya ingin memastikan bahwa saya memiliki konteks logging terdistribusi. Mungkin ketika pengguna memulai permintaan, buat GUID untuk permintaan itu dan catat nama pengguna pengguna itu dan permintaan yang mereka buat. Kemudian, operasikan GUID itu saat aplikasi Anda berbicara ke sistem lain. Dengan agregasi dan pengindeksan log yang tepat, Anda dapat meminta seluruh platform Anda untuk pengguna melaporkan masalah, dan memiliki visibilitas ke semua tindakan mereka dan mereka menetes melalui sistem untuk dengan cepat mengidentifikasi di mana terjadi kesalahan. Sekali lagi, ini adalah arsitektur yang lebih rumit,

Sumber: http://alistair.cockburn.us/Hexagonal+architecture https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing


Jawaban yang sangat bagus, layak dibaca. Terima kasih telah meluangkan waktu untuk menulis balasan yang hebat ini!
Dominic

6

Jika saya mengerti benar apa itu DBAL , maka jawabannya adalah bahwa antarmuka REST memungkinkan Anda untuk menggunakan bahasa apa pun untuk kliennya, sedangkan DBAL adalah perpustakaan yang memungkinkan Anda untuk menggunakan satu bahasa untuk kliennya.

Ini, pada gilirannya, dapat menjadi keuntungan bagi perusahaan di mana ada banyak tim pengembangan dan tidak semuanya mahir dalam bahasa yang sama. Mengizinkan perangkat lunak mereka melakukan kueri langsung, DB akan memiliki fungsionalitas yang setara, tetapi seperti yang Anda katakan "lebih baik membuka API REST terbatas daripada DB lengkap".

Dalam istilah yang lebih abstrak, Anda sendiri yang menjawab pertanyaan:

Jadi ini merupakan lapisan tipuan tambahan yang memperlambat proses diagnostik

... karena ada pepatah terkenal yang menyatakan: "Semua masalah dalam ilmu komputer dapat diselesaikan dengan tingkat tipuan lain". :)


6

Hanya karena Anda berada di dalam perusahaan yang sama tidak berarti Anda harus mengungkapkan semuanya kepada semua orang. REST API adalah cara untuk mendefinisikan hubungan konsumen / penyedia terbatas antara tim dalam perusahaan, dengan kontrak yang jelas. Amazon telah menjadi pelopor dalam bentuk organisasi ini.

API juga menyediakan lapisan abstraksi, memungkinkan Anda untuk menggunakan serangkaian idiom tertentu - Anda tidak perlu ingin berbicara dengan konsumen Anda dengan istilah yang sama yang digunakan dalam database Anda. Anda juga tidak perlu ingin berbicara dengan setiap konsumen dengan cara yang sama.


3

Anda berpikir bahwa REST adalah untuk permintaan basis data dan bukan. REST mewakili keadaan sesuatu saat ini. Menggunakan REST mengubah atau mengambil representasi tetapi hanya itu. Jika keadaan itu tersedia oleh basis data, itu tidak masalah dan tidak ada yang peduli karena BAGAIMANA representasi itu bukan bagian dari REST dan begitu pula kueri basis data.


Saya tidak menyarankan bahwa permintaan basis data == REST. Tentu saja REST mampu menjadi lebih dari sekadar lapisan abstraksi basis data, tetapi pada dua perusahaan yang lalu saya telah bekerja untuk itu pada dasarnya adalah semua itu - lapisan abstraksi basis data. Ia tidak melakukan apa-apa lain selain menerjemahkan HTTP permintaan untuk query DB. Dan jika hanya itu yang Anda lakukan, bagi saya tampaknya Anda akan lebih baik dilayani oleh DBAL. Memang, menurut saya satu-satunya alasan beberapa orang menggunakan REST hari ini adalah karena itu trendi - bukan karena itu solusi terbaik di luar sana untuk tugas yang ada.
neubert

@neubert apakah DBAL berfungsi langsung melalui internet seperti REST?
Rob

Yakin. Anda dapat memberi tahu MySQL untuk menggunakan alamat IP / nama domain / port yang dimiliki komputer lain di internet. Anda dapat menggunakan tunneling SSH serta (saya percaya) SSL auth juga. Agaknya pekerjaan DBMS lainnya juga sama.
neubert

@neubert: dalam hal itu, REST API adalah DBAL, bukan?
RemcoGerlich

2
@RemcoGerlich - tentu saja, tetapi menggunakan REST API sebagai DBAL Anda mungkin menambahkan lapisan tengah yang tidak perlu dan menghambat diagnosis masalah. Maksud saya, jika Anda akan menggunakan definisi DBAL yang cukup luas maka Anda dapat mempertimbangkan Google SERP sebagai DBAL. Anda hanya perlu mem-parsing HTML untuk mendapatkan data paginasi dari server Google ...
neubert
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.