Tiga bulan terakhir saya berkenalan dengan pekerjaan dan aplikasi yang mengharuskan 90% kode program yang saya tulis menggunakan bahasa pemrograman Go, atau biasa disebut Golang. Aplikasi berupa API ini adalah pengalaman pertama saya menulis program dalam bahasa Go dengan basis kode yang cukup besar.

Sebelumnya saya sudah pernah menulis API minimalis menggunakan Go untuk pengambilan kata Bahasa Indonesia secara acak. API ini dapat diutilisasi untuk pembuatan permainan tebak kata atau gambar seperti Gartic.io. Bagi yang berminat, aplikasinya bisa diakses melalui RapidAPI. Subscribe ya! :)

beg
GIF via Giphy

Lanjut ke aplikasi yang saya kerjakan saat ini, untuk koneksi dan segala aktifitas yang terkoneksi ke database menggunakan package go-pg. Package ini umum digunakan dalam pemrograman Go untuk memudahkan dalam mengontrol dan melakukan operasi terhadap database PostgresSQL. Salah satu fitur utama dari package ini adalah memberi kemudahan dalam memetakan dan mendefinisikan struktur database yang kita miliki ke dalam bentuk kode, sederhanya adalah setiap kolom dalam database dapat kita definisikan sebagai objek dengan kolom sebagai atributnya. Kalau dalam studi kasus dengan Go, tiap tabel dimodelkan ke dalam bentuk Struct.

Di PHP, khususnya yang familiar dengan framework Laravel terbiasa dengan istilah model untuk setiap kolom dalam database, library yang digunakan adalah Eloquent. Kemampuan dari suatu package atau libarary tersebut lebih dikenal dengan istilah ORM, Object-relational mapping. Pendefinisian struktur database dengan ORM ini bahkan dapat meliputi pendefinisian relasi antar tabel.

Kembali ke go-pg, beberapa waktu yang lalu saya mendapat tugas untuk membuat tabel baru termasuk mendefinisikan modelnya, tentunya dengan menggunakan go-pg. Anggaplah kasusnya saya harus menambahkan tabel recipe_items yang berisi item-item dari resep masakan yang ada di tabel recipes, maka SQL-nya kurang-lebih sebagai berikut:

CREATE TABLE recipe_items (
  id serial NOT NULL,
  recipe_id int NOT NULL,
  measurement_unit_id int NOT NULL,
  quantity real NOT NULL,

  PRIMARY KEY (id)
);

ALTER TABLE recipe_items ADD CONSTRAINT FK_recipe_items_recipe_id FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE recipe_items ADD CONSTRAINT FK_recipe_items_measurement_unit_id FOREIGN KEY (measurement_unit_id) REFERENCES measurement_units(id) ON DELETE RESTRICT ON UPDATE CASCADE;

Tambahan tabel dari SQL di atas kemudian saya definisikan dalam bentuk kode beserta dengan tags json dan go-pg nya.

type RecipeItem struct {
    ID                int     `pg:",pk" json:"id"`
    RecipeID          int     `json:"recipe_id"`
    MeasurementUnitID int     `json:"measurement_unit_id"`
    Quantity          float64 `json:"quantity"`

    tableName struct{} `pg:"recipe_items"`
}
beg
GIF via Giphy


Semua berjalan lancar hingga akhirnya ada skenario test dari QA yang menset nilai 0 pada kolom quantity. Muncul error, yang setelah ditelusuri ternyata berasal dari error SQL.

ERROR #23502 null value in column "quantity" violates not-null constraint


Penyebabnya adalah nilai 0 terkonversi menjadi null saat go-pg melakukan input ke database, sementara kolom quantity tidak boleh null. Untuk menonaktifkan perilaku go-pg yang mengkonversi 0 menjadi SQL null ini maka perlu ditambahkan tags pg:",use_zero" pada kolom quantity. Sehingga bentuknya menjadi:

type RecipeItem struct {
    ID                int     `pg:",pk" json:"id"`
    RecipeID          int     `json:"recipe_id"`
    MeasurementUnitID int     `json:"measurement_unit_id"`
    Quantity          float64 `pg:",use_zero" json:"quantity"`

    tableName struct{} `pg:"recipe_items"`
}


Selain 0 pada data numeric, penggunaan go-pg ini juga harus berhati-hati dengan tipe data lainnya, khususnya dalam mengontrol dan menvalidasi zero value pada kolom yang tidak boleh null (NOT NULL).

By default all columns except primary keys are nullable and go-pg marshals Go zero values (empty string, 0, zero time, nil map, and nil slice) as SQL NULL. This behavior can be changed using pg:",use_zero"tag. (pg.uptrace)