Create guides/federation authored by Calum Mackervoy's avatar Calum Mackervoy
# Synopsis
Linked Data Platform (LDP), provides a set of rules for HTTP operations on web resources to provide an architecture for read-write Linked Data on the web. In short, federation is the connecting of multiple server instances so that the service provided to the user is consistent across multiple peers and databases, potentially using different technologies and structures
# The Federated Model
DjangoLDP is built on the `Model` class from the [main package](https://git.startinblox.com/djangoldp-packages/djangoldp)
```python
class Model(models.Model):
urlid = LDPUrlField(blank=True, null=True, unique=True)
...
```
In Linked Data Protocol, every resource can be uniquely identified by a url. In DjangoLDP, this accessible on every model by its `urlid` field. A clientside application will take the urlid, and use it to access the underlying resource it points to
In a federated application, I expect to use instances both **local** (with a urlid belonging a resource on my server) and **external** (with a urlid pointing to a resource on another server)
Detailed documentation on the options and methods available on this model are available from the package page
# Client-Side Federation
A federated client is a client which serves and interfaces data from multiple sources. Using Solid standards, we're able to use a single protocol to talk to any of these servers, so that APIs and clients are no longer inter-dependent
# Server-Side Federation
Server-side, federation is achieved in consistency of data links between peers. This means that if a local object references a model with an external `urlid` (a local pointer to a distant resource), a reverse pointer needs to be created on the distant server and maintained (for example if an object in the relation is deleted or changed)
These reverse-pointers are also referred to as **backlinks**. A backlinks service is provided in djangoldp/activities, which listens to `post_save`, `post_delete` and `m2m_changed` signals on all Models, utilising the [ActivityStreams](https://www.w3.org/TR/activitystreams-vocabulary/) vocabulary to inform referenced peers of the need to update their references
## The Backlinks System in DjangoLDP
Take, for example, that we have the following models:
```python
from django.contrib.auth import AbstractUser
from djangoldp.models import Model
class User(Model, AbstractUser):
...
class Circle(Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name="circles")
```
Note that both models inherit from `djangoldp.models.Model`, where `Circle` has a ForeignKey to `User`
Managing federated users requires a little extra effort in authentication which isn't included in this tutorial, for more information please see [the associated guide](https://git.startinblox.com/djangoldp-packages/djangoldp/wikis/guides/authentication)
### Backlink Detection
In the djangoldp/activities app, there are several signal handlers which listen for an object being saved, deleted or a many-to-many relation changed. When an object is saved or deleted, the relations of its model are parsed, identifying any foreign key relations which reference an external urlid. For example:
```python
user = User.objects.create(urlid='https://distant.com/users/1/')
Circle.objects.create(owner=user)
```
In this code I have created a link to an external user, which I have then made the owner to my local circle. When the Circle object is saved, the distant link will be identified, its `inbox` discovered (see the [Linked Data Notifications](https://www.w3.org/TR/ldn/) spec for details) and the following activity sent to it:
```python
{
"@context": [...],
"summary": "A circle was created",
"type": "Create",
"actor": {
"type": "Service",
"name": "Backlinks Service"
},
"object": {
"@type": "hd:circle",
"@id": "https://local.com/circles/1/",
"owner": {
"@type": "foaf:user",
"@id": "https://distant.com/users/1/"
}
}
}
```
Similarly this service will manage saves for `Update`/`Delete` activities on a model, and `Add`/`Remove` activities for many-to-many relationships
### Responding to Backlinks
DjangoLDP defines an `InboxView` which is capable of receiving ActivityStreams format requests. Upon successfully parsing an activity, it returns `201 - Accepted` with the URL of the stored activity in the `Location` header of the response
In the case of our prior example, the backlink is processed by parsing the object, saving backlinks for any distant resources (or getting an existing reference) and updating the relationship. The model will be marked with `is_backlink = True` to indicate its status
### Other consumers
In certain cases, the currently linked references are not the only servers which I should notify of an object's status. Consider that in our example another service may wish to listen to the object for changes
For these purposes DjangoLDP defines a `Follower` model, which simply stores the `object` URL being watched and the `inbox` subscribing to it. As a third-party consumer I can send a `Follow` activity on the object:
```python
{
"@context": [...],
...
"type": "Follow",
"actor": {
"type": "Person",
"name": "Alice",
"inbox": "https://alice.com/inbox/"
},
"object": {
"@type": "hd:circle",
"@id": "https://distant.com/circles/1/"
}
}
```
Which will cause me to be notified of any Updates to the object's relations until it is deleted, or I revoke my subscription with an `Undo` activity
\ No newline at end of file