DEBI PRAHARADIKA
← Back to Blog Index
System Design2026-05-2610 min read

Membangun Collaborative Whiteboard Real-Time: Arsitektur, UML, ERD, dan Kode

Panduan rekayasa sistem mendalam untuk merancang kanvas kolaborasi digital multi-user berlatensi ultra-rendah dengan sinkronisasi WebSockets, database ERD, dan diagram sekuens UML.

Membangun papan tulis digital kolaboratif (Collaborative Whiteboard) seperti Miro, FigJam, atau Excalidraw merupakan salah satu studi kasus rekayasa sistem (System Engineering) yang sangat menantang. Aplikasi jenis ini tidak hanya menuntut pertukaran data berlatensi sangat rendah, tetapi juga memerlukan presisi rendering grafis, sinkronisasi koordinat multi-user, dan manajemen state dokumen yang dinamis.

Untuk mewujudkannya dengan performa stabil, kita harus menggabungkan teknik manipulasi HTML5 Canvas, arsitektur WebSockets Gateway Room-Based, serta optimasi penyimpanan data koordinat.

Di dalam artikel ini, kita akan membongkar arsitektur sistemnya, menganalisis skema ERD & UML Sequence, serta mengimplementasikan kode server-client whiteboard kolaboratif siap pakai.


Arsitektur Sistem Kolaborasi (High-Availability System Design)

Untuk melayani ribuan kolaborator yang menggambar secara bersamaan, kita harus merancang sistem berbasis peristiwa (event-driven) yang terdistribusi secara horizontal. Berikut adalah desain arsitekturnya:

Arsitektur Sistem Collaborative Whiteboard

Alur System Thinking Kerja Arsitektur:

  1. HTML5 Canvas: Sisi klien menangkap input mouse/stylus secara lokal, menggambarkannya secara instan ke layar menggunakan koordinat 2D, lalu mereduksi frekuensi pengiriman koordinat agar tidak membanjiri jaringan.
  2. Load Balancer (NGINX / AWS ALB): Meneruskan jabat tangan WebSocket ke node gerbang (Gateway Nodes) aktif.
  3. Redis Adapter & Pub/Sub Event Bus: Menghubungkan beberapa instance server Node.js secara horizontal. Jika User A menggambar di kanvas yang terhubung ke Server Node 1, Redis Adapter akan menyebarkan drawing events tersebut ke Server Node 2 agar diteruskan ke User B secara real-time.
  4. Active Session Storage (Redis Cache): Menyimpan koordinat gambar aktif (in-flight drawing data) dan status kehadiran user (user presence) di RAM demi akses berkecepatan mikrodetik.
  5. Write-Behind Worker (Asynchronous Persistence): Secara berkala (misal 5-10 detik sekali) mengambil data coretan dari Redis Cache dan menuliskan perubahannya secara massal (bulk write) ke database relasional PostgreSQL atau dokumen MongoDB agar koneksi WebSocket tidak tertahan oleh proses antrean I/O database.

Perancangan UML Sequence & Skema Database ERD

Berikut adalah gabungan diagram alur sekuens pergerakan data koordinat (UML Sequence Flow) dan cetak biru relasi tabel database (ERD Database Schema):

UML Sequence & ERD Collaborative Whiteboard

1. UML Sequence Flows

Alur di atas menggambarkan urutan kejadian saat Client 1 menggambar coretan baru:

  • User menekan dan menggerakkan mouse di area Canvas.
  • Client App menangkap peristiwa (capture event), merender coretan tersebut secara instan di kanvas lokal (untuk menghilangkan persepsi latensi), lalu memaketkan data koordinat tersebut dalam format JSON untuk dikirim ke WebSocket Gateway.
  • Server WebSocket menerima muatan payload, memeriksa otorisasi room ID, dan langsung menyiarkan (broadcast) peristiwa gambar tersebut ke seluruh kolaborator aktif lainnya di room yang sama.
  • Client 2 menerima paket pembaruan (update event), lalu merender lintasan garis (path) tersebut di atas kanvas mereka secara real-time.

2. ERD Database Schema

  • USER: Menyimpan profil kolaborator dasar.
  • BOARD: Entitas utama representasi papan tulis kolaborasi. Satu user dapat memiliki dan mengelola banyak papan tulis (1:N).
  • DRAWINGPATH: Menyimpan coretan berbentuk garis bebas (freehand lines). Data koordinat disimpan dalam kolom PointData bertipe data JSON atau BLOB (berupa deretan array koordinat [x, y]). Ini jauh lebih efisien dibanding menyimpan ribuan baris koordinat terpisah di database relasional.
  • SHAPE: Menyimpan entitas geometris standar seperti persegi panjang (rectangle), lingkaran (circle), atau garis lurus (line) beserta dimensi ukuran dan warna garis/isi untuk kemudahan pemindahan posisi (drag-and-drop).

Implementasi Kode Papan Tulis Kolaboratif

Mari kita bangun prototipe fungsional papan tulis kolaboratif berbasis WebSocket menggunakan HTML5 Canvas (Frontend) dan Node.js (Backend).

1. Sisi Server (Node.js & Library ws)

Server ini bertanggung jawab mengelompokkan klien ke dalam Room papan tulis dan menyebarkan koordinat gambar ke pengguna lain secara efisien.

const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });

// Menyimpan daftar room aktif dan koneksi di dalamnya
const rooms = new Map(); // Key: roomId, Value: Set of ws connections

wss.on('connection', (ws, req) => {
  // Parsing parameters room dari URL, misal: ws://localhost:8080?roomId=dev-brainstorm
  const urlParams = new URLSearchParams(req.url.split('?')[1]);
  const roomId = urlParams.get('roomId') || 'default-room';

  // Daftarkan koneksi ke room yang sesuai
  if (!rooms.has(roomId)) {
    rooms.set(roomId, new Set());
  }
  rooms.get(roomId).add(ws);

  console.log(`Kolaborator bergabung ke Room: ${roomId}`);

  // Menangani data gambar masuk
  ws.on('message', (message) => {
    try {
      const payload = JSON.parse(message);
      
      // Pastikan payload berupa drawing event
      if (payload.type === 'draw') {
        // Broadcast koordinat ke seluruh anggota room KECUALI pengirimnya
        const clientsInRoom = rooms.get(roomId);
        clientsInRoom.forEach((client) => {
          if (client !== ws && client.readyState === 1) { // 1 = OPEN
            client.send(JSON.stringify({
              type: 'draw',
              data: payload.data
            }));
          }
        });
      }
    } catch (err) {
      console.error('Gagal memproses pesan:', err);
    }
  });

  // Hapus koneksi dari room saat kolaborator keluar
  ws.on('close', () => {
    const clientsInRoom = rooms.get(roomId);
    if (clientsInRoom) {
      clientsInRoom.delete(ws);
      if (clientsInRoom.size === 0) {
        rooms.delete(roomId);
      }
    }
    console.log(`Kolaborator keluar dari Room: ${roomId}`);
  });
});

console.log('Whiteboard WebSocket Server aktif di port 8080');

2. Sisi Client (Frontend React / Vanilla JS)

Bagian ini bertugas mendeteksi tarikan mouse di atas elemen HTML5 Canvas, merender goresan kuas, dan mengirimkan data koordinat tersebut secara real-time.

<!DOCTYPE html>
<html lang="id">
<head>
  <meta charset="UTF-8">
  <title>Real-Time Collaborative Whiteboard</title>
  <style>
    body { margin: 0; padding: 0; background: #080808; overflow: hidden; font-family: sans-serif; }
    canvas { display: block; cursor: crosshair; }
    #toolbar { position: absolute; top: 20px; left: 20px; background: rgba(20,20,20,0.9); padding: 10px; border: 1px solid #333; color: white; display: flex; gap: 15px; border-radius: 4px; }
  </style>
</head>
<body>

  <div id="toolbar">
    <label>Kuas: <input type="color" id="colorPicker" value="#0055FF"></label>
    <label>Ketebalan: <input type="range" id="lineWidth" min="1" max="15" value="3"></label>
    <button onclick="clearCanvas()">Hapus Semua</button>
  </div>

  <canvas id="whiteboard"></canvas>

  <script>
    const canvas = document.getElementById('whiteboard');
    const ctx = canvas.getContext('2d');
    const colorPicker = document.getElementById('colorPicker');
    const lineWidthInput = document.getElementById('lineWidth');

    // Menyesuaikan ukuran kanvas dengan layar browser
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    let drawing = false;
    let lastX = 0;
    let lastY = 0;
    
    // Hubungkan ke room tertentu via WebSocket
    const roomId = "dev-brainstorm"; 
    const ws = new WebSocket(`ws://localhost:8080?roomId=${roomId}`);

    ws.onmessage = (event) => {
      const payload = JSON.parse(event.data);
      if (payload.type === 'draw') {
        drawRemote(payload.data);
      }
    };

    // Fungsi menggambar goresan lokal
    canvas.addEventListener('mousedown', (e) => {
      drawing = true;
      [lastX, lastY] = [e.clientX, e.clientY];
    });

    canvas.addEventListener('mousemove', (e) => {
      if (!drawing) return;
      
      const currentX = e.clientX;
      const currentY = e.clientY;
      const drawData = {
        x0: lastX,
        y0: lastY,
        x1: currentX,
        y1: currentY,
        color: colorPicker.value,
        width: lineWidthInput.value
      };

      // 1. Render coretan di layar sendiri
      drawLocal(drawData);

      // 2. Kirim koordinat ke server
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({
          type: 'draw',
          data: drawData
        }));
      }

      [lastX, lastY] = [currentX, currentY];
    });

    canvas.addEventListener('mouseup', () => drawing = false);
    canvas.addEventListener('mouseout', () => drawing = false);

    function drawLocal(data) {
      ctx.beginPath();
      ctx.strokeStyle = data.color;
      ctx.lineWidth = data.width;
      ctx.lineCap = 'round';
      ctx.moveTo(data.x0, data.y0);
      ctx.lineTo(data.x1, data.y1);
      ctx.stroke();
    }

    function drawRemote(data) {
      // Menggambar data kiriman kolaborator lain
      ctx.beginPath();
      ctx.strokeStyle = data.color;
      ctx.lineWidth = data.width;
      ctx.lineCap = 'round';
      ctx.moveTo(data.x0, data.y0);
      ctx.lineTo(data.x1, data.y1);
      ctx.stroke();
    }

    function clearCanvas() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    }
  </script>
</body>
</html>

Optimasi Performa Rendering di Sisi Client

Ketika ada puluhan kolaborator mencoret kanvas secara agresif, proses render Canvas API dapat memicu penurunan performa (lagging / penurunan frame rate). Berikut adalah taktik optimasi rekayasa kanvas yang wajib diterapkan pada skala besar:

  1. RequestAnimationFrame (rAF): Jangan langsung memicu penggambaran Canvas di dalam listener mousemove. Masukkan koordinat baru ke dalam buffer array, lalu gambar goresan tersebut secara sinkron dengan frekuensi refresh layar monitor menggunakan fungsi requestAnimationFrame() bawaan HTML5.
  2. Penyederhanaan Jalur (Path Simplification): Gunakan algoritma matematika seperti Ramer-Douglas-Peucker (RDP) untuk mereduksi jumlah titik koordinat berlebih yang terlalu rapat. Ini akan menghemat memori penyimpanan database dan mengompres ukuran paket data JSON WebSocket hingga 60%!
  3. Kanvas Bertumpuk (Layered Canvas): Pisahkan elemen interaktif menjadi 2 layer Canvas bertumpuk:
    • Layer Bawah (Static Canvas): Digunakan untuk merender lintasan gambar yang sudah final secara permanen.
    • Layer Atas (Dynamic Canvas): Digunakan khusus untuk menggambar elemen sementara yang sangat sering berubah, seperti kursor melayang milik kolaborator lain atau garis pembatas selection box. Ini menghindari perlunya me-render ulang seluruh papan tulis saat kursor bergeser.

Kesimpulan

Membangun collaborative whiteboard yang cepat dan efisien memerlukan kombinasi manipulasi kanvas berkinerja tinggi di frontend dan jaringan terdistribusi di backend. Dengan memanfaatkan HTML5 Canvas, WebSockets, Redis Pub/Sub, serta pendekatan arsitektur asinkron (Write-Behind), Anda dapat melahirkan sistem brainstorm papan tulis tak terbatas yang responsif dan sangat andal bagi tim kolaborator di seluruh dunia!