Cloud pubsub services compared: Azure Web PubSub & Ably
Tags: azure, programming, architecture, cloud
Published: 2022-08-18 11:18:00

In this post, I compare two cloud pubsub services: [Azure Web PubSub](https://docs.microsoft.com/azure/azure-web-pubsub/overview) and [Ably](https://ably.com/), to determine which provides the best development experience. The context will be a multi-user pixelart drawing application that I've built with both services. 

- How easy is it to use their SDKs/APIs?
- How much code do you need to write to add realtime capabilities to your app?

![Collaborative Pixelart drawing animation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qk3j8faurqy2dxl90rc.gif)

This is not a step-by-step instruction on how to build the entire application, I'll highlight some key features and differences. 

> **TLDR**: Draw on the [live canvas](https://pixel-paint.ably.dev/), or look at the source code in the [GitHub repo](https://github.com/ably-labs/collaborative-pixel-drawing) which includes [CodeTours](https://code.visualstudio.com/learn/educators/codetour) to guide you through the solution.

{% embed https://www.youtube.com/watch?v=sPgHwm3-yiM %}

_Watch the video to learn about the three largest differences between Web PubSub and Ably._

---

Realtime messaging is taking over the world. Our food delivery apps show us our meal is only 5 minutes away. We use chat applications every day for work and private use. We receive the latest news on sports, stocks and celebrity news on our mobile devices, and we collaborate on digital documents.

 As I mentioned in my recent article about [pubsub with C#.NET](https://dev.to/ably/how-to-use-pubsub-in-c-net-6-to-build-a-chat-app-1ok7), there are plenty of benefits of using pubsub in multi-user realtime applications. The number of cloud-based messaging services that include client-side pubsub has been growing significantly and for developers it can be difficult to make a decision which one to use. The services available are built using protocols such as WebSockets: some services offer the native WebSocket API directly while others have created abstractions on top of it, making it easier to use.

Let's have a look at the technologies used in this application and expand on the cloud-based WebSockets providers.

## Tech stack

I've created two versions of a collaborative drawing application. Both versions use these components:

- [p5js](https://p5js.org/): A creative coding library, used for the drawing canvas.
- [Azure Functions](https://docs.microsoft.com/azure/azure-functions/functions-overview): A serverless compute service from Microsoft Azure, used for the authentication endpoint, and a function to change the color palette.
- [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/overview): An Azure service for hosting the static web app.
  
### Serverless WebSockets

One version of the drawing application uses [Ably](https://ably.com/), the other uses [Azure Web PubSub](https://docs.microsoft.com/azure/azure-web-pubsub/overview). Both are cloud-based pubsub services (or Serverless WebSocket providers), used for realtime communication. Here's an overview of some features they offer.

| Feature | Ably | Azure Web PubSub |
| -| - | -
| pubsub messaging | Y | Y
| message queues| Y | N
| presence | Y | N
| connection state recovery | Y | Y<sup>1</sup>
| automatic transport selection<sup>2</sup> | Y | N
| guaranteed message ordering | Y |Y
| message history | Y | N
| webhooks | Y | Y

<sup>1</sup> *Web PubSub reliable subprotocol is still in preview.*
<sup>2</sup> Ability to use the [best transport available](https://ably.com/docs/key-concepts#any-internet-device) depending on client capablities.

The communication between clients and the cloud-based messaging service is shown in this diagram:

![Communication flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9uji3t7wyzwnxf2otkr.png)
 
## How I compare cloud pubsub services

I compare the two services by considering the following criteria:

- Creating the cloud resource
- Authentication
- Creating a client-side connection
- Presence
- Publishing messages
- Subscribing to messages

### Naming

Before I start comparing the developer experience, let's have a look at naming first, since both cloud services sometimes use a different terminology for certain concepts.

| Ably | Azure Web PubSub | Description
| - | - | -
| App | Service Instance | A cloud resource is usually created per application. This resource can contain multiple channels/hubs.
| Connection | Connection | An individual client connection (over WebSockets) that is connected to the cloud service.
| Channel | Hub | Logical group of client connections, usually for one purpose (e.g. chat).
| Presence | - | Awareness of the clients present in a channel. Clients can announce their presence by entering a channel, or remove their presence by leave a channel. Presence can be queried to retrieve a list of clients present in a channel.
| - | Group | A subset of client connections. Clients can join or leave a group. A Group can't be queried for their members. Messages can be sent to a group instead of an entire Hub.
| Integrations | Event Handlers | A method of adding extra functionality (e.g. calling a webhook, relaying a message to an upstream server) once messages are received at the cloud service.
| User | User | A user (person) uses a client (device) to establish a connection to the cloud service. One user can have multiple connections when using several clients.
| Client | Client | A client device that uses one connection to communicate with the cloud service.
| Message | Message | Information that is published by a user, sent to the cloud service and distributed to the connected clients.

For more detailed info, see [Web PubSub Service Internals](https://docs.microsoft.comazure/azure-web-pubsub/concept-service-internals) and [Ably Key Concepts](https://ably.com/docs/key-concepts).

## Creating the cloud resource

Before we can do anything with cloud-based messaging, we need to create the appropriate resources.

### Ably

For Ably, the first thing that has to be done is to create a new Ably app and an API key. If you've just [signed up](https://ably.com/signup), a new default app and root API key is already created for you. I do suggest that you create a dedicated Ably app for each application you build. 

![Create Ably App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62emaznaclxfd7ca6bhy.png)

The same goes for the API key. The default root API key has all the capabilities that Ably offers and probably has too many rights. For this application, you only require *Publish*, *Subscribe* and *Presence* capabilities. The API key is required when using the Ably SDK for authentication.

![Ably Dashboard with API key capabilities](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nsqr7af1ud5mdz3t2pkg.png) 

Creating the Ably app and API key can be done via:

- [The Ably dashboard](https://ably.com/tutorials/publish-subscribe#setup-ably-account)
- [The Ably GitHub action](https://github.com/marketplace/actions/ably-control-api)
- [The Ably VSCode extension](https://marketplace.visualstudio.com/items?itemName=ably-labs.vscode-ably)

### Web PubSub

For Web PubSub Service, you need to create a new Web PubSub service resource in Azure. You require an [Azure subscription](https://azure.microsoft.com/free/). When creating the cloud resource, you have to specify a resource group and a region to deploy the service. 

![Create Web PubSub instace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xsshhc3qwx6s918fvivp.png)

Once the service instance is created, you'll find the connection strings in the Keys blade in the portal. One of these connection strings is required when using the server-side Web PubSub SDK for authentication.

![Azure Portal Web PubSub connection string](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idm81slout7dby51sl58.png) 

Creating the Azure resource can be done via:

- [The Azure portal](https://docs.microsoft.com/azure/azure-web-pubsub/howto-develop-create-instance)
- [The Azure CLI](https://docs.microsoft.com/azure/azure-web-pubsub/quickstart-cli-create)
- [Bicep templates](https://docs.microsoft.com/azure/azure-web-pubsub/quickstart-bicep-template)
- Third-party infrastructure tools such as Terraform and Pulumi.

### Resource creation differences

For the Azure resource, you need to define a region where the Web PubSub resource will be deployed. For Ably, you don't have to pick a region, it's multi-region by default (hosted on AWS). When a client connection is established, the Ably client will select a region that results in the lowest latency.

For Ably, you need to create an API key with the required capabilities, unless you're using the root key which has all the capabilities. For Web PubSub, there are connection strings (primary and secondary) and the Client URL Generator that is intended for test and validation purposes only.

## Authentication

For a client to connect to the cloud service, it requires a client access token. Both Web PubSub and Ably provide SDKs to generate these tokens server-side, since it's not advised to have these tokens in client-side code. To generate the tokens, the SDKs require a connection string or API key to authenticate to the cloud service.

### Ably

For Ably, an Azure Function is written in C# that uses the .NET [ably.io NuGet package](https://www.nuget.org/packages/ably.io) to create a client access token:

```csharp
[FunctionName(nameof(CreateTokenRequest))]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "CreateTokenRequest/{clientId}")] HttpRequestMessage req,
    string clientId,
    ILogger log)
{
    var tokenParams = new TokenParams() { ClientId = clientId };
    var tokenData = await _ablyClient.Auth.RequestTokenAsync(tokenParams);

    return new OkObjectResult(tokenData);
}
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/f223d1d582fa7f67a591137115e8feaf6c3f303f/api/CreateTokenRequest.cs#L21) for the full implementation.

The `CreateTokenRequest` function depends on an Ably `IRestClient` object via constructor injection. The dependency is configured in the `StartUp` class:

```csharp
public override void Configure(IFunctionsHostBuilder builder)
{
    var ablyApiKey = Environment.GetEnvironmentVariable("ABLY_APIKEY");
    var ablyClient = new AblyRest(ablyApiKey);
    builder.Services.AddSingleton<IRestClient>(ablyClient);
}
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/8339a4b070037dfb0c4a8e10b1c7f99a6cd3711d/api/Startup.cs#L12) for the full implementation.

The Ably API key mentioned in the *Creating the cloud resource* section is put in the App Settings of the Azure Function App, so it can be retrieved as an environment variable, and used when an instance of the `AblyRest` client is created.

### Azure Web PubSub

For Azure Web PubSub, a similar `CreateTokenRequest` Azure Function is created that uses the [Azure.Messaging.WebPubSub NuGet package](https://www.nuget.org/packages/Azure.Messaging.WebPubSub).

```csharp
[FunctionName(nameof(CreateTokenRequest))]
public async Task<HttpResponseMessage> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "CreateTokenRequest/{hubName}/{groupName}/{clientId}")] HttpRequestMessage req,
    string hubName,
    string groupName,
    string clientId,
    ILogger log)
{
    var webPubSubService = new WebPubSubServiceClient(
        Environment.GetEnvironmentVariable("WebPubSubConnectionString"),
        hubName);
    var clientUri = await webPubSubService.GetClientAccessUriAsync(
        TimeSpan.FromMinutes(30),
        clientId,
        new[] { 
            $"webpubsub.joinLeaveGroup.{groupName}",
            $"webpubsub.sendToGroup.{groupName}"
            }
        );

    return new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(clientUri.ToString())
    };
}
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/da83ca3099da76bb16d12cde5aa3eca1b1f39b53/api/CreateTokenRequest.cs#L14) for the full implementation.

The Web PubSub connection string key mentioned in the *Creating the cloud resource* section is put in the App Settings of the Azure Function App, retrieved as an environment variable, and used when an instance of the `WebPubSubServiceClient` is created.

### Authentication differences

The Ably specific function depends on the `AblyRest` client, which only requires the Ably API key. The API key has the configured capabilities to allow publishing, subscribing and presence management for the channel. This means that if several different access rights are required for various roles, multiple API keys need to be created.

The Web PubSub specific function requires more parameters to create a client token:

- The connection string of the service.
- The Hub name
- The Group name*
- The client ID*
- The roles for leaving and joining the group*

_<sup>*</sup> These are required since a specific subprotocol is used for the client connection. See the *Creating a client-side connection* section._

The Web PubSub connection string has all the rights to the service. When the client access token is generated, the group-specific access is defined.

Although the result is more or less the same (a client access token) the level of detail required for creating the connection is different.

Since the `WebPubSubServiceClient` requires the Hub name next to the connection string, I decided not to use dependency injection for the service client. Otherwise, I had to put the Hub name in an environment variable as well. I believe that a Hub name should be truly dynamic, so I prefer it not be present in configuration or back-end code.

## Creating a client-side connection

The user (with a client device) need to establish a connection with the cloud service. The client-side code will call the `CreateTokenRequest` Azure Function shown earlier, that will return a client access token to establish the WebSocket-based connection.

### Ably

Ably has a client SDK to manage connections and messaging, instead of using the native WebSockets API. An instance of the `Ably.Realtime` client is created that uses the `CreateTokenRequest` endpoint to obtain the client access token that contains the user ID.

```javascript
const clientOptions = {
    authUrl: `/api/CreateTokenRequest/${user.id}`,
    echoMessages: false,
};
ably = new Ably.Realtime.Promise(clientOptions);
```

Once the `ably` client is connected, the `channel` can be retrieved:

```javascript
channel = await ably.channels.get(channelName, {
    params: { rewind: "2m" },
});
```

> Note that the `rewind` parameter is used to retrieve the last two minutes of messages. This is possible because messages are persisted by Ably.

### Azure Web PubSub

Azure Web PubSub uses the WebSocket API directly by specifying the URL with the client access token, and a subprotocol:

```javascript
let tokenResponse = await fetch(
    `/api/CreateTokenRequest/${hubName}/${groupName}/${user.id}`
);
let clientUrl = await tokenResponse.text();
webSocket = new WebSocket(clientUrl, "json.webpubsub.azure.v1");
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/da83ca3099da76bb16d12cde5aa3eca1b1f39b53/src/connectAzureWebPubSub.js#L13) for the full implementation.

### Client-side connection differences

For Ably, the [JavaScript client library](https://github.com/ably/ably-js) is used to create a WebSocket-based connection and interacting with the cloud service. The benefit of using this library over the native WebSocket API is that a lot of boilerplate WebSocket code and difficult to implement concepts, such as reliable connection management, and message ordering, are taken care of for you.

For Web PubSub, the [`json.webpubsub.azure.v1` WebSocket subprotocol](https://docs.microsoft.com/azure/azure-web-pubsub/reference-json-webpubsub-subprotocol) is specified when creating the connection. This is required for doing pubsub without the need of [using event handlers](https://docs.microsoft.com/azure/azure-web-pubsub/concept-service-internals#server_protocol) which push messages to an upstream server. The downside of using this subprotocol is that custom events can only be supported by using event handlers. In this application, I did not use any integrations/event handlers to reduce complexity.

## Presence

When a user visits the web application and clicks the *Connect* button, they connect to the messaging service and their (randomly generated) client ID is added to a `users` collection in the client-side code. The total number of connected users (client devices, actually) is shown next to the button:

![Users connected](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivr0lknn7glgwflw721o.png) 

How do you find out which (or how many) clients are connected to the channel? This concept is called [presence](https://ably.com/docs/realtime/presence).

### Ably

With Ably, presence is a built-in feature that is exposed via the client SDKs.

The following four actions are done when a user has established a connection to Ably:

1. Subscribe to presence *enter* messages.
2. Subscribe to presence *leave* messages.
3. Get the presence of the channel.
4. Enter their own presence.

Subscribing to *enter* and *leave* presence messages published by other clients is done via the `channel.presence` object:

```javascript
channel.presence.subscribe("enter", (member) => {
    addUser(member.clientId, member.data.color);
});
channel.presence.subscribe("leave", (member) => {
    removeUser(member.clientId);
});
```

The entire channel presence can be retrieved as follows:

```javascript
await channel.presence.get((err, members) => {
    members.forEach((member) => {
        if (member.data) {
            addUser(member.clientId, member.data.color);
        }
    });
});
```

Retrieving the total presence of a channel is very useful since you don't have to store the connected users yourself. As soon as a client has joined a channel, they can retrieve the presence and know which users are present in the same channel.

A client can announce their own presence to the channel as follows:

```javascript
channel.presence.enter({
    color: user.strokeColor,
});
```

> Note that you can submit a JSON payload when entering (or updating) presence information. This is ideal for storing client-specific data. In this case, the color of their pixel cursor.

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/8339a4b070037dfb0c4a8e10b1c7f99a6cd3711d/src/connectAbly.js#L23) for the full implementation of user presence.

### Azure Web PubSub

Although Web PubSub does have the *Group* concept and allows adding/remove users, it does not provide a way to retrieve user information from a *Group*. So, there is no presence awareness in Web PubSub.

Once a connection to the Web PubSub service has been established, these steps are performed:

1. Send a *joinGroup* message, so the user is part of this group, and can send and receive group messages.
2. Add user information to the `users` collection (to identify the user on the client-side).
3. Send a *sendToGroup* message to broadcast their user information to the rest of the group.

```javascript
webSocket.send(
    JSON.stringify({
        type: "joinGroup",
        group: groupName,
    })
);
addUser(user.id, user.strokeColor);
webSocket.send(
    JSON.stringify({
        type: "sendToGroup",
        group: groupName,
        noEcho: true,
        data: {
            messageType: joinedMessage,
            clientId: user.id,
            color: user.strokeColor,
        }
    })
);
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/da83ca3099da76bb16d12cde5aa3eca1b1f39b53/src/connectAzureWebPubSub.js#L21) for the full implementation.

However, one part is not yet accounted for. Say there are two clients, A and B, who are connecting in sequence (first A, then B) to the same Hub and Group. When B connects to the Hub, a *sendToGroup* message is published by B which is received by A to announce B has joined. But B has no idea that A is also connected, since B connected later to the Hub than A.

![Web PubSub lack of presence](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rmzo1fjaqqmj9o0h0gqe.png) 

The only way for B to be aware of A is that A sends a *sendToGroup* message as well. The solution I chose was to always include the client ID and pixel cursor color when publishing the *hoverPositionMessage* (a user moves their mouse over the pixel canvas):

```javascript
webSocket?.send(JSON.stringify({
    type: "sendToGroup",
    group: groupName,
    noEcho: true,
    data: {
        messageType: hoverPositionMessage,
        clientId: clientId,
        color: this.strokeColor,
        x: this.x,
        y: this.y,
    }
}));
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/da83ca3099da76bb16d12cde5aa3eca1b1f39b53/src/User.js#L20) for the full implementation.

I don't really like this approach, since the payload gets a bit bloated with redundant information. Ideally, I'd only send `x` and `y` coordinates here.

### Presence differences

The difference between Ably and Azure Web PubSub is significant when it comes to user presence. It would be great if Web PubSub would offer a way to list the users in a  group. Currently, some workarounds are required to get the same functionality as is offered by Ably. On the other hand, the Group concept of Web PubSub has no equivalent in Ably. You could say that both Hubs and Groups map to Channels in Ably. So if you require multiple Groups, you can use multiple channels in Ably.

## Publishing messages

The application publishes messages when:

1. The user's mouse hovers over the canvas.
2. The user clicks a pixel on the canvas.
3. The user enters the 'r' key to reset the canvas.
4. The user selects a color palette.

Scenarios 1 to 3 use client-side publishing. Scenario 4 uses server-side publishing (from the Azure Function). Scenario 4 could also have been implemented via client-side publishing, but I wanted to use the .NET SDK to see how it differs from JavaScript and what other options there are when using server-side code.

### Client-side publishing

Let's look at the `mouseClicked` function as an example of client-side publishing.

### Ably

For Ably, the `publish` method is used on the `channel` object. The method requires an event name and a payload.

```javascript
async function mouseClicked() {
    if (mouseX >= 0 && mouseX <= resoX && mouseY >= 0 && mouseY <= resoY) {
        clickCell(mouseX, mouseY);
        await channel?.publish(clickPositionMessage, {
            x: mouseX,
            y: mouseY,
        });
    }
}
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/8339a4b070037dfb0c4a8e10b1c7f99a6cd3711d/src/sketch.js#L83) for the full implementation.

### Azure Web PubSub

For Web PubSub, the `send` method is used on the `WebSocket` object. The method takes a JSON string and requires a `type`, `groupName`, and a payload.

```javascript
async function mouseClicked() {
    if (mouseX >= 0 && mouseX <= resoX && mouseY >= 0 && mouseY <= resoY) {
        clickCell(mouseX, mouseY);
        webSocket?.send(JSON.stringify({
            type: "sendToGroup",
            group: groupName,
            noEcho: true,
            data: {
                messageType: clickPositionMessage,
                x: mouseX,
                y: mouseY,
            }
        }));
    }
}
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/da83ca3099da76bb16d12cde5aa3eca1b1f39b53/src/sketch.js#L90) for the full implementation.

To distinguish between messages, I added the `messageType` field to the payload. The `noEcho` argument is optional, but since I don't want to echo the message back to the user, it is set to `true`.

### Client-side publishing differences

The Azure Web PubSub code is a bit more verbose since Groups are used. The usage of Groups is required though since the `json.webpubsub.azure.v1` subprotocol is used because there was no need for me to send messages to an upstream server before sending them to the clients. Ideally, I want to use custom events using this subprotocol without the need of going through an upstream server. This is what Ably does by default by using the event name as the first argument.

The `noEcho` can be configured for each `send` method separately using Web PubSub. Although this is very flexible, I wonder how many use cases there are that require this flexibility. For Ably, the `echoMessages` option is set when creating the `Ably.Realtime` client and is applied across all `publish` methods.

### Server-side publishing

The `ChangeColorPalette` Azure Function selects a color palette based on a palette ID in the route, and publishes a message to the cloud service.

### Ably

For Ably, the publishing is done using the `AblyRest` client that is injected via the constructor and assigned to the `_ablyClient` parameter:

```csharp
[FunctionName(nameof(ChangeColorPalette))]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ChangeColorPalette/{paletteId}")] HttpRequestMessage req,
    string paletteId,
    ILogger log)
{
    string[] colors;
    switch (paletteId)
    {
        // removed the switch cases to shorten this code snippet.
    }

    var channel = HttpUtility.ParseQueryString(req.RequestUri.Query)["channel"];
    if (channel != null)
    {
        await _ablyClient.Channels.Get(channel).PublishAsync(
            "color-palette",
            new {
                paletteId = paletteId,
                colors = colors,
            });
    }

    return new OkObjectResult(colors);
}
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/8339a4b070037dfb0c4a8e10b1c7f99a6cd3711d/api/ChangeColorPalette.cs#L21) for the full implementation.

A channel is obtained by name and the `PublishAsync` method on that channel is called that takes an event name (*color-palette*) and a payload containing the `paletteId` and the `colors` array.

### Azure Web PubSub

For Web PubSub, the `WebPubSub` output binding is used:

```csharp
[FunctionName(nameof(ChangeColorPalette))]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ChangeColorPalette/{groupId}/{paletteId}")] HttpRequestMessage req,
    [WebPubSub(Hub = "{query.hubName}")] IAsyncCollector<WebPubSubAction> actions,
    string groupId,
    string paletteId,
    ILogger log)
{
    string[] colors;
    switch (paletteId)
    {
        // removed the switch cases to shorten this code snippet.
    }

    var data = BinaryData.FromObjectAsJson(
        new {
            messageType = "color-palette",
            paletteId = paletteId,
            colors = colors,
        });
    var action = WebPubSubAction.CreateSendToGroupAction(groupId, data, WebPubSubDataType.Json);
    await actions.AddAsync(action);

    return new OkObjectResult(colors);
}
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/da83ca3099da76bb16d12cde5aa3eca1b1f39b53/api/ChangeColorPalette.cs#L16) for the full implementation.

In general, I really like input & output bindings in Azure Functions, since they offer a quick way to move data in and out of a function without writing much code in the function body. The `WebPubSub` binding however still requires a few lines of code since the payload needs to be formatted, a `WebPubSubAction` object has to be created, and this has to be added to the `IAsyncCollector`.

An alternative would be to use the `WebPubSubServiceClient` object with the `SendToGroup`  method in the function body instead of the output binding. It won't be much different regarding the lines of code required.

### Server-side publishing differences

On the server-side, the amount of code required to publish a message is similar between Ably and Azure Web PubSub. I expected it to be less code for Web PubSub when using an output binding, but this wasn't the case. When looking at the steps required to publish a message, I find the Ably API a bit more concise and readable.

## Subscribing to messages

The clients are subscribing to the following messages:

- *hoverPositionMessage*: When the mouse is moved over the canvas.
- *clickPositionMessage*: When the mouse is clicked on the canvas.
- *changeColorPaletteMessage*: When the color palette is changed.
- *resetMessage*: When the canvas is reset.

### Ably

For Ably, subscribing to specific messages looks like this:

```javascript
channel.subscribe(hoverPositionMessage, (message) => {
    setUserPosition(message.clientId, message.data.x, message.data.y);
});
channel.subscribe(clickPositionMessage, (message) => {
    clickCell(message.data.x, message.data.y);
});
channel.subscribe(changeColorPaletteMessage, (message) => {
    handleChangeColorPalette(message.data.paletteId, message.data.colors);
});
channel.subscribe(resetMessage, () => {
    resetGrid();
});
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/8c726d879f19c3c634f62f1f6449a6058de06c70/src/connectAbly.js#L39) for the full implementation.

In addition, the client can subscribe to presence events, when a user joins or leaves a channel:

```javascript
 channel.presence.subscribe("enter", (member) => {
    addUser(member.clientId, member.data.color);
});
channel.presence.subscribe("leave", (member) => {
    removeUser(member.clientId);
});
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/8c726d879f19c3c634f62f1f6449a6058de06c70/src/connectAbly.js#L23) for the full implementation.

### Azure Web PubSub

For Azure Web PubSub, since it's using the native WebSocket API, the `onmessage` event is used, which captures all kinds of events. It up to the developer to filter the exact type and since I added a separate `messageType` field to the payload, that is used to identify the exact message.

```javascript
webSocket.onmessage = (event) => {
    const message = JSON.parse(event.data);
    if (message.type === "message") {
        switch (message.data.messageType) {
            case hoverPositionMessage:
                setUserPosition(
                    message.data.clientId,
                    message.data.color,
                    message.data.x,
                    message.data.y
                );
                break;
            case clickPositionMessage:
                clickCell(message.data.x, message.data.y);
                break;
            case changeColorPaletteMessage:
                handleChangeColorPalette(
                    message.data.paletteId,
                    message.data.colors
                );
                break;
            case resetMessage:
                resetGrid();
                break;
            case joinedMessage:
                addUser(
                    message.data.clientId, 
                    message.data.color);
                break;
            default:
               break;
        }
    }
};
```

See [GitHub](https://github.com/ably-labs/collaborative-pixel-drawing/blob/da83ca3099da76bb16d12cde5aa3eca1b1f39b53/src/connectAzureWebPubSub.js#L45) for the full implementation.

### Subscribing differences

The largest difference is the presence events, which are supported by Ably but not by Azure Web PubSub. Next to that, Ably supports event names that make it very convenient to subscribe to specific events. For Web PubSub, the lack of specific event subscriptions results in a large switch statement inside the `onmessage` handler. Azure Web PubSub does support custom events, but these have to be processed with event handlers and sent to an upstream server, which adds additional complexity.

## Summary

Both Azure Web PubSub and Ably offer a similar pubsub feature set. Ably provides some additional features not offered by Web PubSub, such as presence, message history, and transport selection. I missed presence the most when creating the application with Web PubSub.

I found the Ably client-side API easier to develop with than the native WebSockets API. Using a higher level client SDK for realtime messaging removes a lot of complexity and makes the code more concise. I prefer writing less code and focusing on the (business) domain, so I'm happy with using client SDKs if they make my life easier.

Being able to publish named events and subscribe to them (client-side) without the need of handling them via an upstream server and custom event handlers is a big plus for me.

A potential negative aspect of using a 3rd party library could be the download size of the client app. If you need to keep that to a minimum, then that would be an argument to choose native WebSockets.

I haven't touched on reliable message delivery, message ordering, and default multi-region support. The Ably SDK takes care of this for you completely, and there's no need to manage `ackIDs`, `connectionIds`, and `reconnectionTokens` yourself. 

I do admit I'm biased, since I have used Ably more than I have used the native WebSockets API. But look at the code snippets in this post and decide for yourself.

I encourage you to clone/fork the [GitHub repository](https://github.com/ably-labs/collaborative-pixel-drawing) and run the collaborative pixel drawing application yourself (either using Ably or Web PubSub). Try to add some extra pubsub features to it and [let me know](https://twitter.com/marcduiker) what your experience is.

## Further reading

- [Combine realtime pub/sub messaging with Azure Functions & Durable Entities](https://dev.to/ably/quest-for-serverless-websockets-an-adventure-with-azure-functions-durable-entities-36oh)
- [What is pubsub and how to apply it in C# .NET to build a chat app](https://dev.to/ably/how-to-use-pubsub-in-c-net-6-to-build-a-chat-app-1ok7)