Atproto, le protocole social décentralisé

atproto

Pour Louminot, une application que je développe, j’ai exploré un protocole décentralisé pour gérer les données utilisateurs.

Ce document a pour but de mettre sur papier “numérique” ma compréhension de Atproto.

Avant-propos

Je n’ai jamais aimé les réseaux sociaux… Ce qui me dérange, c’est qu’on n’a pas le contrôle sur nos données. C’est pourquoi quand j’ai entendu parler de Atproto, j’ai été intrigué.

Nos données sur nos propres serveurs et les supprimer quand bon nous semble ? Trop beau pour être vrai ? Des iles sur un océan bleu Photo par Denys Nevozhai sur Unsplash

Le protocole Atproto

On interagit avec le PDS (Personal Data Server) directement pour toutes les opérations. Si je souhaite consulter le post d’un compte, j’envoie une requête à mon PDS qui va l’envoyer sur le “réseau Atproto” et le récupérer.

JorisPDSReseau AtProtoPDS

C’est un schéma simplifié. On va voir plus bas qu’il y a plusieurs concepts à comprendre pour bien appréhender le protocole.

Composition d’un post

Avant de rentrer dans les détails, il faut comprendre comment un post est structuré. Voici donc un post typique sur un réseau social :

JorisHello world!1125j

Dans une base de données relationnelle, on aurait ici des tables posts, authors et likes. Et pour le nombre de “like”, commentaires et partages, on aurait des requêtes SQL pour les statistiques.

posts authors likes
id id id_author
parent_post name id_post
author_id avatar
text
createdAt

Dans Atproto, les données sont stockées dans des fichiers.

Records

Les records sont des fichiers sous format JSON. Le contenu du post précédent a cette structure :

{
  "$type": "app.bsky.feed.post", // le lexicon utilisé pour ce record
  "text": "Hello world", // le contenu du post
  "createdAt": "2026-06-18T08:10:57.856Z" // la date de création du post
}
JorisHello world!1125j

Tout comme dans une base de données relationnelle, les informations sur l’auteur du post ne sont pas stockées dans le même record que le post :

{
  "$type": "app.bsky.actor.profile",
  "avatar": {
    "ref": {
      "$link": "bafkreiha3bnz4knckg6tgdgawvvgtc27bmoykfe2dpe5fq7suxclijgjdm"
    },
    "size": 685201,
    "$type": "blob",
    "mimeType": "image/jpeg"
  },
  "description": "Freelance 🐪 Dev and Engineering Manager",
  "displayName": "Joris"
}
JorisHello world!1125j

Le lien se fait naturellement. Ils sont tous deux sur le même repository, ils appartiennent donc au même compte.

Arborescence de mes fichiers sur le site pdsls.dev @jgundermann sur pdsls

Les records key

Comment faire pour avoir plusieurs posts d’un même auteur ? Il faut que chaque post ait un identifiant unique. Pour cela, on utilise une record key ou en abrégé rkey. Tous les records n’ont pas besoin d’une rkey. Par exemple, l’auteur d’un post est unique dans un repository.

On aura donc une arborescence de fichiers sur le repository qui ressemble à ceci :

app.bsky
  |- actor.profile
    |- self.json
  |- feed.post
    |- 3mqhjuttizs22.json
    |- 3mqhjqwvflc22.json

Repository

Un repository est un ensemble de records. Chaque utilisateur a son propre repository. Un PDS peut contenir plusieurs repositories. Comme il s’agit d’un espace personnel, il peut y avoir plusieurs applications qui y ajoutent des records. Tous les records ne sont pas compatibles entre eux. Du coup, on peut avoir un namespace pour chaque application.

PDS
  |- repository de Joris
    |- app.bsky
      |- actor.profile
        |- self.json
      |- feed.post
        |- 3mqhjuttizs22.json
        |- 3mqhjqwvflc22.json
    |- com.twitter
      |- actor.profile
        |- self.json
      |- feed.post
        |- 3mqhjuttizs34.json

Lexicons

Les lexicons sont les schémas de données qui définissent la structure et les interactions dans le protocole. Voici la définition de notre post :

{
  "id": "app.bsky.feed.post",  
  "defs": {
    "main": {
      "key": "tid",
      "type": "record",
      "record": {
        "type": "object",
        "required": [
          "text", 
          "createdAt"
        ],
        "properties": {
          "text": { 
            "type": "string", 
            "description": "The primary post content. May be an empty string, if there are embeds.",
            "maxLength": 3000, 
            "maxGraphemes": 300 
          },
          "createdAt": { 
            "type": "string",
            "description": "Client-declared timestamp when this post was originally created.",
            "format": "datetime" 
          }
        }
      }
    }
  },
  "$type": "com.atproto.lexicon.schema",
  "lexicon": 1
}

Dans ce lexicon, il y a trois éléments à retenir :

  • type : qu’est-ce que le PDS doit faire de ce schéma. Ici, c’est un record. Le PDS doit donc faire une opération sur un fichier.
  • key : c’est la rkey utilisée pour identifier un post. Pour un singleton, on a par exemple literal:self.
  • properties : ce sont les propriétés du post.

Le schéma nous offre également de l’interopérabilité. Si un autre développeur souhaite créer une application qui interagit avec le protocole Atproto, il peut utiliser le même lexicon pour créer des posts. Ça permet d’avoir des applications qui interagissent entre elles sans être couplées.

Bon, il reste quand même un problème. Comment je fais pour avoir les likes, les commentaires et les partages d’un post ? poste fictif avec les interactions surligné.

C’est là que les Appviews interviennent.

Appviews et relays

Voici un record app.bsky.feed.like :

{
  "$type": "app.bsky.feed.like",
  "subject": {
    "cid": "bafyreigraigbderlnhnwsw4j42qhklrsu4urbwvadu2bihf4kwycelvpzq",
    "uri": "at://did:plc:ns7i7bp55lay2bx75k5jcnbh/app.bsky.feed.post/3mqp3psb2js2w"
  },
  "createdAt": "2026-07-15T19:06:35.954Z"
}

Nous avons donc un record qui contient une référence vers le post qui a été liké. Mais comment je fais pour récupérer tous les likes d’un post ? Il faudrait en théorie scanner tous les PDS du réseau pour trouver les records qui ont app.bsky.feed.like et qui ont pour subject le post. Cela n’est pas très efficace.

Pour cela, il y a les relays. Ce sont des serveurs qui reçoivent des notifications de tous les PDS. Ainsi, une application, disons bsky.app, peut interroger un relay pour récupérer un flux de likes et créer une projection personnalisée dans une base de données locale. Ces applications sont appelées appviews. Les appviews ont une application sidecar qui est le tap.

taprelayrelayrelayFirehosebsky.appSingle page applicationAppviewPDShttps://bsky.appPDSQuel est ma timeline ?Ajout d'un posteRécupération du fluxVoici un nouveau post

Je ne rentre pas dans les détails. Je pense que c’est suffisant pour comprendre le fonctionnement global de Atproto. Si vous souhaitez en savoir plus, je vous invite à lire la documentation officielle.

Mes impressions

Ce protocole est très cool, mais il est complexe. Il y a beaucoup de concepts à comprendre et, comme dans les architectures d’Event sourcing, il y a de la consistance éventuelle. Il faut donc faire attention au read after write. Un autre avantage que je vois à ce protocole, c’est que les environnements de développement peuvent dépendre de données de production sans risque de les corrompre. On peut donc faire des tests sur des données réelles sans risque.