# Design Patterns – Rahasia di Balik Arsitektur Software yang Elegan

# Pendahuluan

Di dunia pengembangan perangkat lunak modern, kita sering mendengar istilah *Design Patterns*.  
Namun, banyak developer yang masih berpikir bahwa ini hanya teori dari buku [*Gang of Four*](https://en.wikipedia.org/wiki/Design_Patterns#cite_note-copies-sold-1). Padahal, **design pattern adalah bahasa komunikasi antar engineer**, cara berpikir yang membuat kode kita konsisten, efisien, dan mudah dipelihara.

Tanpa pola desain yang tepat, aplikasi yang kita buat bisa cepat tumbuh menjadi **spaghetti code** — penuh duplikasi, sulit di-*debug*, dan tidak scalable.  
Artikel ini akan membahas konsep dasar *design patterns*, kategori utamanya, dan bagaimana kita bisa menerapkannya di ekosistem **Node.js** dengan contoh nyata.

# Apa itu Design Patterns?

Design paterns adalah solusi umum dan terbukti efektif untuk masalah berulang dalam desain perangkat lunak. Bukan kode siap pakai melainkan template berpikir yang bisa kita sesuaikan.

> 🧠 Analogi sederhananya:  
> “Pattern itu seperti resep masakan — bahan dan langkah bisa disesuaikan, tapi prinsipnya tetap sama.”

Tujuannya sederhana:

* Membuat kode lebih **terstruktur**.
    
* Meningkatkan **reusability** dan **maintainability**.
    
* Memperkuat komunikasi antar developer (“Kita pakai *Observer Pattern* ya” lebih jelas daripada menjelaskan 10 baris logika).
    

# Tiga Kategori Utama Design Patterns

1. Creational Patterns(Pola Penciptaan Objek)  
    Bagaimana objek di buat tanpa tergantung pada kelas tertentu.  
    Contoh:
    
    * Singleton
        
    * Factory method
        
    * Builder
        
2. Struktural Patterns (Pola Struktur)  
    Bagaimana menghubungkan objek/kelas agar lebih fleksibel.  
    Contoh:
    
    * Adapter
        
    * Decorator
        
    * Composite
        
    * Facade
        
3. Behavioral Patterns (Pola Perilaku)  
    Bagaimana objek berinteraksi dan berkomunikasi.  
    Contoh:
    
    * Observer
        
    * Strategy
        
    * Command
        
    * Chain of Responsibility
        

# Studi Kasus (Node.js)

Mari lihat bagaimana beberapa *design pattern* diterapkan dalam konteks **Node.js** — bahasa yang sangat dinamis dan berbasis event.

1. **Singleton Pattern – Koneksi Database**
    
    Memastikan hanya satu koneksi database dalam satu Aplikasi. Tanpa pattern ini sebenernya kamu bisa membuat koneksi baru setiap ada request tetapi memboroskan resource.
    
    ```javascript
    // database.js
    const mongoose = require('mongoose');
    
    let instance = null;
    
    class Database {
      constructor() {
        if (!instance) {
          mongoose.connect(process.env.MONGO_URI);
          console.log('✅ Database connected');
          instance = this;
        }
        return instance;
      }
    }
    
    module.exports = new Database();
    ```
    
    **Penjelasan:**
    
    * Pertama kali di-*require*, modul ini membuat koneksi database.
        
    * Pemanggilan berikutnya akan menggunakan instance yang sama.
        
    * Node.js *module caching system* memperkuat efek *singleton* ini secara alami.
        
2. **Factory Pattern – Pembuatan Service Dinamis**  
    Membuat objek tanpa menentukan kelas spesifik secara langsung.  
    Contoh: kita punya sistem pengiriman pesan yang bisa berubah tergantung channel (email, SMS, WhatsApp).
    
    ```javascript
    // messageFactory.js
    class EmailService {
      send(msg) { console.log(`📧 Email sent: ${msg}`); }
    }
    
    class SmsService {
      send(msg) { console.log(`📱 SMS sent: ${msg}`); }
    }
    
    class MessageFactory {
      static create(type) {
        if (type === 'email') return new EmailService();
        if (type === 'sms') return new SmsService();
        throw new Error('Unknown message type');
      }
    }
    
    module.exports = MessageFactory;
    
    // usage.js
    const MessageFactory = require('./messageFactory');
    const service = MessageFactory.create('sms');
    service.send('Hello from Node.js!');
    ```
    
    **Kelebihan:**
    
    * Kode lebih fleksibel — menambah channel baru cukup membuat kelas baru.
        
    * Memudahkan *dependency injection* dan pengujian unit.
        
3. **Facade Pattern – Penyederhanaan Modul Kompleks**
    
    Menyederhanakan akses ke sistem yang rumit. Misalnya, kamu punya sistem upload yang melibatkan validasi, resize, dan upload ke cloud.
    
    ```javascript
    // uploadFacade.js
    const fs = require('fs');
    const sharp = require('sharp');
    const cloud = require('./cloudService');
    
    class UploadFacade {
      static async uploadImage(filePath) {
        const valid = fs.existsSync(filePath);
        if (!valid) throw new Error('❌ File not found');
        
        const resized = await sharp(filePath).resize(800).toBuffer();
        return await cloud.upload(resized);
      }
    }
    
    module.exports = UploadFacade;
    
    // usage.js
    const UploadFacade = require('./uploadFacade');
    UploadFacade.uploadImage('./photo.png').then(console.log);
    ```
    
    **Kelebihan:**
    
    * Developer lain cukup memanggil `UploadFacade.uploadImage()`, tanpa tahu detail internal.
        
    * Meningkatkan keterbacaan dan *encapsulation*.
        
4. Observer Pattern - Event-Driven Architecture
    
    Node.js secara alami sudah menerapkan pola ini melalui `EventEmitter`. Cocok untuk sistem yang perlu bereaksi otomatis terhadap suatu event.
    
    ```javascript
    // observer.js
    const EventEmitter = require('events');
    const emitter = new EventEmitter();
    
    // Subscriber
    emitter.on('orderCreated', (data) => {
      console.log(`📦 New order received: ${data.id}`);
    });
    
    // Publisher
    function createOrder(order) {
      console.log('✅ Order created');
      emitter.emit('orderCreated', order);
    }
    
    // Usage
    createOrder({ id: 'ORD-001', total: 150000 });
    ```
    
    **Kelebihan:**
    
    * Memisahkan logika event dari aksi yang terjadi.
        
    * Cocok untuk microservices atau *event bus system*.
        

# Anti Pattern - Saat Design Pattern disalahgunakan

Tidak semua masalah butuh pattern. Beberapa developer sering *over-engineer* solusi dengan menambahkan lapisan-lapisan yang tidak perlu.

Contoh:  
Menggunakan Singgleton untuk semua service, padahal tidak ada kebutuhan untuk state global. Akibatnya sistem jadi sulit di test dan refactor.

> Prinsip Penting:  
> Gunakan design pattern untuk menyederhanakan bukan untuk memperumit.

# Best practices dalam menggunakan design patterns

1. Pilih pattern berdasarkan masalah nyata, bukan hanya terdengar keren.
    
2. Gunakan dokumentasi internal: tulis di readme atau architecture doc jika pattern tertentu digunakan.
    
3. Gabungkan pattern bila perlu, misalnya:
    
    * *Singleton + Facade* untuk service global seperti cache manager.
        
    * *Factory + Strategy* untuk sistem modular yang dinamis.
        

# Kesimpulan

*Design Patterns* bukan teori usang dari tahun 90-an — justru semakin relevan di era modern saat aplikasi tumbuh cepat dan kompleks.

Dalam Node.js, penerapan pattern seperti singleton, Factory, Observer, dan Facade membantu menjaga struktur arsitektur tetap bersih dan scalable.

> “Engineer hebat bukan yang menulis banyak kode, tapi yang tahu kapan dan bagaimana menulis kode dengan pola yang tepat.” — Safei Muslim

Sudahkah kamu menerapkan *Design Pattern* di proyek mu?  
Coba refactor satu modul menggunakan pendekatan ini — lihat bagaimana kode menjadi lebih rapi dan mudah dipelihara.

Kalau kamu punya pattern favorit atau pengalaman unik menggunakannya, bagikan di komentar — karena pola terbaik lahir dari pengalaman nyata 💡
