SoC #
Separation of Concerns (SoC) adalah salah satu prinsip fundamental dalam software engineering yang menekankan bahwa sebuah sistem harus dipisahkan ke dalam bagian-bagian (concerns) yang masing-masing menangani satu tanggung jawab spesifik. Setiap bagian fokus pada apa yang menjadi urusannya sendiri, tanpa mencampuri urusan bagian lain.
Prinsip ini pertama kali diperkenalkan oleh Edsger W. Dijkstra, dan hingga hari ini menjadi fondasi bagi berbagai konsep modern seperti clean architecture, layered architecture, microservices, dan bahkan framework web populer.
Secara sederhana:
Satu modul, satu tanggung jawab utama.
Apa yang Dimaksud dengan “Concern”? #
Concern adalah aspek atau tanggung jawab tertentu dalam sebuah aplikasi. Contoh concern dalam aplikasi backend:
- Logika bisnis (business rules)
- Akses database
- HTTP handling (request/response)
- Validasi input
- Autentikasi & otorisasi
- Logging
- Configuration
Masalah umum muncul ketika semua concern ini dicampur dalam satu tempat, misalnya satu function HTTP handler yang:
- parsing request
- validasi
- query database
- logika bisnis
- formatting response
Inilah yang ingin dihindari oleh SoC.
Tujuan dan Manfaat Separation of Concerns #
Maintainability #
Perubahan pada satu concern tidak merusak bagian lain.
Readability #
Kode lebih mudah dibaca karena setiap bagian punya fokus jelas.
Testability #
Business logic dapat diuji tanpa HTTP server atau database sungguhan.
Reusability #
Concern yang terpisah bisa digunakan ulang di konteks lain.
Scalability (Secara Tim & Kode) #
Tim bisa bekerja paralel pada concern yang berbeda.
SoC vs Prinsip Lain #
SoC sering berkaitan dengan prinsip lain:
- SRP (Single Responsibility Principle) → fokus pada class/module
- SoC → fokus pada pemisahan aspek sistem secara keseluruhan
- Layered Architecture → salah satu implementasi SoC
SoC adalah konsep besar, SRP adalah penerapannya di level lebih kecil.
Contoh Buruk: Tanpa Separation of Concerns (Golang) #
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
// parsing request
var req CreateUserRequest
json.NewDecoder(r.Body).Decode(&req)
if req.Email == "" {
http.Error(w, "email required", http.StatusBadRequest)
return
}
// business logic
hashedPassword := hashPassword(req.Password)
// database logic
db.Exec("INSERT INTO users(email, password) VALUES (?, ?)", req.Email, hashedPassword)
// response
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
Masalah: #
- HTTP logic bercampur dengan business logic
- Sulit di-test tanpa database
- Sulit digunakan ulang di konteks lain (CLI, worker, dll)
Contoh Baik: Penerapan SoC dengan Layered Approach #
Kita pisahkan concern menjadi beberapa layer:
/handler -> HTTP layer
/service -> business logic
/repository -> database access
/domain -> entity & rule
Domain Layer (Entity) #
// domain/user.go
package domain
type User struct {
ID int64
Email string
Password string
}
Concern:
- Representasi data dan aturan inti
Repository Layer (Database Concern) #
// repository/user_repository.go
package repository
import "database/sql"
type UserRepository interface {
Save(user *domain.User) error
}
type userRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) UserRepository {
return &userRepository{db: db}
}
func (r *userRepository) Save(user *domain.User) error {
_, err := r.db.Exec(
"INSERT INTO users(email, password) VALUES (?, ?)",
user.Email,
user.Password,
)
return err
}
Concern:
- Hanya tahu cara menyimpan data
- Tidak tahu HTTP atau business rules
Service Layer (Business Logic Concern) #
// service/user_service.go
package service
type UserService interface {
CreateUser(email, password string) error
}
type userService struct {
repo repository.UserRepository
}
func NewUserService(repo repository.UserRepository) UserService {
return &userService{repo: repo}
}
func (s *userService) CreateUser(email, password string) error {
if email == "" {
return errors.New("email is required")
}
hashed := hashPassword(password)
user := &domain.User{
Email: email,
Password: hashed,
}
return s.repo.Save(user)
}
Concern:
- Aturan bisnis
- Tidak tahu HTTP atau database detail
Handler Layer (HTTP Concern) #
// handler/user_handler.go
package handler
type UserHandler struct {
service service.UserService
}
func NewUserHandler(s service.UserService) *UserHandler {
return &UserHandler{service: s}
}
func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
json.NewDecoder(r.Body).Decode(&req)
err := h.service.CreateUser(req.Email, req.Password)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
Concern:
- HTTP request/response
- Tidak tahu detail hashing atau SQL
Dampak Positif dari SoC #
Unit Test Lebih Mudah #
func TestCreateUser(t *testing.T) {
repo := new(MockUserRepository)
service := NewUserService(repo)
err := service.CreateUser("[email protected]", "password")
assert.NoError(t, err)
}
Tanpa HTTP server, tanpa database sungguhan.
SoC di Dunia Nyata #
SoC tidak hanya soal folder atau package, tapi soal batas tanggung jawab:
- Controller vs Service vs Repository
- API Gateway vs Backend Service
- Service A (Auth) vs Service B (Payment)
- Synchronous API vs Async Worker
Di arsitektur microservices, SoC sering diterjemahkan menjadi service-level separation.
Kesalahan Umum Saat Menerapkan SoC #
- Over-engineering (terlalu banyak layer tanpa kebutuhan)
- Menganggap SoC = banyak folder
- Business logic bocor ke handler
- Repository berisi logic bisnis
SoC harus proporsional dengan kompleksitas sistem.
Kapan SoC Sangat Penting? #
- Aplikasi jangka panjang
- Tim besar
- Sistem dengan perubahan bisnis cepat
- Aplikasi dengan banyak integrasi
Untuk aplikasi kecil, SoC bisa diterapkan secara bertahap.
Kesimpulan #
Separation of Concerns adalah prinsip inti yang membantu kita membangun sistem:
- Lebih rapi
- Mudah diuji
- Mudah dikembangkan
- Tahan terhadap perubahan
Dalam Golang, SoC sangat natural diterapkan melalui package, interface, dan dependency injection sederhana.
Pisahkan concern, bukan sekadar file.