Berapa banyak logika bisnis yang harus diterapkan oleh database?


107

Saya telah bekerja di beberapa proyek di mana sebagian besar logika bisnis diterapkan pada database (kebanyakan melalui prosedur tersimpan). Di sisi lain, saya pernah mendengar dari beberapa rekan programmer bahwa ini adalah praktik yang buruk ("Database ada untuk menyimpan data. Aplikasi ada di sana untuk melakukan sisanya").

Manakah dari pendekatan ini yang umumnya lebih baik?

Kelebihan menerapkan logika bisnis dalam DB yang dapat saya pikirkan adalah:

  • Sentralisasi logika bisnis;
  • Independensi tipe aplikasi, bahasa pemrograman, OS, dll;
  • Database kurang rentan terhadap migrasi teknologi atau refactoring besar (AFAIK);
  • Tidak ada pengerjaan ulang migrasi teknologi aplikasi (mis: .NET ke Java, Perl ke Python, dll).

Kontra:

  • SQL kurang produktif dan lebih kompleks untuk pemrograman logika bisnis, karena kurangnya perpustakaan dan konstruksi bahasa yang ditawarkan oleh kebanyakan bahasa yang berorientasi aplikasi;
  • Kode yang lebih sulit (jika mungkin sama sekali) digunakan kembali melalui perpustakaan;
  • IDE kurang produktif.

Catatan: Basis data yang saya bicarakan adalah basis data relasional, basis data populer seperti SQL Server, Oracle, MySql dll.

Terima kasih!


3
Anda mungkin menemukan jawaban untuk pertanyaan ini bermanfaat.
Blrfl

7
Argumen ini sudah diperdebatkan secara mendalam . Apa lagi yang bisa kita tambahkan ke percakapan di sini?
Robert Harvey

2
@gnat: Bahkan tidak dekat.
Robert Harvey


7
Pertimbangkan bahwa basis data akan jauh ( jauh ) lebih lama dari aplikasi Anda. Basis data bahkan mungkin lebih lama dari bahasa tempat Anda menulis aplikasi. Data itu sendiri biasanya adalah bisnis, dan basis data harus dapat melindungi integritas data yang dikandungnya. Sejalan dengan itu, setiap kendala kunci asing, sejujurnya, adalah penerapan aturan bisnis. Kecuali Anda menyingkirkan semua kendala relasional dalam database relasional Anda, Anda benar - benar tidak bisa mendapatkan logika bisnis sepenuhnya dari database.
Craig

Jawaban:


82

Logika bisnis tidak masuk ke dalam basis data

Jika kita berbicara tentang aplikasi multi-tier, tampaknya cukup jelas bahwa logika bisnis, jenis kecerdasan yang menjalankan perusahaan tertentu, termasuk dalam Lapisan Logika Bisnis, bukan dalam Lapisan Akses Data.

Database melakukan beberapa hal dengan sangat baik:

  1. Mereka menyimpan dan mengambil data
  2. Mereka membangun dan menegakkan hubungan antara entitas data yang berbeda
  3. Mereka menyediakan sarana untuk menanyakan data untuk jawaban
  4. Mereka memberikan optimasi kinerja.
  5. Mereka menyediakan kontrol akses

Sekarang, tentu saja, Anda dapat menyusun semua hal dalam basis data yang berkaitan dengan masalah bisnis Anda, hal-hal seperti tarif pajak, diskon, kode operasi, kategori, dan sebagainya. Tetapi tindakan bisnis yang diambil pada data tersebut umumnya tidak dikodekan ke dalam basis data, karena segala macam alasan yang telah disebutkan oleh orang lain, meskipun suatu tindakan dapat dipilih dalam basis data dan dieksekusi di tempat lain.

Dan tentu saja, mungkin ada hal-hal yang dilakukan dalam database untuk kinerja dan alasan lain:

  1. Menutup periode akuntansi
  2. Angka berderak
  3. Proses batch setiap malam
  4. Gagal

Secara alami, tidak ada yang terukir di batu. Stored Prosedur cocok untuk beragam tugas hanya karena mereka tinggal di server database dan memiliki kekuatan dan kelebihan tertentu.

Prosedur Tersimpan Di Mana Saja

Ada daya pikat tertentu untuk mengode semua tugas penyimpanan, pengelolaan, dan pengambilan data dalam prosedur tersimpan, dan hanya mengonsumsi layanan data yang dihasilkan. Anda tentu akan mendapat manfaat dari optimalisasi kinerja dan keamanan semaksimal mungkin yang disediakan oleh server database, dan itu bukan hal kecil.

Tapi apa risikonya?

  1. Penguncian vendor
  2. Kebutuhan akan pengembang dengan keahlian khusus
  3. Alat pemrograman sederhana, secara keseluruhan
  4. Kopling perangkat lunak yang sangat ketat
  5. Tidak ada pemisahan kekhawatiran

Dan tentu saja, jika Anda memerlukan layanan web (yang mungkin merupakan tujuan semua ini), Anda masih harus membangunnya.

Jadi apa praktik yang khas?

Saya akan mengatakan bahwa pendekatan modern yang khas adalah menggunakan Object-Relational Mapper (seperti Entity Framework) untuk membuat kelas yang memodelkan tabel Anda. Anda kemudian dapat berbicara ke database Anda melalui repositori yang mengembalikan koleksi objek, situasi yang sangat akrab bagi pengembang perangkat lunak yang kompeten. ORM secara dinamis menghasilkan SQL yang sesuai dengan model data Anda dan informasi yang diminta, yang kemudian diproses oleh server database untuk mengembalikan hasil kueri.

Seberapa baik ini bekerja? Sangat bagus, dan jauh lebih cepat daripada menulis prosedur dan pandangan yang tersimpan. Ini biasanya mencakup sekitar 80% dari persyaratan akses data Anda, kebanyakan CRUD. Apa yang mencakup 20% lainnya? Anda dapat menebaknya: prosedur tersimpan, yang didukung langsung oleh semua ORM utama.

Bisakah Anda menulis pembuat kode yang melakukan hal yang sama dengan ORM, tetapi dengan prosedur tersimpan? Tentu kamu bisa. Tapi ORM umumnya vendor-independen, dipahami dengan baik oleh semua orang, dan lebih didukung.


3
Terima kasih atas jawaban Anda yang luar biasa, @Robert Harvey. Tetapi saya berpikir tentang argumen "vendor lock-in": tidak menggunakan teknologi tertentu (katakanlah, .NET atau Java stack) untuk membangun aplikasi juga vendor lock-in? Atau apakah ada keuntungan dari penguncian vendor stack yang berorientasi aplikasi versus DB?
Raphael

3
@ RobertTarvey, Tetapi bagian dari logika aplikasi yang ada di .NET masih terkunci di .NET. Hal yang sama berlaku untuk PHP dan Java.
Pacerier

2
@Pacerier: Menurut vendor-lockin, saya merujuk pada vendor database. Dalam praktik sebenarnya, database (dan tumpukan pemrograman) jarang diganti.
Robert Harvey

2
@ Kai: Ya, Anda tidak bisa mendapatkan keduanya. Entah Anda menggunakan bertopik dan mengolok-olok dan hidup dengan kenyataan bahwa tes itu buatan, atau Anda menulis tes yang realistis, dan hidup dengan sedikit penundaan. Saya ragu bahwa tradeoff Anda adalah 10 menit vs 30 detik.
Robert Harvey

3
Mungkin terlambat tetapi saya berpendapat bahwa prosedur tersimpan yang menerapkan logika bisnis adalah milik lapisan logika bisnis, bukan lapisan data. Mereka adalah jenis bahasa yang terpisah tanpa memerlukan ORM.
Paralife

16

Saya sangat percaya dalam menjaga logika bisnis dari database sebanyak mungkin. Namun, sebagai pengembang kinerja perusahaan saya, saya menghargai bahwa kadang-kadang perlu untuk mencapai kinerja yang baik. Tapi saya pikir itu perlu jauh lebih jarang daripada yang diklaim orang.

Saya membantah pro dan kontra Anda.

Anda mengklaim bahwa itu memusatkan logika bisnis Anda. Sebaliknya, saya pikir itu desentralisasi. Dalam produk yang saat ini saya kerjakan, kami menggunakan prosedur tersimpan untuk banyak logika bisnis kami. Banyak masalah kinerja kami datang dari fungsi panggilan berulang kali. Misalnya

select <whatever>
from group g
where fn_invoker_has_access_to_group(g.group_id)

Masalah dengan pendekatan ini adalah bahwa umumnya (mungkin ada kasus di mana ini salah) memaksa database untuk menjalankan fungsi Anda N kali, sekali per baris. Terkadang fungsi itu mahal. Beberapa basis data mendukung indeks fungsi. Tetapi Anda tidak dapat mengindeks setiap fungsi yang mungkin terhadap setiap input yang mungkin. Atau bisakah kamu?

Solusi umum untuk masalah di atas adalah mengekstrak logika dari fungsi dan menggabungkannya ke dalam kueri. Sekarang Anda telah merusak enkapsulasi dan duplikasi logika.

Masalah lain yang saya lihat adalah memanggil prosedur tersimpan dalam satu lingkaran karena tidak ada cara untuk bergabung atau memotong set hasil proc yang disimpan.

declare some_cursor
while some_cursor has rows
    exec some_other_proc
end

Jika Anda menarik kode dari proc bersarang, maka Anda kembali mendesentralisasi. Karena itu, Anda dipaksa untuk memilih antara enkapsulasi dan kinerja.

Secara umum, saya menemukan bahwa database buruk di:

  1. Komputasi
  2. Iterasi (mereka dioptimalkan untuk operasi yang ditetapkan)
  3. Penyeimbang beban
  4. Parsing

Database bagus dalam:

  1. Mengunci dan membuka kunci
  2. Memelihara data dan hubungannya
  3. Memastikan integritas

Dengan mengambil operasi yang mahal seperti loop dan penguraian string dan menyimpannya di tingkat aplikasi Anda, Anda dapat secara horizontal mengukur aplikasi Anda untuk mendapatkan kinerja yang lebih baik. Menambahkan beberapa server aplikasi di belakang load balancer biasanya jauh lebih murah daripada mengatur replikasi database.

Anda benar, bagaimanapun, bahwa itu memisahkan logika bisnis Anda dari bahasa pemrograman aplikasi Anda, tetapi saya tidak melihat mengapa itu merupakan keuntungan. Jika Anda memiliki aplikasi Java, maka Anda memiliki aplikasi Java. Mengubah sekelompok kode Java ke dalam prosedur tersimpan tidak mengubah fakta bahwa Anda memiliki aplikasi Java.

Preferensi saya adalah menjaga kode basis data tetap fokus pada kegigihan. Bagaimana Anda membuat widget baru? Anda harus memasukkan ke dalam 3 tabel dan mereka harus dalam transaksi. Itu termasuk dalam prosedur tersimpan.

Menentukan apa yang dapat dilakukan untuk widget dan aturan bisnis untuk menemukan widget termasuk dalam aplikasi Anda.


8
Dalam SQL server, hanya sps yang ditulis dengan buruk perlu dipanggil dalam satu lingkaran, Anda dapat mengirimkannya set data dalam parameter dan melakukan proses berbasis set.
HLGEM

2
SQL Server akan menghasilkan rencana permintaan sub-optimal setiap kali ada UDF dalam klausa WHERE.
Jim G.

7
Sepertinya masalah kinerja Anda bukan kesalahan logika dalam database vs aplikasi .. itu hanya ditulis dengan buruk dan dirancang. Masalah itu akan mengikuti Anda di dunia ORM sama saja. ORM dapat menjadi sakit kepala nyata di luar operasi CRUD. Jika sistem Anda berat data, tipe pelaporan sistem, harap berhati-hati.
sam yi

Itu benar. Sebagian besar masalah kinerja kami hanya disebabkan oleh kode yang ditulis dengan buruk dan arsitektur yang terlalu rumit. Tetapi saya masih percaya bahwa kita memasukkan jenis pekerjaan yang salah ke dalam basis data kita. Pengkodean sebanyak mungkin ke dalam basis data telah menyebabkan kami melakukan hal-hal yang tidak bisa dilakukan oleh basis data.
Brandon

1
Contoh ini bahkan argumen untuk menempatkan bagian-bagian inti dari logika biz dalam DB: untuk menghindari pendekatan berulang (kode atau loop kursor bukan ekspresi berbasis set) seperti wabah. Pemrogram cenderung memperlakukan set objek dengan cara berulang (loop, traverse), kemungkinan mengarah ke beban yang tidak perlu atau masalah SELECT N +1 pada banyak perjalanan bolak-balik kueri tunggal. Dengan menggunakan SQL atau ekspresi berbasis bahasa (misalnya LINQ), mereka akan dipaksa untuk menggunakan pendekatan berbasis set, jika memungkinkan.
Erik Hart

10

Saya telah bekerja di 2 perusahaan berbeda yang memiliki visi berbeda tentang masalah ini.

Saran pribadi saya adalah menggunakan Prosedur Tersimpan ketika waktu eksekusi penting (kinerja). Karena Prosedur Tersimpan dikompilasi, jika Anda memiliki logika kompleks untuk menanyakan data, lebih baik menyimpannya di database itu sendiri. Juga, itu hanya akan mengirim data akhir ke program Anda di akhir.

Kalau tidak, saya pikir logika suatu program harus selalu dalam perangkat lunak itu sendiri. Mengapa? Karena sebuah program perlu diuji dan saya tidak berpikir ada cara mudah untuk menguji prosedur yang tersimpan. Jangan lupa, program yang tidak diuji adalah program yang buruk.

Jadi gunakan Prosedur Tersimpan dengan hati-hati, saat dibutuhkan.


3
Prosedur tersimpan adalah unit yang dapat diuji. Lihat di sini untuk beberapa teknik.
Robert Harvey

4
afaik, unit test tidak pernah menggunakan database atau file. Jadi secara teknis, "unit testing" prosedur yang tersimpan bukanlah pengujian unit dan itu akan lambat sekali. Unit uji unit harus dijalankan dalam hitungan detik (atau mungkin beberapa menit dengan aplikasi yang sangat besar) kapan saja selama pengembangan.
Jean-François Côté

1
OP berbicara tentang "logika bisnis" dan logika bisnis harus diuji unit. Dengan meletakkannya di prosedur tersimpan, Anda mencampurnya dengan permintaan basis data yang memperlambat seluruh proses. Seperti yang saya katakan, Anda dapat menggunakan Prosedur Tersimpan (ini bukan kejahatan) tetapi itu akan mengaburkan garis antara logika bisnis dan lapisan basis data yang buruk. Gunakan dengan hati-hati :)
Jean-François Côté

1
Jika Anda membuat db dan benda-benda yang diperlukan, sp, tes, dan kemudian merobohkannya setelah itu, itu adalah unit test. Ini menguji unit kerja.
Tony Hopkinson

2
Bukankah keuntungan kinerja dengan mitos prosedur tersimpan telah dibongkar?
JeffO

9

Ada jalan tengah yang perlu Anda temukan. Saya telah melihat proyek-proyek yang menakutkan di mana programmer menggunakan database sebagai tidak lebih dari toko kunci / nilai yang mahal. Saya telah melihat orang lain di mana programmer gagal menggunakan kunci & indeks asing. Di ujung lain dari spektrum, saya telah melihat proyek-proyek di mana sebagian besar jika tidak semua logika bisnis diimplementasikan dalam kode database.

Seperti yang telah Anda catat, T-SQL (atau yang sederajat dalam RDBMS populer lainnya) bukan tempat terbaik untuk menjadi pengkodean logika bisnis yang kompleks.

Saya mencoba untuk membangun model data yang cukup layak, menggunakan fitur-fitur basis data untuk melindungi asumsi saya tentang model itu (yaitu, FK dan kendala), dan menggunakan kode basis data dengan hemat. Kode database berguna ketika Anda membutuhkan sesuatu (yaitu jumlah) yang sangat baik dilakukan oleh database dan dapat membantu Anda memindahkan jutaan catatan di atas kabel saat Anda tidak membutuhkannya.


2
Menggunakan basis data sebagai penyimpanan kunci / nilai "terlalu mahal" adalah teknik yang benar-benar valid, karena banyak praktisi NoSQL akan membuktikannya.
Robert Harvey

1
@RobertHarvey Anda jelas benar, tetapi entah bagaimana usus saya terus bersikeras bahwa harus ada solusi yang lebih sederhana / lebih murah / lebih cepat daripada database jika semua yang Anda butuhkan adalah toko kunci / nilai. Saya perlu belajar lebih banyak tentang NoSQL.
Dan Pichelman

2
Saya tidak melihat menggunakan prosedur tersimpan sebagai obat untuk database yang dirancang dengan buruk.
JeffO

2
@RobertHarvey, saya membaca "toko kunci / nilai yang terlalu mahal" secara harfiah. Memakai lisensi Oracle atau SQL Server untuk sesuatu seperti itu, ketika ada opsi seperti MongoDB tersedia secara gratis, sepertinya membuang-buang uang.
Raphael

@Raphael Atau Anda bisa menggunakan PostgreSQL 😉
Demi

9

Jika logika bisnis Anda melibatkan operasi yang ditetapkan, kemungkinan besar tempat yang baik untuk itu adalah dalam database karena sistem database sangat baik dalam melakukan operasi yang ditetapkan.

http://en.wikipedia.org/wiki/Set_operations_(SQL)

Jika logika bisnis melibatkan semacam perhitungan, itu mungkin termasuk di luar prosedur basis data / toko karena basis data tidak benar-benar dirancang untuk pengulangan dan penghitungan.

Meskipun ini bukan aturan yang keras dan cepat, ini adalah titik awal yang baik.


6

Tidak ada jawaban yang tepat untuk ini. Tergantung pada apa Anda menggunakan database. Dalam aplikasi perusahaan, Anda memerlukan logika dalam database melalui kunci asing, batasan, pemicu, dll. Karena itu adalah satu-satunya tempat di mana semua aplikasi mungkin berbagi kode. Selanjutnya, memasukkan logika yang diperlukan dalam kode umumnya berarti database tidak konsisten dan data berkualitas buruk. Itu mungkin tampak sepele bagi pengembang aplikasi yang hanya memahami cara kerja GUI, tetapi saya jamin Anda bahwa orang yang mencoba menggunakan data dalam laporan kepatuhan merasa sangat menjengkelkan dan mahal ketika mereka mendapatkan denda miliaran dolar karena memiliki data yang tidak ikuti aturan dengan benar.

Dalam lingkungan non-regulasi saat Anda tidak terlalu peduli dengan seluruh rangkaian catatan dan hanya satu atau dua aplikasi yang menyentuh database, mungkin Anda bisa lolos dengan menyimpannya dalam aplikasi.


3

Setelah beberapa tahun, pertanyaannya tetap penting ...

Aturan praktis sederhana untuk saya: jika itu adalah kendala logis atau ekspresi di mana-mana (pernyataan tunggal), letakkan di database (ya, kunci asing dan periksa kendala juga logika bisnis!). Jika itu prosedural, dengan mengandung loop dan cabang kondisional (dan benar-benar tidak dapat diubah menjadi ekspresi), masukkan ke dalam kode.

Hindari DB dump sampah

Upaya untuk benar-benar menempatkan semua logika bisnis dalam kode aplikasi kemungkinan akan mengubah basis data (relasional) menjadi tempat sampah, di mana desain relasional sebagian besar dihilangkan sepenuhnya, di mana data dapat memiliki keadaan tidak konsisten, dan normalisasi hilang (biasanya XML, JSON) , CSV dll. Kolom trashbin).

Logika khusus-aplikasi semacam ini mungkin merupakan salah satu alasan utama munculnya NoSQL - tentu saja dengan kelemahannya bahwa aplikasi harus mengurus semua logika itu sendiri, apa yang telah dibangun menjadi DB relasional selama beberapa dekade. Namun, database NoSQL lebih cocok untuk jenis penanganan data ini, misalnya, dokumen data mempertahankan "integritas relasional" tersirat dalam diri mereka sendiri. Untuk DB relasional, itu hanya penyalahgunaan, yang menyebabkan lebih banyak masalah.

Ekspresi (set-based) alih-alih kode prosedural

Dalam kasus terbaik, setiap kueri atau operasi data harus dikodekan sebagai ekspresi, bukan kode prosedural. Dukungan hebat untuk ini adalah ketika bahasa pemrograman mendukung ekspresi, seperti LINQ di dunia .NET (sayangnya, hanya permintaan saat ini, tidak ada manipulasi). Di sisi DB relasional, telah diajarkan untuk waktu yang lama, untuk lebih suka ekspresi pernyataan SQL daripada loop kursor prosedural. Sehingga DB dapat mengoptimalkan, melakukan operasi paralel, atau apa pun yang mungkin berguna.

Memanfaatkan mekanisme integritas data DB

Ketika datang ke RDBMS dengan Foreign Key dan Periksa kendala, kolom terhitung, mungkin pemicu dan pandangan, ini adalah tempat untuk menyimpan logika bisnis dasar dalam database. Normalisasi yang tepat membantu menjaga integritas data, untuk memastikan contoh data yang unik dan berbeda. Bahkan jika Anda harus menduplikatnya dalam kode dan DB, mekanisme dasar integritas data ini tidak boleh dihilangkan!

Prosedur Tersimpan?

Prosedur Tersimpan jarang diperlukan saat ini, karena database terus menyusun rencana eksekusi untuk SQL dan menggunakannya kembali ketika permintaan yang sama datang lagi, hanya dengan parameter yang berbeda. Jadi argumen precompile untuk SPs tidak lagi valid. Satu dapat menyimpan atau menghasilkan secara otomatis kueri SQL dalam aplikasi atau ORM, yang akan menemukan rencana kueri yang telah dikompilasi sebagian besar waktu. SQL adalah bahasa ekspresi, selama Anda tidak secara eksplisit menggunakan elemen prosedural. Jadi, dalam kasus terbaik, Anda menggunakan ekspresi kode yang dapat diterjemahkan ke dalam SQL.

Sementara sisi aplikasi, termasuk ORM yang dihasilkan, SQL, tidak lagi ada di dalam database, tidak seperti Stored Procedures, saya masih menghitungnya sebagai kode basis data. Karena masih membutuhkan SQL dan pengetahuan basis data (kecuali CRUD yang paling sederhana), dan, jika diterapkan dengan benar, kerjanya sangat berbeda dari kode prosedural yang biasanya dibuat dengan bahasa pemrograman seperti C # atau Java.


2

Itu benar-benar tergantung pada bisnis, budaya dan warisannya. Selain pertimbangan teknis (ini telah dibahas dari kedua sisi), jawaban yang diberikan memberitahu Anda bahwa itu berasal dari mana orang berasal. Di beberapa organisasi, data adalah raja dan DBA adalah sosok yang kuat. Ini adalah lingkungan terpusat khas Anda, pusat data dengan banyak terminal yang melekat padanya. Preferensi dalam jenis lingkungan ini jelas. Desktop dapat berubah berkali-kali secara radikal sebelum ada perubahan apa pun di pusat data dan akan ada sedikit di antaranya.

Ujung lain dari spektrum adalah arsitektur 3-tier murni. Atau mungkin multi-tier dalam bisnis yang berorientasi web. Kemungkinan Anda akan mendengar cerita yang berbeda di sini. DBA, jika ada, hanya akan menjadi pendamping yang melakukan beberapa tugas administratif.

Pengembang aplikasi zaman modern akan memiliki lebih banyak kedekatan dengan model kedua. Jika Anda tumbuh dengan sistem server-klien besar, Anda kemungkinan besar akan berada di kubu lain.

Sering ada begitu banyak faktor terkait lingkungan non-teknis yang terlibat di sini, tidak ada jawaban umum untuk pertanyaan ini.


2

Logika bisnis istilah terbuka untuk interpretasi. Saat membangun sistem, kami ingin memastikan integritas basis data dan isinya. Sebagai langkah pertama harus ada hibah akses pengguna yang berbeda di tempat. Sebagai contoh yang sangat sederhana, mari kita pertimbangkan aplikasi ATM.

Untuk mendapatkan saldo akun, melakukan pemilihan pada tampilan yang sesuai harus dilakukan. Tetapi untuk mentransfer dana, Anda ingin transkapsulasi dengan prosedur tersimpan. Logika bisnis tidak boleh diizinkan untuk secara langsung memperbarui tabel untuk jumlah kredit dan debit.

Dalam contoh ini, logika bisnis dapat memeriksa saldo sebelum meminta transfer atau cukup memanggil proc yang disimpan untuk transfer dan melaporkan kegagalannya. IMHO, logika bisnis, dalam contoh ini, harus memeriksa secara pre-emptive bahwa dana yang cukup tersedia dan bahwa akun target ada dan hanya kemudian meminta dana transfer. Jika debet lain terjadi antara langkah-langkah awal dan permintaan proc disimpan, hanya kemudian kesalahan akan dikembalikan.


Contoh dan penjelasan yang bagus.
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.