Repo adalah process yang menjadi satu-satunya pintu masuk ke database pada aplikasi Elixir yang menggunakan Ecto: seluruh pembacaan dan penulisan data melewati fungsi-fungsinya. Topik ini membahas konfigurasi Repo, pembukaan koneksi pertama ke PostgreSQL, query, yaitu perintah yang dikirim ke database melalui Repo.query!/2, serta persiapan sandbox pengujian yang membuat setiap tes berjalan pada data yang selalu kembali bersih. Seluruh contoh dijalankan terhadap server Postgres di 127.0.0.1 port 5433 dengan pengguna postgres tanpa kata sandi.
Repo sebagai gateway database#
Repo adalah modul yang membungkus pool, yaitu kumpulan koneksi database yang dipinjam bergantian oleh pemanggil, dan menawarkan antarmuka akses data yang seragam. Melalui antarmuka tersebut, Repo berperan sebagai gateway, yakni satu titik lalu lintas data menuju database. Modul Repo didefinisikan dengan use Ecto.Repo yang menerima dua informasi penting, yaitu otp_app sebagai nama aplikasi tempat konfigurasi disimpan dan adapter sebagai driver database yang digunakan. Definisi berikut adalah bentuk yang dihasilkan generator Phoenix untuk sebuah proyek bernama perpustakaan:
# tidak-dijalankan
defmodule Perpustakaan.Repo do
use Ecto.Repo,
otp_app: :perpustakaan,
adapter: Ecto.Adapters.Postgres
enduse Ecto.Repo tidak menyimpan nilai konfigurasi apa pun di dalam kode. Nilai konfigurasinya dibaca dari Application environment, yaitu penyimpanan pengaturan yang berjalan bersama VM, dengan kunci otp_app dan nama modul Repo. Pada proyek Mix, nilai itu ditulis di berkas konfigurasi:
# tidak-dijalankan
import Config
config :perpustakaan, Perpustakaan.Repo,
database: "perpustakaan_dev",
username: "postgres",
password: "postgres",
hostname: "localhost"Berkas config/config.exs memuat pengaturan yang sama untuk semua lingkungan, sedangkan nilai yang berbeda antarlingkungan ditempatkan pada config/dev.exs, config/test.exs, dan config/prod.exs. Skrip pada modul ini berjalan tanpa proyek Mix, sehingga konfigurasi yang sama disetel secara programatik melalui Application.put_env/3 sebelum modul Repo didefinisikan. Karena kunci dan nilainya sama persis, pola konfigurasinya tetap dapat dipindahkan ke proyek sungguhan.
Konfigurasi koneksi dan pool#
Setiap kunci konfigurasi mengatur satu aspek koneksi, yaitu alamat server, identitas pengguna, nama database, dan jumlah koneksi yang dipelihara. Tabel berikut merangkum kunci yang paling sering disetel:
| Kunci | Peran |
|---|---|
hostname | Alamat server database, misalnya 127.0.0.1. |
port | Port server, 5432 secara default dan 5433 pada contoh modul ini. |
username | Identitas pengguna database yang digunakan untuk autentikasi. |
database | Nama database tujuan. |
pool_size | Jumlah koneksi yang dipelihara dalam pool, defaultnya 10. |
log | Pengaturan pencatatan query, false untuk meredam log debug. |
queue_target | Batas tunggu pinjaman koneksi dalam milidetik sebelum permintaan mulai ditolak. |
Konfigurasi khusus lingkungan development dituliskan pada config/dev.exs:
# tidak-dijalankan
import Config
config :perpustakaan, Perpustakaan.Repo,
hostname: "127.0.0.1",
port: 5433,
database: "perpustakaan_dev",
pool_size: 10Pemahaman pool menentukan cara membaca perilaku Repo. pool_size mengatur berapa koneksi yang dibuka ke server, dan setiap operasi database meminjam salah satunya untuk sementara. Apabila seluruh koneksi sedang dipinjam, permintaan berikutnya menunggu di antrean selama queue_target milidetik sebelum mulai dibatalkan. Kunci log perlu diperhatikan pada skrip: secara default setiap query tercatat pada level debug beserta durasinya, sehingga keluaran program dipenuhi baris QUERY OK yang mencemari hasil cetak. Menyetel log: false meredam baris-baris tersebut.
Skrip utuh: koneksi pertama#
Skrip berikut merangkum seluruh materi di atas menjadi satu alur yang dapat dijalankan. Repo dikonfigurasi dan didefinisikan, database dibuat apabila belum ada melalui koneksi admin yang terpisah, kemudian Repo dijalankan dan beberapa query langsung dieksekusi:
# koneksi.exs — membuka Repo dan menjalankan query pertama
Mix.install([{:ecto_sql, "~> 3.13"}, {:postgrex, "~> 0.21"}])
Logger.configure(level: :warning)
Application.put_env(:jalur, Repo,
hostname: "127.0.0.1", port: 5433, username: "postgres",
database: "jalur_repo", pool_size: 2, log: false, queue_target: 500)
defmodule Repo do
use Ecto.Repo, otp_app: :jalur, adapter: Ecto.Adapters.Postgres
end
# membuat database bila belum ada, lewat koneksi admin yang terpisah
{:ok, admin} =
Postgrex.start_link(
hostname: "127.0.0.1", port: 5433, username: "postgres", database: "postgres"
)
case Postgrex.query(admin, "CREATE DATABASE jalur_repo", []) do
{:ok, _} -> :dibuat
{:error, %{postgres: %{code: :duplicate_database}}} -> :sudah_ada
end
{:ok, _} = Repo.start_link()
# query langsung tanpa schema: SELECT 1
{:ok, hasil} = Repo.query("SELECT 1 AS satu")
IO.inspect(hasil.rows, label: "SELECT 1")
# query berparameter
{:ok, hitung} = Repo.query("SELECT $1::int + $2::int AS jumlah", [2, 3])
IO.inspect(hitung.rows, label: "SELECT $1 + $2")
# identitas koneksi yang sedang dipakai
{:ok, info} = Repo.query("SELECT current_database() AS db, current_user AS siapa")
IO.inspect(info.rows, label: "koneksi aktif")SELECT 1: [[1]]
SELECT $1 + $2: [[5]]
koneksi aktif: [["jalur_repo", "postgres"]]Skrip ini dijalankan dengan perintah berikut dari folder topiknya:
# menjalankan skrip koneksi
elixir koneksi.exsSELECT 1: [[1]]
SELECT $1 + $2: [[5]]
koneksi aktif: [["jalur_repo", "postgres"]]Tiga hal pada skrip tersebut layak digarisbawahi. Pertama, database dibuat melalui koneksi admin ke database postgres karena perintah CREATE DATABASE tidak dapat dijalankan melalui Repo yang menunjuk database yang belum ada. Klausa penangkap duplicate_database membuat skrip tetap aman dijalankan ulang tanpa pesan apa pun. Kedua, Repo.query/2 mengembalikan {:ok, %Postgrex.Result{}} dengan kolom rows berupa list dari list, persis seperti [[1]] pada keluaran. Ketiga, query kedua menggunakan placeholder $1 dan $2 untuk nilai dari luar, sehingga nilai tidak pernah dirangkai ke dalam teks SQL. Penulisan SQL mentah seperti ini tidak diajarkan lebih jauh pada jalur Elixir karena keterampilan SQL dikuasai melalui jalur PostgreSQL.
Sandbox pengujian#
Sandbox pengujian membuat setiap tes berjalan di dalam transaksi database yang dibatalkan setelah tes selesai, sehingga data yang ditulis tes tidak pernah tersisa di database. Mekanisme ini disediakan oleh Ecto.Adapters.SQL.Sandbox dan dipasang dengan dua langkah, yaitu mengganti pool pada konfigurasi lingkungan tes dan menetapkan mode sandboxnya:
# tidak-dijalankan
import Config
config :perpustakaan, Perpustakaan.Repo,
hostname: "127.0.0.1",
port: 5433,
database: "perpustakaan_test",
pool: Ecto.Adapters.SQL.Sandbox# tidak-dijalankan
Perpustakaan.Repo.start_link()
Ecto.Adapters.SQL.Sandbox.mode(Perpustakaan.Repo, :manual)
ExUnit.start()Mode :manual berarti tidak ada tes yang memperoleh koneksi secara diam-diam. Setiap tes harus melakukan checkout, yaitu meminjam satu koneksi sandbox untuk dirinya, melalui Ecto.Adapters.SQL.Sandbox.start_owner!/2 pada callback setup, lalu melepaskannya dengan stop_owner/1 melalui on_exit. Pemisahan ini membuat tes dengan tag async: true dapat berjalan bersamaan tanpa saling mengganggu data, karena setiap pemilik koneksi bekerja di dalam transaksinya sendiri.
Kesalahan umum#
Database yang belum dibuat adalah kegagalan pertama yang paling sering muncul. Gejalanya menipu karena Repo.start_link() tetap mengembalikan {:ok, pid}, sedangkan error baru muncul pada query pertama berupa DBConnection.ConnectionError tentang koneksi yang tidak tersedia. Log pada saat itu menampilkan penyebab sebenarnya secara berulang, yaitu FATAL 3D000 (invalid_catalog_name) dengan pesan “database does not exist”. Perbaikannya adalah membuat database lebih dahulu melalui koneksi admin seperti yang dilakukan skrip koneksi.exs, bukan menaikkan pool_size yang tidak berhubungan dengan masalah.
Kesalahan kedua adalah melupakan log: false pada skrip atau peraga kecil. Tanpa kunci itu, setiap query menambahkan baris log debug QUERY OK berikut durasinya, sehingga keluaran yang seharusnya tiga baris menjadi puluhan baris dan sulit dibandingkan antareksekusi. Untuk program yang memang memerlukan pencatatan, redam level Logger ke :warning seperti yang dilakukan setiap skrip pada modul ini, karena baris log query berada di level debug.
Ringkasan#
- Repo adalah process yang mengelola pool koneksi dan menjadi satu-satunya pintu masuk data. Konfigurasinya dibaca dari Application environment dengan
otp_appdan nama modul Repo sebagai kuncinya. - Kunci konfigurasi utama mencakup
hostname,port,username,database,pool_size,log, danqueue_target. Repo.query!/2menjalankan SQL mentah dengan nilai terikat melalui placeholder, dan hasilnya berupa list dari list padarows.- Sandbox pengujian membungkus setiap tes dalam transaksi yang dibatalkan di akhir tes. Penggantian
poolpadaconfig/test.exsdan mode:manualpadatest_helper.exswajib dipasang berpasangan.
Referensi#
- Ecto.Repo — Hexdocs Ecto — dokumentasi resmi modul Repo beserta daftar kunci konfigurasinya.
- Ecto.Adapters.SQL — Hexdocs Ecto SQL
— penjelasan adapter SQL dan fungsi query langsung seperti
Repo.query!/2. - Ecto.Adapters.SQL.Sandbox — Hexdocs Ecto SQL — cara kerja sandbox beserta mode dan pola checkoutnya.
- Ecto.Adapters.Postgres — Hexdocs Ecto SQL — opsi koneksi khusus adapter Postgres.