UI vs. UX: What’s the difference?

Clean Architecture di Node.js: Apa dan Bagaimana?

“Kok aplikasi Node.js-ku makin lama makin nggak teratur, ya?”
Pernah merasa begitu? Tenang, kamu tidak sendiri. Banyak developer—terutama pemula—mengalami hal yang sama saat aplikasinya mulai tumbuh. Awalnya sederhana, tapi lama-lama struktur foldernya jadi berantakan, logika bisnis tercampur dengan hal teknis, dan akhirnya bikin pusing sendiri.
Nah, kabar baiknya: semua ini bisa dicegah dengan pendekatan yang disebut Clean Architecture.

Artikel ini akan membahas secara rinci dan mudah dipahami tentang:

  • Apa itu Clean Architecture
  • Kenapa penting diterapkan
  • Bagaimana mengimplementasikannya di proyek Node.js (untuk pemula)

Apa Itu Clean Architecture?

Clean Architecture adalah arsitektur perangkat lunak yang memisahkan kode menjadi beberapa lapisan (layer) untuk menjaga agar:

  • Kode tetap modular
  • Bisa diuji dengan mudah (testable)
  • Mudah dipelihara dan dikembangkan

Konsep ini dipopulerkan oleh Uncle Bob (Robert C. Martin) dalam bukunya Clean Architecture. Intinya: “bisnis logikamu tidak boleh tergantung pada detail teknis seperti database, framework, atau UI.”

Struktur Dasar Clean Architecture

Clean Architecture biasanya dibagi menjadi 4 layer:

1. Entities (Entitas)

  • Berisi business rules atau logika inti aplikasi.
  • Tidak tergantung pada library atau framework apa pun.
  • Contoh: struktur data User, aturan validasi, dll.

2. Use Cases / Interactors

  • Berisi logika aplikasi (aturan spesifik tentang apa yang aplikasi lakukan).
  • Mengatur alur proses antara entitas dan luarannya.

3. Interface Adapters

  • Menjembatani antara use cases dengan hal-hal eksternal seperti database, API, atau UI.
  • Di sini tempatnya controller, presenter, dan repository interface.

4. Frameworks and Drivers

  • Bagian paling luar: Express, MongoDB, PostgreSQL, dan library lainnya.
  • Semua "detail teknis" disimpan di sini.

Contoh Struktur Folder Clean Architecture di Node.js

src/
├── entities/
│   └── User.js
├── usecases/
│   └── RegisterUser.js
├── adapters/
│   ├── controllers/
│   │   └── RegisterController.js
│   ├── repositories/
│   │   ├── UserRepository.js (interface)
│   │   └── MongoUserRepository.js (implementasi)
├── frameworks/
│   ├── express/
│   │   └── server.js
│   └── database/
│       └── mongo.js

Implementasi Sederhana: Register User

1. Entities/User.js

class User {
  constructor({ name, email }) {
    if (!email.includes('@')) throw new Error('Invalid email');
    this.name = name;
    this.email = email;
  }
}

module.exports = User;

2. Usecases/RegisterUser.js

module.exports = function RegisterUser(userRepo) {
  return async function (userData) {
    const user = new (require('../entities/User'))(userData);
    const existing = await userRepo.findByEmail(user.email);
    if (existing) throw new Error('User already exists');
    return await userRepo.save(user);
  };
};

3. Adapters/Repositories/UserRepository.js (Interface)

module.exports = class UserRepository {
  async findByEmail(email) {}
  async save(user) {}
};

4. Adapters/Repositories/MongoUserRepository.js

const UserRepo = require('./UserRepository');

class MongoUserRepository extends UserRepo {
  constructor(db) {
    super();
    this.db = db;
  }

  async findByEmail(email) {
    return this.db.collection('users').findOne({ email });
  }

  async save(user) {
    return this.db.collection('users').insertOne(user);
  }
}

module.exports = MongoUserRepository;

5. Adapters/Controllers/RegisterController.js

module.exports = function RegisterController(registerUserUseCase) {
  return async function (req, res) {
    try {
      const result = await registerUserUseCase(req.body);
      res.status(201).json({ success: true, data: result });
    } catch (err) {
      res.status(400).json({ error: err.message });
    }
  };
};

6. Frameworks/Express/server.js

const express = require('express');
const { MongoClient } = require('mongodb');
const MongoUserRepository = require('../../adapters/repositories/MongoUserRepository');
const RegisterUser = require('../../usecases/RegisterUser');
const RegisterController = require('../../adapters/controllers/RegisterController');

const app = express();
app.use(express.json());

(async () => {
  const client = await MongoClient.connect('mongodb://localhost:27017');
  const db = client.db('cleanarch');
  const userRepo = new MongoUserRepository(db);
  const registerUser = RegisterUser(userRepo);

  app.post('/register', RegisterController(registerUser));

  app.listen(3000, () => console.log('Server running at http://localhost:3000'));
})();

 

Kenapa Clean Architecture Layak Dipakai?

  •  Mudah dirawat
  • Terpisah dengan baik antara logika dan teknologi
  • Gampang diganti: Mau pindah dari MongoDB ke PostgreSQL? Tinggal ganti implementasi repo!
  • Cocok untuk tim besar atau proyek jangka panjang
  • Siap untuk diuji unit test secara independen

 

Tantangan Clean Architecture

  • Terlihat rumit di awal, apalagi untuk proyek kecil
  • Perlu disiplin dalam pemisahan tanggung jawab
  • Banyak file dan folder yang terlihat “berlebihan”

Tapi percayalah: di proyek besar, kamu akan sangat bersyukur memakainya.

 

Kesimpulan

Clean Architecture bukan sekadar gaya struktur folder, tapi cara berpikir: memisahkan logika aplikasi dari hal-hal teknis agar lebih fleksibel, teruji, dan mudah berkembang. Meski awalnya terasa rumit, tapi dengan latihan, kamu akan terbiasa.