Modeling Users, Objects, and Tenants in Knock
In this guide, we’ll cover some best practices in modeling users, tenants, and objects in Knock. Since Knock is a set of flexible abstractions, there are many possible ways to map these concepts in Knock to entities in your own application, but this guide will use examples to help you with this decision-making process.
To do this, we’ll use an example collaboration app called Collab.io that consists of users, workspaces, projects, and alerts.
Users in Knock
Users in Knock are the most straightforward concept to explain in this guide because users in your application will map directly to users in Knock. Users in Knock are identified with a unique id
, which in most cases should be the same id
that you use to identify them in your application.
Users can have any number of custom properties associated with them, and Knock reserves a number of optional properties like email
, name
, phone_number
, timezone
, and avatar
that are used as defaults across different message delivery channels.
You can sync these properties with Knock through a process called identification.
Objects in Knock
Objects in Knock are a flexible abstraction that you can use to send notifications to non-user recipients. You can also represent relationships between these non-user recipients and users via Subscriptions.
Let’s look at some non-user recipient use cases first.
Non-user recipients
These non-user recipients can include things like a Slack integration or a webhook destination. Objects are like a NoSQL data store that allows you to map resources from your application into Knock. Objects in Knock live inside of collections and are identified with an id that’s unique to that collection.
A webhook destination
The alert
entities inside of Collab.io exist to send webhooks to a downstream service when certain events are triggered inside of a project. To store this entity in Knock, you can create an Object with the id of alert_URJXQKT1
inside of the alerts
collection.
Since an Object can store any number of custom properties, you can also include values for url
and signingKey
along with any other data you might need when sending a webhook event or listing these alerts in your application’s UI.
When you trigger a workflow with this object as a recipient, you can use the url
and signingKey
properties to generate a secure webhook request. Using the events
array, you can store event subscriptions directly on the Object and filter out webhook events using a step condition. To learn more about using Objects and webhooks, you can read our guide on creating customer facing webhooks.
A Slack integration
The project
entities in Collab.io are the main surface areas for collaboration in the application and generate several types of notifications. Let’s assume that users can map individual projects to Slack channels in a shared workspace so that new comments get sent to a particular channel.
To map this entity to Knock, you would create an Object with the id of project_1YQ4XR18
inside of the projects
collection.
The Object which represents that project
might look like this:
Unlike the webhook example, where we used custom properties on the Object to power a downstream notification using the webhook channel, channels like Slack look for connection information in a special property called channel data.
Details like the channel_id
and access_token
are stored on the Object’s channel data, which you can see in the example below:
When you use the project_1YQ4XR18
object as a recipient in a workflow with a Slack channel step, Knock automatically looks for channel data on the object to provide the necessary details to deliver the notification.
Subscriptions
In addition to acting as non-user recipients, Objects also allow you to express a relationship to groups of users via Subscriptions. Object subscriptions are great for use cases where you want to notify a group of users in bulk, like a contact list, or fan out to all the subscribers or a particular topic. In more advanced implementations, you can also model hierarchies using objects and subscriptions, which should give you more flexibility in fan-out operations.
Let’s look at an example of how you can subscribe a Collab.io user to updates on a project
.
First, you need to create a subscription between the user
and the project
, and add any custom properties you want stored on the subscription. You can access subscription properties for recipients in your message templates using the recipient.subscription
property:
Finally, to send a message to all recipients, you trigger a workflow using the project_1YQ4XR18
object as a recipient:
When this workflow is triggered, Knock will generate individual workflow runs for the object itself AND for each of the object’s subscribers.
If you recall from the previous step, there is also some Slack channel data stored on project_1YQ4XR18
in Knock, and the workflow.trigger
code snippet above is also used in that example. This is why Knock can be so powerful in simplifying notification logic.
In practice, that means the first workflow run using the project
object can generate a Slack notification, and all of the following workflow runs can notify individual recipients on another channel like email or in-app messaging.
Tenants in Knock
Tenants in Knock are a concept that allow you to segment your users and their messages. Most SaaS applications have some concept that is similar to “accounts,” “organizations,” “workspaces,” or “groups.” Under the hood, Tenants are a system-level Object collection called $tenants
, so you can operate on them the same way you would an Object. You can set custom properties and subscribe users to them.
In Collab.io, tenants are modeled as workspaces
, which contain projects
and alerts
. You can create a corresponding Tenant in Knock using the same id that you use in your application:
Tenants in Knock are loosely coupled to your users and objects, which means Knock does not know anything about the relationship between your users and tenants.
Instead, you need to tell Knock that a particular workflow run belongs to a particular tenant when triggering a workflow. This means that you have less data to synchronize to Knock, and the risk of drift between what's current in your system and what's reflected in Knock is reduced.
Tagging messages with a particular tenant can help you segment your notifications and apply per-tenant branding and preferences.
Tenants are also useful for helping you scope the in-app feed to messages about a certain workspace or organization.