Skip to content

Commit

Permalink
Add Quickstart page content
Browse files Browse the repository at this point in the history
  • Loading branch information
GregHolmes committed May 22, 2024
1 parent 1842e2c commit 19c8c89
Showing 1 changed file with 222 additions and 1 deletion.
223 changes: 222 additions & 1 deletion content/livesync/quickstart.textile
Original file line number Diff line number Diff line change
@@ -1,7 +1,228 @@
---
title: Quickstart
meta_description: "."
meta_description: "A quickstart guide to learn the basics of integrating the Ably LiveSync product into your application."
product: livesync
languages:
- javascript
---

LiveSync facilitates broadcasting realtime updates from backend databases to frontend clients at scale. Leveraging Ably's Pub/Sub Channels, LiveSync ensures that data updates are propagated reliably and in order to all connected clients in realtime.

This quickstart will provide a brief introduction into the LiveSync components integrated into a realtime web application enabling people to comment on a post. Following this, you'll follow instructions to get this demo application running locally.

h2(#breakdown). Understanding the implementation

Each component used within this section is based on the "working demo application":https://github.com/ably-labs/models/examples/livecomments that you'll have running locally by the end of this quickstart.

h3(#model). The model

The @model@ is a data model represntation of a specific part of your frontend application. In the @livecomments@ example, the "model":https://github.com/ably-labs/models/blob/main/examples/livecomments/lib/models/hook.ts#L25-L55 is a specific post, defined by its unique post ID, @post:${id}@.

```[javascript]
const modelsClient = new ModelsClient({
ably,
logLevel: 'trace',
optimisticEventOptions: { timeout: 5000 },
syncOptions: { retryStrategy: backoffRetryStrategy(2, 125, -1, 1000) },
});

const model = modelsClient.models.get({
name: `post:${id}`,
channelName: `${channelNamespace}post:${id}`,
sync: async () => getPost(id),
merge,
});
```

h4(#sync). sync

The sync function is used by the Models SDK to fetch the latest data from your backend. The SDK will automatically call this function when it is initialized, and when the SDK detects that the latest data is no longer available on the Ably channel. There are two objects returned within the response, these are the "@sequenceId@":/livesync/models#sequence-id, which is used to identify the point in the stream of change events that corresponds to the current version of the database's state. The second object is the data relevant to the expected data model, in this instance it's the current state of the post defined by its @id@. In this working demo the sync function called is "@getPost@":https://github.com/ably-labs/models/blob/main/examples/livecomments/lib/models/hook.ts#L12-L23.

```[javascript]
export async function getPost(id: number) {
const response = await fetch(`/api/posts/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});

if (!response.ok) {
throw new Error(`GET /api/posts/:id: ${response.status} ${JSON.stringify(await response.json())}`);
}

const { sequenceID, data } = (await response.json()) as { sequenceID: string; data: PostType };

return { sequenceID, data };
}
```

h4(#merge). merge

When a message is received on the channel, the merge function is called to merge that new data model state into the existing model state. The merge function should return the updated model state with the new comment merged in. In this working demo, the "merge":https://github.com/ably-labs/models/blob/main/examples/livecomments/lib/models/mutations.ts#L40-L71 function uses the event name to determine whether the comment sent is a new, an updated, or a deleted comment.

```[javascript]
export function merge(existingState: PostType, event: OptimisticEvent | ConfirmedEvent): PostType {
const state = cloneDeep(existingState);

switch (event.name) {
case 'addComment':
const newComment = event.data! as Comment;
state.comments.push(newComment);
break;
case 'editComment':
const editComment = event.data! as Comment;
const editIdx = state.comments.findIndex((c) => c.id === editComment.id);
state.comments[editIdx] = editComment;
break;
case 'deleteComment':
const { id } = event.data! as { id: number };
const deleteIdx = state.comments.findIndex((c) => c.id === id);
state.comments.splice(deleteIdx, 1);
break;
default:
console.error('unknown event', event);
}

state.comments.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());

return state;
}
```

h4(#subscribe). Subscribe

In the Models SDK, you subscribe to a model to receive its updated state whenever changes occur. Incoming messages on a channel trigger the merge function which integrates the message content into the existing state. With the current working demo, the "subscribe function":https://github.com/ably-labs/models/blob/main/examples/livecomments/components/post.tsx#L32-L38 updates the current state of the post with the new state.

```[javascript]
const onUpdate = (err: Error | null, post?: PostType) => {
if (err) {
console.error(err);
return;
}

setPost(post!);
};
```

h3(#backend). The backend

There are two parts of the backend that are key for the LiveSync integration in the livecomments demo. These two are the @sync@ function and the @outbox entry@.

h4(#sync-function). Sync function

The sync function in the backend is an endpoint of a @GET@ request, which returns the "@sequenceId@":/livesync/models#sequence-id, used to identify the point in the stream of change events that corresponds to the current version of the database's state. This "endpoint":https://github.com/ably-labs/models/blob/main/examples/livecomments/app/api/posts/%5Bid%5D/route.ts#L5-L18 also returns the current state of the data relevant to the expected data model, in this instance it's the current state of the post defined by its @id@.


```[javascript]
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
try {
let id: number;
try {
id = Number(params.id);
} catch (error) {
return NextResponse.json({ message: 'failed to read :id url parameter', error }, { status: 400 });
}
const [data, sequenceID] = await getPost(id);
return NextResponse.json({ sequenceID, data });
} catch (error) {
return NextResponse.json({ message: 'failed to get post', error }, { status: 500 });
}
}
```

h4(#outbox-entry). outbox entry.

The outbox entry is an entry of an update to one of the models within the database. In "this example":https://github.com/ably-labs/models/blob/main/examples/livecomments/lib/prisma/api.ts#L129-L139 it's a record of a comment being added to, updated with, or deleted from a post. When a post is added, updated, or deleted a record of this is also added to the outbox table so that the ADBC can pick this up and publish the update to the specified Pub/Sub channel.

```[javascript]
export async function withOutboxWrite(
op: (tx: TxClient, ...args: any[]) => Promise<Prisma.outboxCreateInput>,
...args: any[]
) {
return await prisma.$transaction(async (tx) => {
const { mutation_id, channel, name, data, headers } = await op(tx, ...args);
await tx.outbox.create({
data: { mutation_id, channel, name, data, headers },
});
});
}
```

h2(#quickstart). Quickstart

In this quickstart you will cover all the steps required to get an existing demo running locally on your machine.

h3(#step-0). The project

Clone the models repository (https://github.com/ably-labs/models/) and navigate to the @livecomments@ directory within @examples@. The @livecomments@ directory houses the project used for this working demo.

h3(#step-1). Create an Ably account

"Sign up":https://ably.com/signup for a free account to get your own API key. Ensure that your API key includes the @subscribe@ and @publish@ "capabilities":/auth/capabilities.

h3(#step-2). Update environment variables

Set up environment variables by copying them from the template:

```
cp env.example env.local
```

Update your @env.local@ file with the following:

```
POSTGRES_URL="postgres://postgres:postgres@localhost:5432/postgres"
POSTGRES_PRISMA_URL="postgres://postgres:postgres@localhost:5432/postgres"
POSTGRES_URL_NON_POOLING="postgres://postgres:postgres@localhost:5432/postgres"
POSTGRES_USER=postgres
POSTGRES_HOST=localhost
POSTGRES_PASSWORD=postgres
POSTGRES_DATABASE=postgres
SESSION_SECRET=<SOME_SECRET>
NEXT_PUBLIC_ABLY_API_KEY=<YOUR_ABLY_API_KEY>
NEXT_PUBLIC_ABLY_CHANNEL_NAMESPACE=<SOME_NAMESPACE>
```

- Replace @<SOME_SECRET>@ with some random string.
- Replace @<YOUR_ABLY_API_KEY>@ with your Ably API Key
- Optionally replace @<SOME_NAMESPACE>@ with a string value used as a [namespace](https://faqs.ably.com/what-is-a-channel-namespace-and-how-can-i-use-them) for the Pub/Sub channel being used by the model.

Export the environment variables in your shell session:

```[sh]
export $(grep -s -v "^#" env.local | xargs) # export environment variables
```

h3(#step-3). Running Docker container

Within the @docker-compose.yaml@ file, there are two services, @adbc@ and @postgres@. The @adbc@ service is the Ably Database Connector (ADBC) service, which will listen to updates persisted to the outbox table in your database and publish them to the Pub/Sub channel.

The @postgres@ service is a postgres database created and run within a Docker container to assist in running this demo as quick as possible.

Spin up these two Docker containers by running the following command:

```[sh]
docker compose up --build -d
```

h3(#step-4). Install dependencies

Install the required dependencies for the web application, including your Models SDK. Then create the necessary database tables and create some seeded data:

```[sh]
pnpm install
pnpm run db
```

h3(#step-5). Run local web server

Run the web application locally by running the following command:

```[sh]
pnpm run dev
```

h3(#step-6). Try it out!

Open the app on [localhost:3000](http://localhost:3000).

Navigate to a post and try adding, editing, and removing comments from multiple tabs and see the changes reflected to all users in realtime!

0 comments on commit 19c8c89

Please sign in to comment.