Dependency Inversion Principle (DIP)
Tags: go, solidprinciples
Published: 2023-09-08 04:32:29

Robert C. Martin menjelaskan:

> A. High level modules should not depend upon low level modules. both should depend upon abstractions.
> B. Abstractions should not depend upon details. details should depend upon abstractions.

Dave Cheney menjelaskan:

> The Dependency Inversion Principle encourages you move the knowledge of the things your package depends on from compile time–in Go we see this with a reduction in the number of import statements used by a particular package–to run time.

Mari kita lihat kode yang melanggar DIP, berdasarkan kode yang melanggar OCP sebelumnya:

```go
package main

import (
    "fmt"
)

// User adalah tipe data yang mewakili informasi pengguna.
type User struct {
    ID       int
    Username string
    Email    string
}

// UserRepository bertanggung jawab untuk berinteraksi dengan basis data pengguna.
type UserRepository struct{}

func (us *UserRepository) GetUserByUsername(username string) (User, error) {
    // Logika untuk mengambil pengguna berdasarkan username
    return User{}, nil
}

func (us *UserRepository) Save() error {
    // Logika untuk menyimpan ke basis data
    return nil
}

// UserValidator bertanggung jawab untuk memvalidasi data pengguna.
type UserValidator struct{}

func (uv *UserValidator) Validate() error {
    // Logika validasi pengguna seperti validasi email, kata sandi, dsb.
    return nil
}

// UserService adalah layanan yang berfungsi sebagai penghubung antara klien dan data pengguna.
type UserService struct {
    Repository UserRepository
    Validator  UserValidator
}

// CreateUser membuat pengguna baru dengan menggunakan Repository dan Validator.
func (us *UserService) CreateUser(user User) error {
    // Validasi pengguna menggunakan Validator
    if err := us.Validator.Validate(); err != nil {
        return err
    }

    // Simpan pengguna ke basis data menggunakan Repository
    if err := us.Repository.Save(); err != nil {
        return err
    }

    return nil
}

func (us *UserService) GetUserByID(userID int) (User, error) {
	// GetUserByID mengambil pengguna berdasarkan ID dari Repository.
    return us.Repository.GetUserByID(userID)
}

func main() {
    // Inisialisasi layanan pengguna
    userService := UserService{
        Repository: UserRepository{}, // Melanggar DIP
        Validator:  UserValidator{},
    }

    // Membuat pengguna baru
    newUser := User{ID: 1, Username: "john_doe", Email: "john@example.com"}
    err := userService.CreateUser(newUser)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    // Mengambil pengguna berdasarkan ID
    user, err := userService.GetUserByID(1)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    fmt.Println("User:", user)
}
```

Berdasarkan kode diatas terlihat bahwa modul tingkat atas (`UserService`) bergantung kepada modul tingkat bawah (`UserRepository`). Untuk mematuhi DIP, `UserService` harus bergantung pada abstraksi (`interface`), dan `UserRepository` harus mengimplementasikan interface tersebut. Mari kita lihat kode yang mematuhi DIP, berdasarkan kode yang mematuhi OCP sebelumnya:

```go
package main

import (
	"fmt"
)

// User adalah tipe data yang mewakili informasi pengguna.
type User struct {
	ID       int
	Username string
	Email    string
}

type UserRepository interface {
	GetUserByUsername(username string) (User, error)
	Save() error
}

// UserRepository bertanggung jawab untuk berinteraksi dengan basis data pengguna.
type UserMySQLRepository struct{}

func (r *UserMySQLRepository) GetUserByUsername(username string) (User, error) {
	// Logika untuk mengambil pengguna berdasarkan username
	return User{}, nil
}

func (r *UserMySQLRepository) Save() error {
	// Logika untuk menyimpan ke basis data
	return nil
}

// UserValidator bertanggung jawab untuk memvalidasi data pengguna.
type UserValidator struct{}

func (uv *UserValidator) Validate() error {
	// Logika validasi pengguna seperti validasi email, kata sandi, dsb.
	return nil
}

// UserService adalah layanan yang berfungsi sebagai penghubung antara klien dan data pengguna.
type UserService struct {
	Repository UserRepository
	Validator  UserValidator
}

// CreateUser membuat pengguna baru dengan menggunakan Repository dan Validator.
func (us *UserService) CreateUser(user User) error {
	// Validasi pengguna menggunakan Validator
	if err := us.Validator.Validate(); err != nil {
		return err
	}

	// Simpan pengguna ke basis data menggunakan Repository
	if err := us.Repository.Save(); err != nil {
		return err
	}

	return nil
}

// GetUserByUsername mengambil pengguna berdasarkan username.
func (us *UserService) GetUserByUsername(username string) (User, error) {
	return us.Repository.GetUserByUsername(username)
}

func main() {
	// Inisialisasi layanan pengguna
	userService := UserService{
		Repository: &UserMySQLRepository{}, // Contoh sederhana, seharusnya menggunakan koneksi basis data nyata.
		Validator:  UserValidator{},        // Contoh sederhana, seharusnya ada validasi yang lebih lengkap.
	}

	// Membuat pengguna baru
	newUser := User{ID: 1, Username: "john_doe", Email: "john@example.com"}
	err := userService.CreateUser(newUser)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Mengambil pengguna berdasarkan username
	user, err := userService.GetUserByUsername("myUser")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println("User:", user)
}
```

Kode diatas telah mematuhi DIP, karena `UserService` telah bergantung pada interface `UserRepository`.

## Referensi
- https://dave.cheney.net/2016/08/20/solid-go-design
- https://web.archive.org/web/20150905081103/http://www.objectmentor.com/resources/articles/dip.pdf