automerge
automerge๋ CRDT ํจํด์ ์ง์ํ๋ ์๋ฐ์คํฌ๋ฆฝํธ ํ๊ฒฝ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค.
๊ตฌํ์ Rust์ง๋ง, ์ ์ด์ ๋ฒ์ฉ์ ์ธ ์น ํ๊ฒฝ์ ๊ณ ๋ คํ ๊ฒ์ด๋ผ์ WASM์ ํตํ Javascript ์ฐ๋์ ์ง์ ์ง์ํ๋ค.
๊ทธ๋์ WASM์ด ๋ถ๊ฐ๋ฅํ ํ๊ฒฝ์๋ผ๋ฉด ์ฐ์ง ๋ชปํ ๊ฒ์ด๋ค.
Rust๋ C๋ก ์ฌ์ฉํ๋ ๊ฒ๋ ๊ฐ๋ฅ์ ํ๋ฐ, ๋ฌธ์ํ๋ ์ ์๋ผ์๊ณ ๊ถ์ฅํ์ง๋ ์๋๋ค.
CRDT์ ๋ํ ๊ฐ๋
์ฐธ์กฐ
https://blog.naver.com/sssang97/223113627152
๋ฐฉํฅ์ฑ๊ณผ ์๋ฆฌ
automerge๊ฐ CRDT๋ฅผ ๊ตฌํํ ๋ฐฉ๋ฒ์ git์ ์๋ฆฌ์ ํก์ฌํ๋ค.
๊ฐ ๋ณ๊ฒฝ์ฌํญ์ ๋ณ๋์ ํ๋ฆ์ผ๋ก ๊ด๋ฆฌํ๋ค๊ฐ, ๋ณํฉํ๋ฉด ๊ทธ๋ ์ด๋ฆฌ์ ๋ฆฌ ๋ผ์๋ฃ๊ณ ๋ฐ์ด๋ฃ๋ ์์
์ ํ๋ ๊ฒ์ด๋ค.
automerge๋ "automerge ๊ฐ์ฒด"๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ชจ๋ ๋ณ๊ฒฝ์ ๋ํ ์ ์ด์ ๋ณํฉ์ ์ด๊ดํ๋ค.
๊ฐ automerge ๊ฐ์ฒด๋ ๋ถ๋ณ ๊ฐ์ด๊ณ , ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด ์๋ก์ด ๋ฒ์ ์ ๊ฐ์ฒด๋ฅผ ์๋ก ์์ฑํ๋ค.
์๋ฐ์คํฌ๋ฆฝํธ ํ๊ฒฝ์ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค๋ ๊ทธ๋ฐ ๋ฐฉ์์ ์ทจํ๊ณค ํ๋๋ฐ, ๋น์ทํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.

๊ทธ๋ฆฌ๊ณ ๋ณ๊ฒฝ์ด ๋๋ฉด ๊ทธ ๋ณ๊ฒฝ๋ ์ฌ๋ณธ๋ค์ ๊ธฐ๋ฐ์ผ๋ก, Merge๋ฅผ ํ๋ฉด ์ ์ ํ ๋ณํฉ์ฒ๋ฆฌ๋ ํด์ฃผ๋๊ฒ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ๋ฒ์ด๋ค.
์ฌ๊ธฐ์ ๊ฐ automerge ๊ฐ์ฒด๋ ๋ก์ปฌ์์ ํ์ฌ ์ฌ์ฉ์๊ฐ ๋ณ๊ฒฝํ ๋ด์ญ์ผ ์๋ ์๊ณ , ๋คํธ์ํฌ๋ก ๋ ๋ผ์จ ๋ค๋ฅธ ์ฌ์ฉ์์ ๋ณ๊ฒฝ๋ด์ญ์ผ ์๋ ์๋ค.
์ผ๋จ automerge ๊ฐ์ฒด๋ค๋ก ๋ฐ์์ค๊ธฐ๋ง ํ๋ฉด merge๋ฅผ ํตํด์ ์์ ์ ์ผ๋ก ๋ณํฉํ ์ ์๋ค.
์ค์น
nodejs์์ ์ด๋ค๋ฉด ๊ทธ๋ฅ npm์ผ๋ก ์ค์นํ๋ ๊ฒ๋ง์ผ๋ก๋ ๊ฐ๋จํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
npm i @automerge/automerge @automerge/automerge-repo

ํ์ง๋ง React, Vue ๋ฑ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ์ ๊ณ ๋ คํ๋ค๋ฉด ์ค์ ํด์ผํ ๊ฒ์ด ๋ช๊ฐ์ง ์๋ค.
์น์ด์
๋ธ๋ฆฌ ์ข
์์ฑ์ด ์๊ธฐ ๋๋ฌธ์ด๋ค. ์์ธํ ๊ฒ์ ๋ฌธ์ ๋ด ๊ฐ์ด๋๋ฅผ ์ฐธ์กฐํ๋ค.
https://automerge.org/docs/library_initialization/
์ฌ๊ธฐ์๋ ์ผ๋จ node.js๋ฅผ ์จ์๋ง ๊ฐ๋จํ๊ฒ ์๋ฆฌ๋ฅผ ๋ณด์ฌ๋ณด๊ฒ ๋ค.
๊ฐ๋จํ ์์: ํ ์คํธ ๋ณํฉ
CRDT์ ๋ํ์ ์ธ ์ฌ์ฉ๋ก๋ ์๋ฌด๋๋ ๋์ ํ
์คํธ ํธ์ง ๊ฐ์ ๊ฒฝ์ฐ์ผ ๊ฒ์ด๋ค.
automerge๋ ๋ฐฐ์ด์ด๋ ์ซ์, ์ค๋ธ์ ํธ์ ๋ํด์๋ ๋ณํฉ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง๋ง, ๋ด๊ฐ ๋ณผ๋ ๋๋ถ๋ถ์ ์ฌ์ฉ๋ก๋ ํ
์คํธ๋ค.
ํ๋ฒ automerge๊ฐ ์ด๋ค ์์ผ๋ก ์ฌ์ฉ๋๋์ง๋ฅผ ๋ณด์ฌ๋ณด๊ฒ ๋ค.
์๋๋ ๊ทธ๋ฅ ํ
์คํธ ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํ๊ณ ํ
์คํธ๋ฅผ ๋ผ์๋ฃ๋ ๋ณ๊ฒฝ์ ํ๋ ๊ฐ๋จํ ์ฝ๋๋ค.
import { next as Automerge } from "@automerge/automerge";
let doc = Automerge.from({ text: "hello world" });
// ๋ณ๊ฒฝ์ ์ํ ๋ณต์ ๋ณธ ์์ฑ
let forked = Automerge.clone(doc);
forked = Automerge.change(forked, (d) => {
// ์ธ๋ฑ์ค 5๋ถํฐ 0๊ฐ๋ฅผ ์ง์ฐ๊ณ ๊ทธ ์๋ฆฌ์" wonderful"๋ฅผ ์ถ๊ฐ
Automerge.splice(d, ["text"], 5, 0, " wonderful");
});
// ๋ณ๊ฒฝ์ฌํญ ๋ณํฉ
doc = Automerge.merge(doc, forked);
console.log(doc.text);
์ด ์ฝ๋์ฒ๋ผ, ์ค์ง์ ์ธ ์ต์ข
๊ฐ์ ๋ํ ๋ณ๊ฒฝ์ ๋ณ๊ฒฝ์ฌํญ์ root์ ๋ณํฉํ๋ ๊ฒ์ผ๋ก ์ด๋ฃจ์ด์ง๋ค.
์ผ๋จ์ ๋๊ธด ํ๋๋ฐ, ์ฌ์ค ๋ณ๊ฒฝ์ฌํญ์ด ํ๋๋ฟ์ด๋ผ์ CRDT๋ฅผ ์ฐ๋ ์๋ฏธ๋ ์์๋ค.
์ด๋ฒ์๋ ๋ณ๊ฒฝ์ฌํญ์ ํ๋ ๋ ์ ์ฉํด๋ณด๊ฒ ๋ค.
์ด๋ ๊ฒ ๋ณ๊ฒฝ ๋ด์ญ์ ํ๋ ๋ง๋ค๊ณ
๋ง์ฐฌ๊ฐ์ง๋ก ๋ณํฉ์ ์์ผ์ ๋ณด๋ฉด ๋๋ค.

๋์ผ ํ
์คํธ์ ๋ํด์ ๋ณ๊ฒฝ์ ์๋ํ์์๋, 2๊ฐ์ง ๋ณ๊ฒฝ์ด ์ ๋นํ ์์ธ ๊ฒ์ ๋ณผ ์ ์๋ค.
์๋๋ ์ ์ฒด ์ฝ๋๋ค.
import { next as Automerge } from "@automerge/automerge";
let doc = Automerge.from({ text: "hello world" });
// ๋ณ๊ฒฝ์ ์ํ ๋ณต์ ๋ณธ ์์ฑ
let forked = Automerge.clone(doc);
forked = Automerge.change(forked, (d) => {
// ์ธ๋ฑ์ค 5๋ถํฐ 0๊ฐ๋ฅผ ์ง์ฐ๊ณ ๊ทธ ์๋ฆฌ์" wonderful"๋ฅผ ์ถ๊ฐ
Automerge.splice(d, ["text"], 5, 0, " wonderful");
});
// ๋ณ๊ฒฝ์ ์ํ ๋ณต์ ๋ณธ ์์ฑ - ๋๋ฒ์งธ
let forked2 = Automerge.clone(doc);
forked2 = Automerge.change(forked2, (d) => {
// ์ธ๋ฑ์ค 0๋ถํฐ 5๊ฐ๋ฅผ ์ง์ฐ๊ณ ๊ทธ ์๋ฆฌ์ "Greetings"๋ฅผ ์ถ๊ฐ
Automerge.splice(d, ["text"], 0, 5, "Greetings");
});
// ๋ณ๊ฒฝ์ฌํญ ๋ณํฉ
doc = Automerge.merge(doc, forked);
doc = Automerge.merge(doc, forked2);
console.log(doc.text);
๊ทผ๋ฐ ์ ์์ ์์๋ ํ
์คํธ๋ฅผ ์๋ก ๋ค๋ฅธ ์์น์ ๊ตฌ๊ฒจ๋ฃ์ด์ ์์ฐ์ค๋ฝ๊ฒ ๋ณํฉ์ด ๋๋ค.
๊ทธ๋ฌ๋ฉด ๋์ผํ ์์น์์ ํ
์คํธ๋ฅผ ์ง์ด๋ฃ์ผ๋ฉด ์ด๋ป๊ฒ ๋ ๊น?

๊ทธ๋ฐ ๊ฒฝ์ฐ์๋ ๋๋ค ํ
์คํธ๋ฅผ ๋ฃ๋, ๋จผ์ ์์ ํ ๋ณ๊ฒฝ์ ๋จผ์ ๋ฐ์ํ๋ ์์ด ๋๋ค.
์ค์๊ฐ ๋คํธ์ํน ๊ตฌ์กฐ ์ ๊ณต
CRDT๊ฐ ํ์ํ ์ ๋์ ํ๊ฒฝ์์๋ ์น์์ผ์ด๋ WebRTC๋ฅผ ํตํ ์ค์๊ฐ ์ฒ๋ฆฌ๊ฐ ๋๋ฐ๋ ๊ฒฝ์ฐ๊ฐ ์ฆ์๋ฐ, CRDT๋ ๊ทธ์ ๋ํ ์ฝ๊ฐ ์ค๋น๋ ๊ตฌ์ฑ์ ์ด๋ ์ ๋ ์ ๊ณตํ๋ค.
์ด๋ฆ์ด @automerge/automerge-repo-storage-*๋ก ์์๋๋ ๋ชจ๋๋ค์ ์คํ ๋ฆฌ์ง ๊ธฐ๋ฐ์ผ๋ก automerge ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๊ณ ๋๊ธฐํํ๋ ๊ธฐ๋ฅ์, @automerge/automerge-repo-network-*๋ก ์์๋๋ ๋ชจ๋๋ค์ ๋คํธ์ํฌ ํต์ ์ ๊ธฐ๋ฐ์ผ๋ก automerge ๊ฐ์ฒด๋ฅผ ์ฃผ๊ณ ๋ฐ์ผ๋ฉด์ ๋๊ธฐํํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
์๋ฅผ ๋ค๋ฉด, ์ด๊ฑด React์ ์ผ๋ฐ์ ์ธ ๊ตฌ์ฑ์ด๋ค.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb"
import { BroadcastChannelNetworkAdapter } from '@automerge/automerge-repo-network-broadcastchannel'
import { Repo } from '@automerge/automerge-repo'
import { RepoContext } from '@automerge/automerge-repo-react-hooks'
const broadcast = new BroadcastChannelNetworkAdapter();
const indexedDB = new IndexedDBStorageAdapter();
const repo = new Repo({
storage: indexedDB,
network: [broadcast],
})
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RepoContext.Provider value={repo}>
<App />
</RepoContext.Provider>
</React.StrictMode>
)
์ด๋ฐ ์์ผ๋ก ์ด๋ํฐ๋ฅผ ์ด๊ธฐํํด์ repo ๋จ์๋ฅผ ์์ฑํ๋ฉด, ์ repo ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก automerge ์์ ๋จ์๋ฅผ ๋ง๋ค๊ฑฐ๋ ์กฐํ, ๋ค๋ฅธ ๋คํธ์ํฌ ์ฌ์ฉ์๋ก์ ๋๊ธฐํ๊น์ง๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ค.
React์ ๋ํ ์์ ์ฝ๋๋ ๋ค์ ๋ฌธ์์ ์ ๋นํ ์ ํ์๋ค. ๋ ์์ธํ ์ฌ์ฉ๋ฒ์ ๋ฌธ์๋ฅผ ์ฐธ์กฐํ๋๊ฒ ์ข์ ๊ฒ ๊ฐ๋ค.
CRDT์์ ์ ๊ณตํ๋ ๋๊ธฐํ ๊ตฌ์ฑ์ ํ์ฉํ๋ ค๋ฉด ์ด๊ฒ์ ๊ฒ ๊ฐ๋ค์จ์ผํ ๊ฒ์ด ๋ง๋ค.
https://automerge.org/docs/quickstart/#manage-docs-with-a-repo
์ฐธ์กฐ
https://automerge.org/docs/hello/
https://github.com/automerge/automerge