Software Architecture 10 Mei 2026 10 Menit Baca MAYASTRA NAWALOKA

Implementasi Konkret SOLID Principles: Mencegah Spageti Code Pada Aplikasi Web Skala Besar

Seiring berkembangnya kompleksitas fitur aplikasi web skala perusahaan, ketiadaan batasan abstraksi yang jelas akan menyebabkan akumulasi spageti code. Memahami dan menerapkan lima prinsip SOLID secara praktis adalah kunci menjaga skalabilitas basis kode.

Evolusi Kode dari Prototip Menuju Spageti Code

Pada fase awal pembuatan perangkat lunak, fokus utama tim rekayasa biasanya tertuju pada kecepatan penyelesaian fitur (time-to-market). Pengembang cenderung menulis kelas atau fungsi monolith yang menangani banyak logika sekaligus: mulai dari validasi input, pengolahan transaksi bisnis, kueri ke basis data, hingga pengiriman notifikasi ke layanan pihak ketiga.

Ketika basis kode tersebut tumbuh dari ratusan menjadi puluhan ribu baris, tim akan mulai mengalami hambatan serius. Perubahan kecil pada satu modul berpotensi merusak modul lain secara tak terduga (fragile code). Inilah gejala klasik dari spageti code. Penerapan prinsip SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, dan Dependency Inversion) hadir sebagai dasar arsitektur untuk memecah keterikatan tinggi (tight coupling) tersebut.

"Arsitektur perangkat lunak yang baik bukan ditentukan oleh kerumitan framework yang digunakan, melainkan oleh kejelasan batasan tanggung jawab dan kelonggaran keterikatan antar komponen di dalam sistem."

MAYASTRA NAWALOKA, Principal Solutions Architect

1. Single Responsibility Principle (SRP)

Prinsip SRP menyatakan bahwa sebuah kelas atau modul hanya boleh memiliki satu alasan untuk berubah (one reason to change). Artinya, kelas tersebut hanya bertanggung jawab atas satu domain tugas bisnis tertentu.

TypeScript Refactoring: SRP Violation vs Clean SRP
// ANTI-PATTERN: Pelanggaran SRP (Satu kelas menangani database, bisnis, dan email)
class OrderProcessorBad {
    async processOrder(orderId: string) {
        // 1. Kueri Database
        const order = await db.query('SELECT * FROM orders WHERE id = ?', [orderId]);
        // 2. Logika Bisnis
        const total = order.items.reduce((sum, item) => sum + item.price, 0);
        // 3. Pengiriman Email
        const transporter = nodemailer.createTransport(...);
        await transporter.sendMail({ to: order.userEmail, subject: 'Order Processed' });
    }
}

// CLEAN PATTERN: Menerapkan SRP dengan Pemisahan Tanggung Jawab
class OrderRepository {
    async findById(orderId: string): Promise<Order> {
        return db.orders.findUnique({ where: { id: orderId } });
    }
}

class NotificationService {
    async sendOrderConfirmation(email: string, total: number): Promise<void> {
        await mailClient.send({ to: email, subject: `Order Total: ${total}` });
    }
}

class OrderProcessorClean {
    constructor(
        private readonly repo: OrderRepository,
        private readonly notifier: NotificationService
    ) {}

    async processOrder(orderId: string): Promise<void> {
        const order = await this.repo.findById(orderId);
        const total = order.calculateTotal();
        await this.notifier.sendOrderConfirmation(order.userEmail, total);
    }
}

2. Open/Closed Principle (OCP)

Prinsip OCP menegaskan bahwa komponen perangkat lunak harus terbuka untuk ekstensi (open for extension), tetapi tertutup untuk modifikasi (closed for modification). Kita harus dapat menambahkan fitur baru tanpa perlu mengubah kode sumber yang telah diuji dan berjalan di lingkungan produksi.

Penggunaan Pola Desain Strategi (Strategy Pattern) atau Polimorfisme merupakan pendekatan paling efektif untuk menerapkan OCP:

TypeScript Implementation: Polymorphic Payment Strategy (OCP)
// Interface Publik Tertutup untuk Modifikasi
interface PaymentProcessor {
    processPayment(amount: number): Promise<boolean>;
}

// Ekstensi Strategi Pembayaran 1: Credit Card
class CreditCardProcessor implements PaymentProcessor {
    async processPayment(amount: number): Promise<boolean> {
        console.log(`Processing credit card payment of $${amount}`);
        return true;
    }
}

// Ekstensi Strategi Pembayaran 2: E-Wallet (Dapat ditambahkan tanpa mengubah PaymentService)
class EWalletProcessor implements PaymentProcessor {
    async processPayment(amount: number): Promise<boolean> {
        console.log(`Processing e-wallet payment of $${amount}`);
        return true;
    }
}

// Layer Layanan yang Tertutup Modifikasi
class PaymentService {
    constructor(private readonly processor: PaymentProcessor) {}

    async checkout(amount: number): Promise<boolean> {
        return this.processor.processPayment(amount);
    }
}

3. Liskov Substitution Principle (LSP)

Prinsip LSP mewajibkan bahwa objek dari subkelas harus dapat menggantikan objek dari superkelas tanpa merusak kebenaran program. Subkelas tidak boleh mengubah perilaku yang diharapkan oleh pemanggil atau melempar pengecualian (exception) yang tidak terduga.

4. Interface Segregation Principle (ISP)

Prinsip ISP menekankan bahwa klien tidak boleh dipaksa untuk bergantung pada antarmuka (interface) yang tidak mereka gunakan. Daripada membuat satu antarmuka raksasa dengan puluhan method, lebih baik memecahnya menjadi beberapa antarmuka yang spesifik dan fokus.

5. Dependency Inversion Principle (DIP)

Prinsip DIP menyatakan bahwa modul tingkat tinggi (high-level modules) tidak boleh bergantung pada modul tingkat rendah (low-level modules). Keduanya harus bergantung pada abstraksi. Selain itu, abstraksi tidak boleh bergantung pada detail, melainkan detail yang harus bergantung pada abstraksi.

TypeScript Dependency Inversion Architecture
// Abstraksi Modul Database
interface UserDataStore {
    getUserById(id: string): Promise<User>;
}

// Implementasi Detail Tingkat Rendah (PostgreSQL)
class PostgresDataStore implements UserDataStore {
    async getUserById(id: string): Promise<User> {
        return pgPool.query('SELECT * FROM users WHERE id = $1', [id]);
    }
}

// Modul Tingkat Tinggi Bergantung Pada Abstraksi UserDataStore
class UserService {
    constructor(private readonly dataStore: UserDataStore) {}

    async fetchUserProfile(id: string): Promise<UserProfile> {
        const user = await this.dataStore.getUserById(id);
        return new UserProfile(user);
    }
}

Perbandingan Indikator Rekayasa Sebelum & Sesudah SOLID

Tabel berikut memperlihatkan dampak nyata penerapan kelima prinsip SOLID terhadap kualitas perangkat lunak perusahaan:

Metrik Kualitas Kode Sebelum SOLID (Monolith Spageti) Sesudah SOLID (Abstraksi Clean)
Coupling & Cohesion Tight Coupling / Low Cohesion Loose Coupling / High Cohesion
Unit Testability Sangat Sulit (Membutuhkan DB Mock Kompleks) Sangat Mudah (Interface Injection Mocking)
Risiko Regresi Bug Tinggi (Efek Samping Antar Fitur) Sangat Rendah (Modul Terisolasi Ketat)
Kecepatan Onboarding Developer Lambat (Perlu Memahami Seluruh Sistem) Cepat (Cukup Memahami Kontrak Modul)

Kesimpulan & Takeaway Arsitektur Kode

  • Gunakan SRP untuk memastikan setiap kelas memiliki tanggung jawab tunggal yang jelas dan fokus.
  • Terapkan OCP dan DIP untuk memisahkan logika bisnis utama dari implementasi pustaka atau database pihak ketiga.
  • Prinsip SOLID bukan sekadar aturan teoretis, melainkan perlindungan utama terhadap degradasi basis kode aplikasi skala besar.
MAYASTRA NAWALOKA Logo

MAYASTRA NAWALOKA

Software Engineering & System Architecture Expert

Spesialis rekayasa perangkat lunak berdedikasi tinggi dalam membangun platform web berkinerja tinggi, sistem terdistribusi, dan arsitektur backend yang scalable untuk perusahaan modern.

Membutuhkan Audit Arsitektur Atau Refactoring Kode Aplikasi Anda?

Tim engineer MAYASTRA NAWALOKA siap membantu Anda merancang, mengoptimalkan, dan mengimplementasikan solusi software engineering tingkat tinggi untuk bisnis Anda.

Konsultasi Proyek Bersama Kami