Tutorial desain database untuk Google Calendar
(kb.databasedesignbook.com)- Dengan fitur penjadwalan dari klon Google Calendar sebagai contoh, artikel ini menunjukkan alur desain yang tidak langsung membuat tabel, melainkan menyelesaikan model logis terlebih dahulu lalu memindahkannya ke skema SQL
- Metode intinya adalah menemukan lebih dulu anchor seperti User, DayEvent, TimeEvent, dan Timezone, lalu memvalidasi kebutuhan dan kardinalitas melalui atribut serta relasi 1:N dan M:N
- Jadwal dibagi menjadi event sepanjang hari dan event berbasis waktu; event berbasis waktu menyimpan waktu lokal mulai/selesai beserta masing-masing Timezone agar perubahan zona waktu di masa depan juga dapat ditangani
- Jadwal berulang direpresentasikan dengan frekuensi, interval, metode pengulangan bulanan, kondisi akhir, dan relasi hari dalam minggu; untuk rendering layar dan perubahan pada instance individual, DaySlot dan TimeSlot dijadikan anchor terpisah
- Desain fisik akhirnya memakai prinsip “satu tabel untuk tiap anchor”, tetapi tidak membuat DayOfTheWeek sebagai tabel, sehingga diringkas menjadi total 8 tabel SQL termasuk 6 tabel anchor dan 2 tabel relasi M:N
Pendekatan desain: buat model logis terlebih dahulu
- Tujuannya adalah menunjukkan proses desain tabel database yang mendekati proyek nyata, dengan fitur kalender dari klon Google Calendar sebagai sasaran
- Alur keseluruhannya adalah terlebih dahulu membuat model logis lengkap yang menjelaskan data kalender yang harus disimpan, lalu memindahkan model itu ke struktur tabel fisik
- Bagian awal membahas model logis yang tidak terikat pada database tertentu
- Memodelkan kebutuhan bisnis secara independen dari MySQL, Postgres, server relasional tradisional, NoSQL, maupun database cloud
- Bagian akhir menunjukkan salah satu pendekatan untuk mengubah model logis menjadi tabel fisik
- Untuk sistem dengan kebutuhan yang tidak terlalu menuntut, ini dapat dianggap sebagai pendekatan desain yang valid
Cakupan masalah: fitur inti penjadwalan Google Calendar
- Sistem yang dituju adalah sistem multi-pengguna, dan data terkait pengguna hanya dimodelkan seminimal mungkin
- Event bisa memiliki atribut seperti judul, deskripsi, dan lokasi, tetapi bagian yang paling kompleks adalah waktu dan tanggal
- Event yang dimodelkan mencakup perbedaan berikut
- event sepanjang hari dan event berbasis waktu
- event berulang dan event tidak berulang
- event sepanjang hari yang membentang beberapa hari
- event berbasis waktu yang terhubung ke zona waktu, dan waktu mulai/selesai dapat berada pada tanggal berbeda atau zona waktu berbeda
- Event berulang mencakup bentuk-bentuk berikut
- berulang setiap hari atau setiap N hari
- berulang tiap minggu pada hari tertentu, dengan interval 2 minggu atau lebih juga dimungkinkan
- berulang tiap bulan pada tanggal tertentu atau pada hari tertentu di minggu tertentu
- berulang tiap tahun
- berulang selamanya, sampai tanggal tertentu, atau sebanyak jumlah tertentu
- Instance tertentu dari event berulang dapat dipindahkan ke tanggal/jam lain atau dihapus
- Jadwal berulang itu sendiri juga harus bisa diubah bahkan setelah sebagian event sudah lewat
Mengekspresikan kebutuhan dengan anchor, atribut, dan relasi
- Pemodelan dimulai dengan menemukan anchor terlebih dahulu
- Anchor adalah objek seperti User atau Event yang dapat dihitung dan dapat ditambahkan ke database
- Jika bisa diucapkan secara alami seperti “There are 200 Users in our database” atau “When this button is clicked, a new DayEvent is created”, maka itu dapat dianggap sebagai anchor
- Atribut menyimpan informasi aktual tentang anchor
- User memiliki atribut email
- DayEvent memiliki nama, tanggal mulai, dan tanggal selesai
- Bahkan untuk event satu hari, tanggal mulai dan tanggal selesai disimpan dengan nilai yang sama agar ditangani dengan struktur yang sama seperti event multi-hari
- Jika dua anchor memiliki hubungan, gunakan relasi alih-alih atribut
- “User mana yang membuat DayEvent ini” bukanlah atribut logis berupa ID User yang dimasukkan langsung ke DayEvent, melainkan relasi antara User dan DayEvent
- Relasi memakai kardinalitas 1:N, M:N, atau 1:1, dan maknanya divalidasi dua arah dengan dua kalimat
- Ada relasi 1:N antara User dan DayEvent
- User dapat membuat banyak DayEvent
- DayEvent dibuat oleh satu User
Event berbasis waktu dan penanganan Timezone
- Untuk event berbasis waktu, ditambahkan anchor Timezone dan TimeEvent
- Model Timezone diminimalkan untuk keperluan tutorial, sehingga hanya memiliki atribut nama yang bisa dibaca manusia
- Contoh nilainya adalah
Europe/Kyiv - Definisi zona waktu yang nyata memerlukan UTC offset, apakah daylight saving time berlaku, awal/akhir DST, UTC offset saat DST berlaku, definisi historis, status aktif/nonaktif, dan seterusnya, tetapi itu dibiarkan sebagai tugas terpisah
- Contoh nilainya adalah
- TimeEvent memiliki nama, tanggal-waktu lokal mulai, dan tanggal-waktu lokal selesai
- Contoh nilainya adalah
2024-01-14 12:30,2024-01-14 13:15
- Contoh nilainya adalah
- Waktu tidak disimpan hanya sebagai UTC, tetapi sebagai waktu lokal yang dimasukkan pengguna
- Sebagai contoh, permainan biliar pada 6 September 2058 pukul 09:30~11:00 waktu Cologne harus disimpan persis seperti yang dimasukkan pengguna, karena UTC offset pada saat itu belum tentu diketahui sekarang
- Jika hukum setempat berubah di kemudian hari, datanya harus bisa disesuaikan dengan perubahan itu
- TimeEvent memiliki relasi 1:N ke Timezone secara terpisah untuk zona waktu mulai dan zona waktu selesai
- Seperti contoh tiket pesawat, penerbangan dari Amsterdam ke London dapat memiliki zona waktu berbeda antara keberangkatan dan kedatangan
- Sebagian besar event berbasis waktu memang memakai zona waktu yang sama untuk mulai dan selesai, tetapi demi kasus umum, keduanya selalu ditentukan
Pemodelan event sepanjang hari yang berulang
- Event sepanjang hari yang berulang direpresentasikan dengan menambahkan atribut dan relasi terkait pengulangan pada DayEvent
- Frekuensi pengulangan dibuat sebagai atribut bertipe either/or/or
- Nilai yang mungkin adalah daily, weekly, monthly, annually
- Jika nilainya tidak diatur, event dianggap tidak berulang
- Interval pengulangan adalah atribut integer yang hanya bermakna “jika event ini berulang”
- Contoh nilainya adalah
2 - Dipakai dengan arti seperti setiap 2 hari, setiap 2 minggu, atau setiap 2 bulan
- Contoh nilainya adalah
- Pengulangan bulanan memiliki atribut pilihan terpisah
same_dayuntuk mengulang pada tanggal yang samasame_weekdayuntuk mengulang pada hari yang sama dalam minggu yang sama seperti tanggal asal
- Hari tertentu pada pengulangan mingguan tidak dimodelkan sebagai atribut array string, melainkan dengan anchor DayOfTheWeek dan relasi M:N
- Contoh ID DayOfTheWeek adalah
Mon,Tue,Wed,Thu,Fri,Sat,Sun - DayEvent dapat terjadi pada banyak DayOfTheWeek, dan DayOfTheWeek dapat dimiliki banyak DayEvent
- Contoh ID DayOfTheWeek adalah
- Kondisi akhir pengulangan juga dibuat sebagai atribut
- forever
- until_date
- N_repetitions
- Untuk
until_date, ditambahkan atribut tanggal akhir - Untuk
N_repetitions, ditambahkan atribut jumlah pengulangan
Slot untuk rendering dan perubahan instance individual
- Jika event berulang hanya disimpan sebagai satu event asal, query untuk mencari event yang harus ditampilkan pada tampilan minggu tertentu bisa menjadi rumit
- Untuk rendering layar dan perubahan individual pada event berulang, diperkenalkan konsep Slot
- DaySlot merepresentasikan instance event sepanjang hari pada tanggal tertentu
- TimeSlot merepresentasikan instance event berbasis waktu pada tanggal dan jam tertentu
- DaySlot memiliki tanggal dan status dilewati
- Tanggal pada slot tertentu dapat diubah
- Jika rapat pada minggu tertentu dibatalkan, DaySlot tersebut dapat ditandai sebagai skipped
- DayEvent dan DaySlot memiliki relasi 1:N
- Satu DayEvent dapat menghasilkan banyak DaySlot
- DaySlot berkorespondensi dengan satu DayEvent
- Bahkan untuk DayEvent yang tidak berulang, DaySlot tetap selalu dibuat agar kode rendering lebih sederhana
- Untuk event yang berulang tanpa batas, ada persoalan jangkauan pembuatan slot
- Event seperti ulang tahun yang berulang tiap tahun selamanya bisa dibuatkan slot sampai batas arbitrer seperti 100 tahun
- Atau slot dapat dibuat on-demand ketika pengguna meminta halaman kalender di masa depan yang jauh
- Perhitungan tanggal perlu diperhatikan
- ulang tahun 29 Februari
- event bulanan yang terjadi setiap tanggal 31
- Untuk kasus seperti ini, perlu diputuskan apakah penggunaannya dilarang, atau dipindahkan satu hari lebih awal atau lebih akhir
TimeSlot dan zona waktu
- Untuk event berulang berbasis waktu, pendekatan yang sama seperti event sepanjang hari diterapkan dengan anchor TimeSlot
- TimeSlot merepresentasikan instance tanggal-waktu tertentu dari suatu event, dan TimeEvent berulang berkorespondensi dengan banyak TimeSlot
- Atribut TimeSlot adalah tanggal-waktu lokal mulai, tanggal-waktu lokal selesai, dan status dilewati
- Karena TimeSlot tertentu dapat dipindahkan ke hari lain, informasi waktu mulai/selesai dipertahankan secara terpisah
- TimeSlot juga memiliki relasi 1:N ke Timezone secara terpisah untuk zona waktu mulai dan zona waktu selesai
- Ada relasi 1:N antara TimeEvent dan TimeSlot, dan TimeSlot juga dibuat untuk TimeEvent yang tidak berulang
Model logis yang selesai
- Total anchor yang didefinisikan sejauh ini adalah 7
- User
- Timezone
- DayEvent
- TimeEvent
- DayOfTheWeek
- DaySlot
- TimeSlot
- Atributnya terdiri dari email User, nama Timezone, nama/tanggal mulai/tanggal selesai/informasi pengulangan pada DayEvent, waktu lokal mulai/selesai pada TimeEvent, serta waktu kejadian dan status skipped pada DaySlot dan TimeSlot
- Total relasinya diringkas menjadi 10
- User dengan DayEvent, User dengan TimeEvent
- Timezone dengan zona waktu mulai/selesai pada TimeEvent
- DayEvent dengan DayOfTheWeek, TimeEvent dengan DayOfTheWeek
- DayEvent dengan DaySlot, TimeEvent dengan TimeSlot
- Timezone dengan zona waktu mulai/selesai pada TimeSlot
Memindahkannya ke tabel SQL
- Desain fisik menggunakan strategi satu tabel untuk tiap anchor untuk tujuan pembelajaran
- Model logis memiliki 7 anchor, 21 atribut, dan 10 relasi
- Secara umum jumlahnya akan menjadi 9 tabel dengan menambahkan jumlah anchor dan relasi M:N, tetapi pada desain akhir DayOfTheWeek diperlakukan secara khusus sehingga tidak dibuat sebagai tabel fisik
- Total tabel SQL akhirnya adalah 8
userstimezonesday_eventstime_eventsday_slotstime_slotsday_event_dowstime_event_dows
- Relasi 1:N masuk sebagai kolom bernuansa foreign key di tabel sisi N
day_events.user_idtime_events.user_idtime_events.start_timezone_idtime_events.end_timezone_idday_slots.day_event_idtime_slots.time_event_idtime_slots.start_timezone_idtime_slots.end_timezone_id
- Relasi M:N dibuat sebagai tabel terpisah
day_event_dowsmenghubungkan DayEvent dengan hari dalam minggutime_event_dowsmenghubungkan TimeEvent dengan hari dalam minggu
- Tipe fisik dipilih sesuai pasangan tipe logisnya
- string menjadi
VARCHAR - tanggal menjadi
DATE - tanggal-waktu lokal menjadi
DATETIME - yes/no menjadi
TINYINT UNSIGNED - atribut yang saling terkait dan hanya bermakna secara kondisional memakai tipe yang mengizinkan
NULL
- string menjadi
NULLdan nilai sentinel diperlakukan sebagai konsep skema fisik, bukan model logis
Indeks dan atribut yang dihilangkan
- Skema SQL contoh tidak mencantumkan indeks yang menurut pengembang database berpengalaman mungkin diperlukan
- Sebagai contoh,
day_events.user_idmungkin memerlukan indeks
- Sebagai contoh,
- Kolom atau kombinasi kolom mana yang perlu diberi indeks bergantung pada bagaimana aplikasi melakukan query ke tabel
- Untuk topik indeks, dapat merujuk ke Use The Index, Luke
- Saat menjelaskan skema logis, beberapa atribut dihilangkan karena mengikuti pola serupa
- nama pengguna
- hash kata sandi pengguna
- lokasi event
- daftar orang yang diundang
- Elemen data yang dihilangkan dapat ditambahkan ke tabel model logis dengan cara yang sama lalu dicerminkan ke skema SQL
Prosedur keseluruhan
- Mulai dari teks bebas yang menjelaskan masalah bisnis
- Menyusun daftar anchor
- Menyusun daftar atribut, sambil menuliskan makna atribut secara jelas dalam bentuk pertanyaan
- Menyusun daftar relasi, lalu memeriksa dengan kalimat apakah kardinalitasnya tepat
- Jika perlu, membuat skema grafis berdasarkan model logis
- Menyusun model fisik dengan mengisi nama tabel, nama kolom, dan tipe data fisik
- Menggunakan informasi dari langkah sebelumnya untuk menulis skema SQL dalam pernyataan
CREATE TABLE - Mengirimkan skema ke server database, memperbaiki salah ketik, lalu mengirimkannya lagi
- Membagikan model logis kepada tim
1 komentar
Pendapat di Hacker News
Jika saya menserialisasi satu event acak dari kalender saya ke iCalendar, ukurannya sekitar 740 byte. Bahkan kalender yang sangat sibuk dengan event setiap 15 menit dari pukul 09.00 sampai 17.00 selama setahun penuh hanya berisi 11.680 item, sekitar 8 MB
Jadi, alih-alih merancang skema, kalender tampak seperti masalah yang mungkin jauh lebih baik diimplementasikan cukup dengan pemindaian sekuensial. Jika parser iCalendar dioptimalkan agar dapat menyapu rentang event yang di-dump dengan throughput kelas GB/s, skenario terburuk di atas pun bisa dipindai dalam hitungan milidetik satu digit
Mengoptimalkan parser adalah masalah yang jauh lebih sederhana dan hanya perlu diselesaikan sekali dibandingkan mengubah atau memperluas model data yang buruk setelah tersebar ke banyak pengguna. Lagi pula, pekerjaan pertama model data baru kemungkinan besar tetap impor/ekspor iCalendar, jadi ini terlihat sebagai kompromi yang cukup baik
BETWEEN ... AND ...di SQL? Begitu juga untuk mencari event milik pengguna 1, 2, 3Dalam aplikasi nyata, kemungkinan sudah ada hal seperti akun pengguna di basis data relasional, tetapi tiba-tiba kita harus memindai event di direktori lalu menghubungkan hasilnya dengan record basis data
Untuk aplikasi tertentu itu mungkin cocok, tetapi ada pekerjaan tertentu yang memang sangat dikuasai basis data, dan karena itulah basis data menjadi pilihan yang baik. Dengan indeks yang tepat, throughput yang sama atau bahkan lebih baik juga mungkin dicapai. Kecuali jika Anda membuat struktur direktori cerdas untuk event; itu pada dasarnya sama seperti indeks satu dimensi, sementara di basis data Anda bisa membuat indeks untuk banyak dimensi dan kombinasi dimensi
Pada akhirnya itu benar, dan ini soal kompromi
Usia formatnya terlihat; tampak bahwa ia dirancang sebelum XML/JSON menjadi “populer”
Referensi: https://en.wikipedia.org/wiki/ICalendar
Ini artikel tentang menangani jadwal berulang dalam aplikasi, dan bagi saya pribadi sangat membuka mata. Sangat saya sarankan untuk dibaca: https://github.com/bmoeskau/Extensible/blob/master/recurrenc...
Artikel asli mengatakan untuk selalu menyimpan zona waktu pada setiap tanggal, sedangkan artikel yang ditautkan mengatakan untuk mengonversi semuanya ke UTC. Untuk bagian ini saya setuju dengan artikel asli
Artikel asli mengatakan untuk membuat baris basis data untuk setiap event, sedangkan artikel yang ditautkan mengatakan jangan lakukan itu. Untuk bagian ini saya setuju dengan artikel yang ditautkan
Kegagalan terbesar saya dalam wawancara adalah ketika diminta membuat sistem reservasi sederhana yang juga harus menangani reservasi berulang
Sejak awal masa kuliah, saya tidak pernah merasa begitu tersesat dan bingung saat mencoba menyelesaikan sesuatu dengan kode. Sampai sekarang pun saya langsung gentar ketika harus mengerjakan hal terkait tanggal/waktu, dan entah kenapa semuanya tidak pernah terasa pas di kepala saya
Di permukaan tampak sederhana, tetapi jika ingin membuatnya bekerja secara umum di dunia nyata, masalahnya menjadi begitu kompleks hingga nyaris mustahil
Ini adalah masalah yang tampak menipu mudah padahal sulit, dan membutuhkan kemampuan pemodelan data yang sangat baik
Kemampuan memodelkan sistem kurang dihargai. Dalam domain baru, kita harus mulai dari analisis menyeluruh atas masalah untuk menangkap baik struktur statis sistem, yaitu model kelas, maupun perilaku dinamisnya, yaitu use case
Jika langsung melompat ke model basis data statis, perilaku dinamis mudah terlewat. Untuk aplikasi CRUD sederhana seperti ini mungkin tidak apa-apa, tetapi pada sistem yang lebih kompleks itu bisa menjadi kesalahan besar
Pertanyaan terakhir yang saya ingat adalah bagaimana membuat tabel produk yang memiliki kupon. Awalnya saya pikir dua tabel saja cukup, tetapi bukankah riwayat juga harus dipertahankan? Kalau begitu, kita harus memasukkan dan memperbarui tanggal/waktu per produk dan kupon. Sekarang kita juga harus memikirkan indeks tabel, dan apakah join untuk mendapatkan harga diskon merupakan cara yang baik. Sebagian besar kupon hanya bisa dipakai sekali oleh satu orang, lalu bagaimana cara mengimplementasikannya?
Mungkin mereka hanya menginginkan tabel produk + kupon sederhana, tetapi saya malah menggali sendiri terlalu jauh seperti orang gila
Saya rasa ini domain yang dipilih dengan baik untuk menunjukkan pemodelan, dan merupakan artikel pengantar yang bagus
Istilah “anchor” terasa agak aneh, tetapi penjelasannya sangat konkret dan membumi seperti jangkar sungguhan, jadi cukup bisa diterima
Konsep mendefinisikan atribut melalui pertanyaan itu solid. Terlalu sering kita langsung melompat ke nama kolom/atribut minimum tanpa mendefinisikan pertanyaan apa yang ingin kita jawab, dan itu gagal menghilangkan ambiguitas di kepala pelanggan. Definisi atribut berbasis pertanyaan adalah cara yang baik untuk mendapatkan kejelasan dengan cepat
Selain itu, istilah ini sudah sangat kelebihan makna. Dalam ilmu komputer, pada dasarnya semua hal adalah objek atau entitas
Tanggapan bahwa konsep mendefinisikan atribut melalui pertanyaan itu solid menjadi konfirmasi penting atas validitas pendekatan ini
Zona waktu benar-benar bisa bikin pusing, terutama di sekitar titik transisi
Misalnya zona waktu maju satu jam saat daylight saving time, lalu mundur satu jam saat kembali ke waktu standar
Jika waktu melompat maju satu jam, event berdurasi 1 jam bisa terlihat di layar seolah berlangsung selama 2 jam, dan jam kedua itu tidak bisa dijangkau atau bahkan tidak ada
Jika waktu mundur satu jam, event berdurasi 1 jam bisa terlihat seperti 2 jam, atau terlihat seperti 0 jam
Zona waktu adalah konstruksi buatan manusia, jadi nilainya tidak boleh di-hardcode. Suatu saat pasti berubah
Kita bisa bolak-balik melintasi zona waktu ke dua arah, bisa juga melintasi garis tanggal internasional, dan tanggal/waktu mulai lokal bahkan bisa terjadi setelah tanggal/waktu selesai lokal
Lebih baik fokus menghapus konsep daylight saving time di beberapa wilayah yang masih menggunakannya. Hal itu paling sering menimbulkan kebingungan antarzona waktu, terutama saat merencanakan jadwal setelah transisi yang akan datang
Saya tidak ingin menyimpan dua tanggal untuk sebuah event. Lebih mudah menyimpan waktu mulai dan durasi event
Dengan begitu, logika pembaruan event menjadi sederhana. Waktu selesai kapan saja bisa dihitung berdasarkan waktu mulai dan durasi
Dulu saya pernah mengerjakan aplikasi kalender dengan jadwal berulang. Setelah banyak riset, kami memutuskan memakai RRules untuk merepresentasikannya, dan hasilnya sangat memuaskan. Pekerjaan awal itu saya lakukan saat masih di agensi
Belakangan, ketika saya bergabung penuh waktu dengan perusahaan itu, saya terkejut melihat kontraktor dari perusahaan lain menghapus RRules dan menggantinya dengan cara membuat serta menghapus instance event secara langsung. Toleransi gangguannya nyaris tidak ada, sehingga skrip yang juga mengerjakan hal lain dan kadang gagal bisa gagal membuat event baru. Akibatnya, ada bulan yang hilang dari event berulang bulanan
Rasanya sangat menjengkelkan melihat sesuatu yang dibuat lewat banyak pemikiran dan riset dihapus oleh seseorang tanpa usaha yang sepadan. Setelah beberapa minggu saja di perusahaan itu, jelas terlihat bahwa CEO berharap tim engineering terus mengeluarkan fitur yang tidak dipakai siapa pun sesuai kemauannya, dan karena ketidakpastian pasar kerja, sayangnya saya bertahan selama 2 tahun
Sebagai tambahan, belakangan saat saya mencari, rasanya benar-benar pahit melihat di Glassdoor ada ulasan palsu yang jelas ditulis CEO, semuanya dengan gaya tulisan yang sama dan tanpa satu pun komentar buruk. Saya dan beberapa kenalan yang pernah bekerja di sana tidak menyukainya, tetapi setidaknya ada penghiburan bahwa saya menulis beberapa esai terbaik saya di perusahaan itu. CTO-nya juga tidak bisa diharapkan
Saya pernah mengimplementasikan backend kalender dan kontrol resource untuk platform low-code
Kontrolnya sangat bisa dikustomisasi, dan selain berbagai tampilan seperti harian, bulanan, dan tahunan, juga bisa memilih tampilan resource. Resource bisa dipesan dengan grup kustom, dan bisa dikelompokkan berdasarkan plugin, ID resource, dan sebagainya. Di data source, kami mendefinisikan “plugins”, lalu menentukan kolom mulai/selesai, kolom judul, dan kolom resource. Resource bisa berasal dari foreign key atau relasi 1:1, bisa juga dari relasi 1:N pada data source “anak”, atau dari data source/tabel yang sama
Kami juga mengimplementasikan berbagai seri appointment seperti bulanan, mingguan, hari tertentu dalam minggu, harian, dan sebagainya, serta memungkinkan pengguna memilih nilai kolom mana yang akan disalin. Konflik appointment juga ditangani, dan konflik bisa dianggap terjadi hanya saat memesan resource yang sama. Buffer sebelum dan sesudah appointment, yang tidak boleh diisi appointment lain, juga bisa dikonfigurasi
Kadang cukup menantang karena zona waktu Eropa serta waktu musim panas/musim dingin, tetapi pekerjaannya cukup menyenangkan
Yang saya inginkan dari Google Calendar hanyalah log perubahan untuk kalender itu sendiri. Tolong sekali tambahkan ini ke database