Lompat ke konten
dokumentasi.id
Commit 43aad26
v1
Star1,1 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

5

Entity dan Schema Database

Aplikasi ini menyimpan tiga tabel: users, contacts, dan addresses. Bentuk tabel ditentukan oleh berkas migration SQL, bukan oleh AutoMigrate, sehingga schema database dan struct Go harus dijaga tetap sinkron secara manual. Ketiga tabel dibuat berurutan mengikuti nama berkas migration: db/migrations/20231030144428_create_table_users.up.sql:1-10, db/migrations/20231030144435_create_table_contacts.up.sql:1-13, db/migrations/20231030144441_create_table_addresses.up.sql:1-14.

Di sisi Go, ketiga tabel diwakili struct GORM di internal/entity: User pada internal/entity/user_entity.go:4-12, Contact pada internal/entity/contact_entity.go:3-14, dan Address pada internal/entity/address_entity.go:3-14. Setiap struct menutup berkasnya dengan method TableName() yang mengembalikan nama tabel secara eksplisit: "users", "contacts", dan "addresses" (internal/entity/user_entity.go:14-16, internal/entity/contact_entity.go:16-18, internal/entity/address_entity.go:16-18).

Relasi antar tabel

Relasi membentuk rantai satu-ke-banyak: satu users memiliki banyak contacts, dan satu contacts memiliki banyak addresses. Keduanya ditegakkan lewat foreign key di level database, fk_contacts_user_id pada contacts dan fk_addresses_contact_id pada addresses (db/migrations/20231030144435_create_table_contacts.up.sql:12, db/migrations/20231030144441_create_table_addresses.up.sql:13).

erDiagram
    users {
        varchar100 id PK
        varchar100 password
        varchar100 name
        varchar100 token
        bigint created_at
        bigint updated_at
    }
    contacts {
        varchar100 id PK
        varchar100 first_name
        varchar100 last_name
        varchar100 email
        varchar100 phone
        varchar100 user_id FK
        bigint created_at
        bigint updated_at
    }
    addresses {
        varchar100 id PK
        varchar100 contact_id FK
        varchar255 street
        varchar255 city
        varchar255 province
        varchar10 postal_code
        varchar100 country
        bigint created_at
        bigint updated_at
    }
    users ||--o{ contacts : "fk_contacts_user_id"
    contacts ||--o{ addresses : "fk_addresses_contact_id"
%% fig: Relasi ketiga tabel beserta kolom dan foreign key
%% desc: Satu users memiliki banyak contacts dan satu contacts memiliki banyak addresses, dengan primary key varchar(100) dan foreign key yang dideklarasikan di berkas migration.
Fig. 1: Relasi ketiga tabel beserta kolom dan foreign keySatu users memiliki banyak contacts dan satu contacts memiliki banyak addresses, dengan primary key varchar(100) dan foreign key yang dideklarasikan di berkas migration.

Di sisi GORM, relasi yang sama dinyatakan lewat tag pada field struct, bukan lewat query manual. User.Contacts memakai foreignKey:user_id;references:id, Contact.User memakai tag yang sama untuk arah sebaliknya, Contact.Addresses memakai foreignKey:contact_id;references:id, dan Address.Contact menunjuk balik ke contacts dengan tag yang sama (internal/entity/contact_entity.go:12-13, internal/entity/address_entity.go:13).

Kolom tabel users

FieldKolomTipe SQLNullTag GORM
IDidvarchar(100)not nullcolumn:id;primaryKey
Passwordpasswordvarchar(100)not nullcolumn:password
Namenamevarchar(100)not nullcolumn:name
Tokentokenvarchar(100)nullcolumn:token
CreatedAtcreated_atbigintnot nullcolumn:created_at;autoCreateTime:milli
UpdatedAtupdated_atbigintnot nullcolumn:updated_at;autoCreateTime:milli;autoUpdateTime:milli
Contacts———foreignKey:user_id;references:id

Nama kolom di tag GORM sama persis dengan urutan kolom pada create table users (internal/entity/user_entity.go:5-11, db/migrations/20231030144428_create_table_users.up.sql:2-9).

Kolom tabel contacts

FieldKolomTipe SQLNullTag GORM
IDidvarchar(100)not nullcolumn:id;primaryKey
FirstNamefirst_namevarchar(100)not nullcolumn:first_name
LastNamelast_namevarchar(100)nullcolumn:last_name
Emailemailvarchar(100)nullcolumn:email
Phonephonevarchar(100)nullcolumn:phone
UserIduser_idvarchar(100)not nullcolumn:user_id
CreatedAtcreated_atbigintnot nullcolumn:created_at;autoCreateTime:milli
UpdatedAtupdated_atbigintnot nullcolumn:updated_at;autoCreateTime:milli;autoUpdateTime:milli
User———foreignKey:user_id;references:id
Addresses———foreignKey:contact_id;references:id

Perhatikan perbedaan penamaan: field Go memakai bentuk UserId, sedangkan kolomnya user_id (internal/entity/contact_entity.go:9, db/migrations/20231030144435_create_table_contacts.up.sql:8).

Kolom tabel addresses

FieldKolomTipe SQLNullTag GORM
IDidvarchar(100)not nullcolumn:id;primaryKey
ContactIdcontact_idvarchar(100)not nullcolumn:contact_id
Streetstreetvarchar(255)nullcolumn:street
Citycityvarchar(255)nullcolumn:city
Provinceprovincevarchar(255)nullcolumn:province
PostalCodepostal_codevarchar(10)nullcolumn:postal_code
Countrycountryvarchar(100)nullcolumn:country
CreatedAtcreated_atbigintnot nullcolumn:created_at;autoCreateTime:milli
UpdatedAtupdated_atbigintnot nullcolumn:updated_at;autoCreateTime:milli;autoUpdateTime:milli
Contact———foreignKey:contact_id;references:id

postal_code adalah satu-satunya kolom teks pendek di tabel ini, varchar(10) (internal/entity/address_entity.go:4-13, db/migrations/20231030144441_create_table_addresses.up.sql:3-12).

Konvensi yang berlaku di ketiga entity

Tiga hal berulang di semua struct: ID selalu bertipe string dengan tag primaryKey, timestamp disimpan sebagai int64 milidetik lewat autoCreateTime:milli dan autoUpdateTime:milli, dan setiap struct menutup berkasnya dengan TableName() (internal/entity/address_entity.go:4, internal/entity/address_entity.go:11-16).

Karena ID bukan auto increment, nilai ID dibuat di lapisan pemanggil. UserUseCase mengisi ID dari request.ID milik event, sementara helper test membuat uuid.NewString() untuk Contact dan Address (internal/usecase/user_usecase.go:89-93, test/helper_test.go:41-48). Panjang UUID 36 karakter masih masuk ke varchar(100).

⚠️ Kolom yang null di SQL tetap bertipe string biasa di struct, misalnya token, last_name, email, dan phone (internal/entity/user_entity.go:8, internal/entity/contact_entity.go:6-8). Konsekuensinya aplikasi tidak dapat membedakan NULL dari string kosong saat membaca baris.

Field enum tidak dipakai di sini; seluruh kolom teks, sehingga perubahan tipe harus dilakukan serentak di berkas migration dan tag struct.

Foreign key dipakai di query

Kolom foreign key bukan sekadar deklarasi. ContactRepository.FindByIdAndUserId memfilter id = ? AND user_id = ? (internal/repository/contact_repository.go:22-24), dan AddressRepository.FindByIdAndContactId memfilter id = ? AND contact_id = ? (internal/repository/address_repository.go:21-23). Pola ini memastikan data anak hanya dapat diakses lewat induknya. Detail method repository dibahas di Lapisan Repository.

Urutan hapus dan migrasi turun

Foreign key memaksa urutan operasi. Helper test menghapus dari anak ke induk: ClearAll memanggil ClearAddresses, lalu ClearContact, lalu ClearUsers (test/helper_test.go:12-16). Arah sebaliknya gagal karena baris induk masih dirujuk.

graph TD
    Clear["ClearAll"] --> Addr["ClearAddresses: Delete entity.Address"]
    Addr --> Cont["ClearContact: Delete entity.Contact"]
    Cont --> Usr["ClearUsers: Delete entity.User"]
    style Clear fill:#f59e0b,color:#000
    style Addr fill:#2d7d46,color:#fff
    style Cont fill:#8b5cf6,color:#fff
    style Usr fill:#336791,color:#fff
%% fig: Urutan hapus data mengikuti arah foreign key
%% desc: ClearAll menghapus addresses lebih dulu, lalu contacts, lalu users agar tidak melanggar foreign key.
Fig. 2: Urutan hapus data mengikuti arah foreign keyClearAll menghapus addresses lebih dulu, lalu contacts, lalu users agar tidak melanggar foreign key.

Berkas migration turun memakai drop table sederhana per tabel: users (db/migrations/20231030144428_create_table_users.down.sql:1), contacts (db/migrations/20231030144435_create_table_contacts.down.sql:1), dan addresses (db/migrations/20231030144441_create_table_addresses.down.sql:1).

⚠️ drop table users; akan ditolak MySQL bila baris di contacts masih merujuk ke tabel itu, karena fk_contacts_user_id. Untuk rollback manual, turunkan tabel dari anak ke induk, bukan dari induk ke anak. Perintah migration dijelaskan di Menjalankan Aplikasi, Migrasi, dan Test.

Koneksi GORM yang membaca schema ini dibuat sekali saat bootstrap lewat NewDatabase; jalur inisialisasi tersebut ada di Konfigurasi dan Bootstrap. Pemetaan entity ke model dan response dibahas di Lapisan Usecase, Model, dan Converter.