tutorial

110 Custom Model Mapping: Menghubungkan Tipe ke Struct Sendiri

110 Custom Model Mapping: Menghubungkan Tipe ke Struct Sendiri

Saat membangun aplikasi backend atau aplikasi enterprise berskala besar, sering kali kita dihadapkan pada kebutuhan untuk melakukan data mapping. Tidak jarang pula, model data dari sumber eksternal (seperti response dari service lain, database, atau API pihak ketiga) tidak langsung sesuai dengan model atau struct yang kita gunakan dalam aplikasi. Custom model mapping menjadi solusi elegan untuk kebutuhan ini: cara kita menyambungkan tipe data “asing” ke dalam struct buatan sendiri dengan aturan main kita sendiri.

Pada artikel ini, saya akan membahas secara mendalam teknik “custom model mapping”, menyorot konsep general hingga ke praktik implementasinya dengan contoh kode konkret, simulasi kasus, dan penggunaan flowchart untuk memetakan logika mapping yang baik. Fokus utamanya pada bahasa Go (Golang), tapi pola-nya sebenarnya sangat universal untuk hampir semua bahasa bertipe statis dan statically typed OOP.


Mengapa Butuh Custom Model Mapping?

Skenario berikut pasti tak asing:

  • API eksternal punya field yang aneh atau tak relevan
  • Nama field berbeda antara response eksternal dan domain internal
  • Field nullable di API, tapi di domain harus non-null
  • Logical transformation: status code “1/0” jadi “active/inactive”

Jika kita langsung memroses data di seluruh lapisan aplikasi, codebase akan jadi “fragile”—rapuh, rawan bug, dan sulit maintenance. Praktik baiknya: mapping data sedini mungkin ke model internal yang dirancang sesuai kebutuhan, lalu seluruh logic selanjutnya jalan dengan model internal ini.


Tabel Perbandingan Model Eksternal vs Model Internal

Mari ambil contoh konkret. Anggap kita punya service eksternal dengan response sbb (dalam JSON):

Field EksternalTipeSample ValueKeterangan
user_idint2024Sama dengan id user
namestr“Andi Online”Nama user
statint11 = aktif, 0 = non aktif, bisa null
is_adminboolfalseBenar/salah, opsional

Sedangkan struct internal kita dirancang sbb:

Field InternalTipeKeterangan
IDintUser ID
FullNamestringNama lengkap user
Statusstring“active” atau “inactive”
IsSuperAdminboolTrue hanya jika admin

Model Struct: Eksternal vs Internal

// Response dari API eksternal
type ExternalUser struct {
    UserID  int     `json:"user_id"`
    Name    string  `json:"name"`
    Stat    *int    `json:"stat"`     // Bisa null
    IsAdmin *bool   `json:"is_admin"` // Bisa null
}

// Struct internal aplikasi kita
type User struct {
    ID           int
    FullName     string
    Status       string // "active" atau "inactive"
    IsSuperAdmin bool
}

Flow Mapping: Diagram Alur Data

Mari visualisasikan proses mapping tipe dalam bentuk flowchart menggunakan mermaid:

flowchart TD
    A[Ambil data dari API eksternal] --> B{Parsing JSON ke ExternalUser}
    B --> C{Transform}
    C -->|Stat==1| D[Status="active"]
    C -->|Stat==0|null| E[Status="inactive"]
    C --> F{IsAdmin==true}
    F -->|ya| G[IsSuperAdmin=true]
    F -->|tidak| H[IsSuperAdmin=false]
    D & G --> I[Bentuk struct User]
    E & H --> I
    I --> J[Simpan/Proses User di aplikasi]

Alur ini memastikan data “kotor” eksternal, langsung bersih ketika masuk ke model internal aplikasi.


Custom Mapping: Cara Implementasi

Kita ingin membuat fungsi MapExternalUserToUser yang dapat dikustom sesuai aturan bisnis:

func MapExternalUserToUser(ext ExternalUser) User {
    status := "inactive"
    if ext.Stat != nil && *ext.Stat == 1 {
        status = "active"
    }

    isSuperAdmin := false
    if ext.IsAdmin != nil && *ext.IsAdmin {
        isSuperAdmin = true
    }

    return User{
        ID:           ext.UserID,
        FullName:     ext.Name,
        Status:       status,
        IsSuperAdmin: isSuperAdmin,
    }
}

Penjelasan singkat:

  • Mengamankan nilai Stat yang nullable (nil) supaya aman (default “inactive”)
  • Translasi field nama berbeda (misal, user_id ke ID, name ke FullName)
  • Default logic IsSuperAdmin jika nil

Simulasi Penggunaan: Kode & Testing

Bayangkan kita mendapat data dari endpoint eksternal seperti ini:

{
    "user_id": 2024,
    "name": "Andi Online",
    "stat": 1,
    "is_admin": null
}

Contoh kode mengkonsumsi dan mapping:

package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    // Simulasi response JSON eksternal
    data := []byte(`{"user_id":2024, "name":"Andi Online", "stat":1, "is_admin":null}`)
    var extUser ExternalUser
    json.Unmarshal(data, &extUser)

    mappedUser := MapExternalUserToUser(extUser)
    fmt.Printf("%+v\n", mappedUser)
    // Output: {ID:2024 FullName:Andi Online Status:active IsSuperAdmin:false}
}

Unit test implementasi mapping:

func TestMapExternalUserToUser(t *testing.T) {
    statVal := 1
    adminVal := false
    ext := ExternalUser{
        UserID:  1,
        Name:    "Ujang",
        Stat:    &statVal,
        IsAdmin: &adminVal,
    }
    u := MapExternalUserToUser(ext)
    if u.Status != "active" || u.IsSuperAdmin {
        t.Errorf("Mapping gagal: %+v", u)
    }
}

Patterns & Best Practice

  • Jangan mapping di seluruh kode. Sediakan satu lapisan mapping; lewati layer service/business hanya struct internal.
  • Selalu cover kasus null/unexpected. Contoh di atas gunakan pointer dan default value.
  • Gunakan unit test untuk handling edge-case pada mapping.
  • Jika kemungkinan berubah-ubah, split fungsi mapping per konteks, hindari god function.

Scaling: Mapping Banyak Data (Bulk Mapping)

Jika kita sering menerima dalam bentuk slice/array, gunakan helper:

func MapExternalUsers(exts []ExternalUser) []User {
    var users []User
    for _, e := range exts {
        users = append(users, MapExternalUserToUser(e))
    }
    return users
}

Mapping Library: Pakai Atau Tidak?

Ada library mapping otomatis (untuk Go misal: https://github.com/mitchellh/mapstructure, untuk Java: MapStruct). Tapi hati-hati: mapping kustom sering melibatkan logika domain, jadi mapping manual tetap solusi utama jika aturannya kompleks.


Studi Kasus: Error Handling Saat Mapping

Cek kondisi “bad data” dari eksternal, return error jika tidak map-able.

func MapExternalUserToUserV2(ext ExternalUser) (User, error) {
    if ext.UserID == 0 {
        return User{}, fmt.Errorf("user_id harus ada")
    }
    // lanjut mapping seperti biasa...
    ...
}

Kesimpulan

Mapping custom model, dari tipe eksternal ke struct sendiri, adalah pola fundamental dalam software engineering. Model decoupling seperti ini membebaskan aplikasi dari “beban warisan” data format eksternal, menjaga codebase tetap rapih, logis, dan mudah di-maintain. Sediakan fungsi mapping sedini mungkin, investasikan waktu di testing, dan atur edge-case dengan teliti. Dalam jangka panjang, model seperti ini pasti hemat biaya maintenance dibanding tempel data seenaknya di seluruh kode.


Sudahkah Anda punya “Custom Model Mapper” dalam aplikasi yang sedang Anda kerjakan hari ini?

comments powered by Disqus