# 튜토리얼: 동시 작업 클라이언트와 서버를 분리하여 배포하기

## Content

SpreadJS 동시 작업 시스템은 **Collaboration Client**(프론트엔드)와 **Collaboration Server**(백엔드)를 사용하여 실시간 다중 사용자 편집 환경을 제공합니다.
이 매뉴얼은 프론트엔드(정적 웹사이트)와 백엔드(Node.js WebSocket 서비스)를 **완전히 분리**하여 각각 **독립적으로 배포**하는 방법을 설명합니다.

## 사전 준비 사항

* [튜토리얼: 실시간 동시 편집 SpreadJS](/spreadjs/docs/spreadjs-collaboration-server/spreadjs-sheets-collaboration/tutorial-real-time-collaborative-spreadjs) 완료
* [데이터베이스 어댑터](/spreadjs/docs/spreadjs-collaboration-server/collaboration-framework/js-collaboration-ot/database-adapter)에 대한 기본 지식

### 주요 기능

* 프론트엔드–백엔드 분리를 통한 유연한 배포
* 여러 사용자가 동시에 SpreadJS 워크북을 편집하는 실시간 동기화
* Node.js 및 WebSocket 기반의 확장 가능한 백엔드
* SQLite 기반 문서 상태 영속성 저장
* 다양한 프론트엔드호스팅 환경(Nginx, Apache, IIS 등) 지원

### 프로젝트 구조

프로젝트는 **두 개의 독립된 디렉터리**로 구성됩니다.

```plaintext
collaboration-server/   # 백엔드 전용 – Node.js 환경에 배포
│── server.js
│── init-database.js
│── package.json

collaboration-client/   # 프론트엔드 전용 – 정적 사이트
│── public/
│   ├── index.html
│   ├── client.js
│   └── client.bundle.js  # webpack 빌드로 생성
│── webpack.config.js
│── package.json
```

## 세부 단계

***

### Collaboration Server 설정

#### 1단계: 프로젝트 디렉터리 생성

```bash
mkdir collaboration-server
cd collaboration-server
npm init -y
```

#### 2단계: package.json 설정

```json
{
  "name": "collaboration-server",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node ./server.js"
  },
  "dependencies": {}
}
```

#### 3단계: 의존성 설치

```bash
npm install @mescius/js-collaboration @mescius/js-collaboration-ot
npm install @mescius/spread-sheets-collaboration
npm install sqlite3 @mescius/js-collaboration-ot-sqlite
```

#### 4단계: server.js 생성

```typescript
import http from 'http';
import sqlite3 from 'sqlite3';
import { Server } from '@mescius/js-collaboration';
import * as OT from '@mescius/js-collaboration-ot';
import { type } from '@mescius/spread-sheets-collaboration';
import { SqliteDb } from '@mescius/js-collaboration-ot-sqlite';

// SpreadJS 동시 작업 타입 등록
OT.TypesManager.register(type);

const httpServer = http.createServer();
const server = new Server({ httpServer });
const port = 8080;

// SQLite 데이터베이스 및 어댑터 초기화
const db = new sqlite3.Database("./docs.db");
const sqliteDbAdapter = new SqliteDb(db);

// OT 문서 서비스 구성
const documentServices = new OT.DocumentServices({ db: sqliteDbAdapter });
server.useFeature(OT.documentFeature(documentServices));

// 서버 시작
httpServer.listen(port, () => {
    console.log(`Collaboration server listening on port ${port}`);
});
```

#### 5단계: 데이터베이스 초기화

`init-database.js` 파일을 생성합니다.

```typescript
import sqlite3 from "sqlite3";
const db = new sqlite3.Database("./docs.db");
async function initSqliteDataTables(db) {
    const run = (sql) => {
        return new Promise((resolve, reject) => {
            db.run(sql, (e) => (e ? reject(e) : resolve()));
        });
    };
    await run(
        `CREATE TABLE IF NOT EXISTS documents (
            id TEXT PRIMARY KEY,
            type TEXT NOT NULL,
            version INTEGER NOT NULL,
            snapshot_version INTEGER NOT NULL
        )`
    );
    await run(
        `CREATE TABLE IF NOT EXISTS operations (
            doc_id TEXT NOT NULL,
            version INTEGER NOT NULL,
            operation TEXT NOT NULL,
            PRIMARY KEY (doc_id, version),
            FOREIGN KEY (doc_id) REFERENCES documents (id) ON DELETE CASCADE
        )`
    );
    await run(
        `CREATE TABLE IF NOT EXISTS snapshot_fragments (
            doc_id TEXT NOT NULL,
            fragment_id TEXT NOT NULL,
            data TEXT NOT NULL,
            PRIMARY KEY (doc_id, fragment_id),
            FOREIGN KEY (doc_id) REFERENCES documents (id) ON DELETE CASCADE
        )`
    );
}
// 초기화 함수 호출
initSqliteDataTables(db);
```

>type=note
> 이 단계는 최초 설정 시 또는 데이터베이스를 초기화한 후에만 실행하면 됩니다.
>
> ```bash
> node init-database.js
> ```

***

### Collaboration Client 설정

#### 1단계: 프론트엔드 프로젝트 생성

```bash
mkdir collaboration-client
cd collaboration-client
npm init -y
```

#### 2단계: package.json 설정

```json
{
  "name": "collaboration-client",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack"
  },
  "dependencies": {}
}
```

#### 3단계: 의존성 설치

```bash
# SpreadJS 및 동시 작업 클라이언트 패키지
npm install @mescius/spread-sheets @mescius/spread-sheets-collaboration-addon
npm install @mescius/js-collaboration-client @mescius/js-collaboration-ot-client
npm install @mescius/spread-sheets-collaboration-client

# 빌드 도구
npm install --save-dev webpack webpack-cli style-loader css-loader
```

#### 4단계: `public` 폴더 및 파일 생성

프론트엔드 HTML 페이지 생성 및 수정: **public/index.html**

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SpreadJS Real-Time Collaboration</title>
    <script src="./client.bundle.js"></script>
</head>
<body>
    <div id="ss" style="width:100vw; height:95vh; border:1px solid darkgray;"></div>
</body>
</html>
```

프론트엔드 JavaScript 코드 생성 및 수정: **public/client.js**

```auto
import * as GC from '@mescius/spread-sheets';
import '@mescius/spread-sheets-collaboration-addon';
import { Client } from "@mescius/js-collaboration-client";
import * as OT from "@mescius/js-collaboration-ot-client";
import { type, bind } from '@mescius/spread-sheets-collaboration-client';
import "@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css";

// SpreadJS 동시 작업 데이터 타입 등록
OT.TypesManager.register(type);

// ===== 중요: 실제 동시 작업 서버 주소로 수정 =====
// 로컬 개발 예시:
const SERVER_URL = "ws://127.0.0.1:8080";

// 운영 환경 예시:
// const SERVER_URL = "wss://collab.yourdomain.com";
// const SERVER_URL = "wss://your-app.herokuapp.com";

window.onload = async function () {
    // SpreadJS 워크북 초기화
    const workbook = new GC.Spread.Sheets.Workbook('ss');

    // 클라이언트 연결 생성 및 동시 작업 룸 참여
    const conn = new Client(SERVER_URL).connect('room1');
    const doc = new OT.SharedDoc(conn);

    // 연결 및 문서 동기화 오류 처리
    doc.on('error', (err) => console.error('Collaboration error:', err));

    // 서버에서 문서 상태 가져오기
    await doc.fetch();

    if (!doc.type) {
        // 초기 콘텐츠로 새 공유 문서 생성
        workbook.getActiveSheet().getCell(0, 0).value("default content");
        await doc.create(workbook.collaboration.toSnapshot(), type.uri, {});

        // 워크북을 공유 문서에 바인딩하여 실시간 동기화
        bind(workbook, doc);
    } else {
        // 이미 공유 문서가 존재하는 경우 바인딩만 수행
        bind(workbook, doc);
    }
};
```

프론트엔드 프로젝트 설정 생성 및 수정: **webpack.config.js**

```auto
const path = require("path");

module.exports = {
  entry: "./public/client.js",
  output: {
    path: path.resolve(__dirname, "public"),
    filename: "client.bundle.js",
  },
  mode: "development",
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};
```

#### 5단계: 프론트엔드 빌드

```auto
npm run build
```

빌드가 완료되면 `public/client.bundle.js` 파일이 생성됩니다.

#### 6단계: 프론트엔드 배포

`public/` 폴더 전체를 정적 호스팅 서버(Nginx, Apache, IIS 등)에 업로드합니다.

***

## 기능 설명

| 기능 | 설명 |
| --- | --- |
| **실시간 동기화** | 하나의 클라이언트에서 수행한 편집이 동일한 문서 룸을 공유하는 모든 사용자에게 즉시 반영됩니다. |
| **문서 영속성** | 서버는 SQLite를 사용하여 스냅샷과 작업 이력을 저장합니다. |
| 프론트엔드**–백엔드 분리** | 각 구성 요소를 독립적으로 배포 및 확장할 수 있습니다. |
| **WebSocket 통신** | 가볍고 효율적인 실시간 연결을 제공합니다. |
| **크로스 플랫폼 호스팅** | 모든 정적 웹 호스트 및 Node.js 환경에서 동작합니다. |

## 사용 방법

### 1단계: 백엔드 시작

```auto
cd collaboration-server
npm run start
```

예상 출력: *Collaboration server listening on port 8080*

### 2단계: 프론트엔드 호스팅

`collaboration-client/public/` 디렉터리를 정적 서버에 배포합니다.

### 3단계: 실시간 동시 작업 테스트

1. 여러 브라우저 또는 기기에서 프론트엔드 페이지를 엽니다.
2. 한 인스턴스에서 셀을 편집합니다.
3. 모든 연결된 클라이언트에 변경 사항이 즉시 반영되는지 확인합니다.