Single Responsibility Principle (SRP)
Tags: go, solidprinciples
Published: 2023-09-08 04:30:18

Robert C. Martin menjelaskan:
> Gather together the things that change for the same reasons. Separate those things that change for different reasons.

Pada artikel yang sama, Robert C. Martin juga mengungkapkan penjelasan lain dari SRP:

> Each software module should have one and only one reason to change.

Sedangkan menurut Dave Cheney:
> The Single Responsibility Principle encourages you to structure the functions, types, and methods into packages that exhibit natural cohesion; the types belong together, the functions serve a single purpose.

Lalu, apa saja alasan untuk berubah? Alasan untuk berubah merujuk kepada aspek atau tanggung jawab tertentu dari sebuah kelas atau modul yang mungkin memerlukan modifikasi atau pembaruan. Contohnya:

Dalam konteks Prinsip Tanggung Jawab Tunggal (Single Responsibility Principle atau SRP), berikut adalah beberapa contoh alasan yang dapat menyebabkan perubahan pada suatu kelas atau modul:

1. **Perubahan Fungsionalitas Utama:**
   
   Kebutuhan bisnis berubah atau berkembang, dan kelas perlu menangani fitur baru atau mengubah cara fungsi utamanya bekerja.

2. **Perbaikan Bug:**
   
   Kesalahan atau masalah yang tidak diinginkan ditemukan dalam kelas, dan perlu diperbaiki untuk menjaga kestabilan sistem.

3. **Optimisasi Kinerja:**
   
   Kinerja kelas atau modul tidak memadai dan perlu dioptimalkan untuk meningkatkan responsivitas atau efisiensi.

4. **Pembaruan Keamanan:**
   
   Ditemukan kerentanan keamanan atau ancaman keamanan baru yang memerlukan perbaikan atau peningkatan keamanan.

5. **Pengembangan Fitur Baru:**
   
   Kebutuhan untuk menambahkan fitur baru ke dalam sistem yang memerlukan penambahan logika atau fungsionalitas tambahan.

6. **Perubahan Logika Bisnis:**
   
   Logika bisnis yang ada dalam kelas perlu diubah karena aturan atau kebijakan bisnis berubah.

7. **Pengoptimalan Kode:**
   
   Kode yang ada perlu diubah untuk memperbaiki struktur atau gaya koding yang buruk agar lebih mudah dipelihara.

8. **Integrasi Eksternal:**
   
   Perubahan dalam sumber data eksternal atau API yang diintegrasikan dengan kelas memerlukan penyesuaian.

9. **Pemeliharaan Infrastruktur:**
   
   Perubahan dalam platform atau lingkungan infrastruktur (seperti database atau sistem operasi) yang mempengaruhi cara kelas berinteraksi dengan komponen eksternal.

10. **Pembaruan Kebijakan atau Regulasi:**
    
	Adanya perubahan dalam kebijakan internal perusahaan atau regulasi eksternal yang memerlukan penyesuaian dalam kelas.

11. **Perbaikan Performa:**
    
	Kinerja kelas atau modul yang tidak memadai memerlukan perbaikan untuk mengurangi beban atau waktu pemrosesan.

12. **Pemeliharaan dan Perbaikan Rutin:**
    
	Perubahan berkala atau pemeliharaan yang diperlukan untuk menjaga kualitas dan keberlanjutan kode.

Agar lebih jelas, coba perhatikan kode berikut:

```go
package main

import (
	"fmt"
)

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

// UserService adalah layanan yang mencoba untuk melakukan beberapa tanggung jawab yang berbeda.
type UserService struct {
	// Koneksi ke basis data atau sumber data lainnya.
	// ...
}

// CreateUserAndValidate membuat pengguna baru dan juga melakukan validasi pada User.
func (us *UserService) CreateUserAndValidate(user User) error {
	// Validasi pengguna
	if err := us.validateUser(user); err != nil {
		return err
	}

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

	return nil
}

// GetUserByIDAndValidate mengambil pengguna berdasarkan ID dan juga melakukan validasi.
func (us *UserService) GetUserByIDAndValidate(userID int) (User, error) {
	// Mengambil pengguna dari basis data
	user, err := us.getUserByID(userID)
	if err != nil {
		return User{}, err
	}

	// Validasi pengguna
	if err := us.validateUser(user); err != nil {
		return User{}, err
	}

	return user, nil
}

func (us *UserService) validateUser(user User) error {
	// Logika validasi pengguna seperti validasi email, kata sandi, dsb.
	// ...
	return nil
}

func (us *UserService) saveUser(user User) error {
	// Simpan pengguna ke basis data
	// ...
	return nil
}

func (us *UserService) getUserByID(userID int) (User, error) {
	// Mengambil pengguna dari basis data
	// ...
	return User{}, nil
}

func main() {
	// Inisialisasi layanan pengguna
	userService := UserService{} // Contoh sederhana, seharusnya menggunakan koneksi basis data nyata.

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

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

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

Dalam contoh ini, `UserService` mencoba untuk melakukan banyak tanggung jawab, termasuk validasi pengguna, penyimpanan pengguna, dan pengambilan pengguna dari basis data. Ini melanggar SRP karena satu komponen seharusnya hanya memiliki satu alasan untuk berubah. Dalam kasus ini, jika salah satu tanggung jawab berubah, itu dapat mempengaruhi fungsionalitas lainnya dalam kelas yang sama. Menggunakan pendekatan yang tidak mematuhi SRP dapat membuat kode menjadi lebih sulit dipahami, dipelihara, dan diuji.

Perhatikan kode berikutnya.

```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
}

// 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: UserRepository{}, // 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 ID
	user, err := userService.GetUserByUsername("myUser")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

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

Pada contoh di atas, tanggung jawab dipisah menjadi tiga bagian:

1. `UserRepository`: Bertanggung jawab untuk berinteraksi dengan basis data atau sumber data lainnya.
2. `UserValidator`: Bertanggung jawab untuk memvalidasi data pengguna.
3. `UserService`: Bertanggung jawab untuk menghubungkan antara klien (penggunaan dalam main) dengan Repository dan Validator. Ini adalah entitas yang berfungsi sebagai penghubung antara berbagai komponen yang berbeda.

Menggunakan pendekatan ini, setiap komponen memiliki satu alasan untuk berubah dan dapat dikembangkan, diuji, dan dipelihara secara independen. Hal ini mempermudah untuk memahami, memodifikasi, dan memperbaiki setiap bagian dari kode.

## Referensi
- https://web.archive.org/web/20150202200348/http://www.objectmentor.com/resources/articles/srp.pdf
- https://en.wikipedia.org/wiki/Single-responsibility_principle
- https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html
- https://faun.pub/implementing-solid-principles-in-golang-a67d0bf04833
- https://hackernoon.com/understanding-solid-principles-in-javascript-w1cx3yrv
- https://dave.cheney.net/2016/08/20/solid-go-design