Intéressé par des cours d'informatique en ligne ?
Visitez mon nouveau site https://www.yesik.it !

Ça y est? Vous avez installé CouchDB? Il est temps maintenant de s'en servir! Dans cet article, nous allons aborder les concepts de base et voir comment accéder à une base CouchDB via l'interface REST.

REST?

REST – pour Representational State Transfer – est une architecture client-serveur dans laquelle un client utilise le vocabulaire standard d'un protocole pour identifier et manipuler des ressources sur un serveur. Un point clé étant que le serveur est sans état. Ce qui implique qu'à chaque requête le client doit être en mesure de fournir assez d'informations au serveur pour qu'il puisse traiter la requête.

Le cas d'école pour l'architecture REST est l'utilisation du protocole HTTP. Celui-ci définit un certain nombre de méthodes (GET, DELETE, PUT, POST, etc.) qui correspondent à des actions dont la sémantique est clairement définie (par exemple: GET veut dire récupérer une ressource, DELETE veut dire supprimer une ressource). Et c'est au travers des méthodes définies par HTTP que seront manipulées les données. Données identifiées dans ce cas par une URI.

Heu...

Oui, d'accord, ça n'est qu'une introduction très superficielle à l'architecture REST. Si vous souhaitez en savoir plus, je ne peux que vous conseiller l'excellent ouvrage RESTful Web Services:

Opération Méthode HTTP Requête SQL
Create POST (ou PUT) INSERT
Read GET SELECT
Update PUT UPDATE
Delete DELETE DELETE
On peut approximativement faire correspondre la sémantique des méthodes HTTP avec les 4 opérations élémentaires d'une base de données (CRUDCreate, Read, Update et Delete).

Pour en revenir au sujet de cet article, CouchDB expose une API (Application Programming Interface – L'ensemble des spécifications de classes, méthodes, fonctions, constantes, etc. qui permettent d'utiliser une technologie dans un programme.) REST/HTTP pour permettre l'accès aux documents stockés dans la base. Et fait correspondre la sémantique des méthodes HTTP avec leur équivalent dans le modèle relationnel.

Tout ça pour dire qu'il est possible d'accéder en lecture ou en modification à une base CouchDB avec n'importe quel outil supportant le protocole HTTP. Pour cet article, nous allons utiliser curl. Mais, afin de vous faire comprendre que ce sont bien des requêtes HTTP qui se cachent derrière l'utilisation de cet outil, j'indiquerai systématiquement pour chaque manipulation l'équivalent avec telnet. Ce qui vous laissera tout le loisir d'examiner tant la requête du client que la réponse renvoyée dans chaque cas par CouchDB.

Note:

Une des raisons pour lesquelles vous trouverez de nombreux tutoriels sur CouchDB ou les services REST qui utilisent curl, c'est que cet outil permet d'exécuter toute la gamme des requêtes HTTP – et accessoirement de contrôler finement ce qui se passe au niveau HTTP (en-têtes, codes de retour).

Pour commencer

Installation

Sous Debian vous aurez besoin du paquet curl pour ce qui suit. Si ce logiciel n'est pas déjà sur votre machine utilisez le gestionnaire de paquets de votre distribution pour procéder à l'installation.

sh# apt-get install curl

Donc, CouchDB est installé sur votre machine. Dans mon cas, ce sera sur l'hôte couchdb. Pour vous, ce sera peut-être localhost, ou le nom ou l'adresse IP du serveur sur lequel vous avez installé CouchDB... Avant toute autre manipulation, nous allons vérifier que le serveur est opérationnel et accessible:

sh$ curl http://couchdb:5984
{"couchdb":"Welcome","version":"0.10.1"}

Peut-être l'ignorez-vous, mais en fait, la commande ci-dessus effectue une requête GET sur un serveur HTTP. Avec curl, elle pourrait être ré-écrite pour le faire apparaître explicitement:

sh$ curl -X GET http://192.168.8.110:5984
{"couchdb":"Welcome","version":"0.10.1"}

Enfin, si vous connaissez un peu le protocole HTTP, vous savez que celui-ci renvoie en plus du contenu à proprement parler

Avec curl, il est possible de visualiser ces informations grâce à l'option -D:

sh$ curl -D - -X GET http://couchdb:5984
HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Date: Mon, 19 Apr 2010 10:47:58 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 41
Cache-Control: must-revalidate

{"couchdb":"Welcome","version":"0.10.1"}

Enfin, comme convenu, voici l'équivalent, mais exécuté via une session telnet:

sh$ telnet couchdb 5984
Trying 10.129.37.61...
Connected to couchdb.
Escape character is '^]'.
GET /

HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Date: Sun, 18 Apr 2010 21:05:04 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 41
Cache-Control: must-revalidate

{"couchdb":"Welcome","version":"0.10.1"}
Connection closed by foreign host.

Créer/Supprimer une base

Maintenant que nous savons que la base est active et accessible, passons aux choses plus sérieuses. Pour la suite de cet article, nous allons considérer le cas d'une base de données servant à référencer des lieux d'intérêt touristique. Notre base s'appellera tourism-db. C'est le type de base de données qui pourrait par exemple servir à un tour operator ou à un office de tourisme.

Mais avant de pouvoir stocker quoi que ce soit dans notre base, il faut tout d'abord la créer.

Or, d'une certaine manière, créer une base, c'est mettre une nouvelle ressource sur le serveur. Autrement dit, dans le vocabulaire HTTP, c'est faire une requête PUT:

sh$ curl -X PUT http://couchdb:5984/tourism-db
{"ok":true}

Une ressource spéciale (une vue) permet d'obtenir la liste des bases de données gérées par une instance de CouchDB. Comme il se doit, on peut consulter cette ressource à l'aide d'une simple requête GET:

sh$ curl -X GET http://couchdb:5984/_all_dbs
["tourism-db"]

Nous voila donc convaincus que la base tourism-db est créée. Imaginons maintenant que nous ayons changé d'avis. Et que nous voulions supprimer cette base. Vous avez compris qu'il va falloir utiliser la méthode HTTP qui veut dire supprimer. J'ai nommé la méthode DELETE:

sh$ curl -X DELETE http://couchdb:5984/tourism-db
{"ok":true}

Et on peut vérifier:

sh$ curl -X GET http://couchdb:5984/_all_dbs
[]

Pour les curieux, voici maintenant l'équivalent avec telnet des commandes ci-dessus. Vous comprenez donc que n'importe quel client HTTP supportant les requêtes PUT et DELETE peut déjà servir pour créer et supprimer une base. Assurons-nous en, en créant une base avec telnet:

sh$ telnet couchdb 5984
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
PUT /tourism-db HTTP/1.1
Host: couchdb:5984
Connection: keep-alive

HTTP/1.1 201 Created
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Location: http://couchdb:5984/tourism-db
Date: Sun, 18 Apr 2010 21:10:50 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 12
Cache-Control: must-revalidate

{"ok":true}
GET /_all_dbs HTTP/1.1
Host: couchdb:5984
Connection: keep-alive

HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Date: Sun, 18 Apr 2010 21:11:02 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 15
Cache-Control: must-revalidate

["tourism-db"]

HTTP/1.1

Vous vous demandez peut-être pourquoi dans cet exemple j'ai spécifié le protocole HTTP/1.1. L'explication est que je voulais maintenir la connexion avec le serveur pour pouvoir émettre plusieurs requêtes sans avoir à me reconnecter. Ca n'a absolument rien d'obligatoire. C'est juste une question de confort...

Toujours est-il que HTTP/1.1 introduit cette possibilité grâce à l'en-tête Connection: keep-alive. Quand à l'en-tête Host, il est obligatoire en HTTP/1.1. C'est la seule raison pour laquelle je l'ai ajouté.

Passons à la suppression de la base (ici, sans keep alive – remarquez que je dois relancer telnet à chaque requête):

sh$  telnet couchdb 5984
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
DELETE /tourism-db 

HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Date: Sun, 18 Apr 2010 21:11:25 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 12
Cache-Control: must-revalidate

{"ok":true}
Connection closed by foreign host.
sh$ telnet couchdb 5984
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /_all_dbs

HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Date: Sun, 18 Apr 2010 21:15:23 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 3
Cache-Control: must-revalidate

[]
Connection closed by foreign host.

JSON

Avant d'aller plus loin, un mot rapide sur le format des données renvoyées par CouchDB:

sh$ curl -X GET http://couchdb:5984/
{"couchdb":"Welcome","version":"0.10.1"}

CouchDB utilise des données au format JSON (JavaScript Object Notation). Comme son nom l'indique, ce format reprend une syntaxe similaire à celle de JavaScript pour représenter des données. Par rapport à d'autres alternatives populaires comme XML, JSON se veut plus compact et plus lisible (pour un humain). Par ailleurs, si JSON utilise une syntaxe issue de JavaScript, il existe des parser pour la plupart des langages.

Bien que relativement intuitive, si vous désirez une explication plus complète sur la syntaxe de JSON, je vous renvoie sur wikipedia ou sur la RFC4627The application/json Media Type for JavaScript Object Notation (JSON).

Ajouter un document

La beauté des architectures REST est d'utiliser le même vocabulaire pour manipuler de manière uniforme les diverses ressources gérées. Nous avons utilisé les méthodes PUT et DELETE pour créer et supprimer des bases de données. Nous utiliserons également PUT et DELETE pour créer et supprimer des documents:

# Ajout d'une base dans CouchDB
sh$ curl -X PUT http://couchdb:5984/tourism-db
{"ok":true}
# Ajout d'un enregistrement dans la base
sh$ curl -X PUT http://couchdb:5984/tourism-db/hotel-de-la-mer
{"error":"bad_request","reason":"invalid UTF-8 JSON"}

Oups! Problème... Comment cela? Il ne serait pas possible d'ajouter un nouvel enregistrement dans la base avec une requête PUT? Rassurez-vous, c'est possible. Mais examinons mieux le message d'erreur:

{"error":"bad_request","reason":"invalid UTF-8 JSON"}

Pour l'ajout d'un enregistrement ... pardon ... d'un document, CouchDB attend deux choses:

Ici, nous avons donné l'identifiant de la ressource. Mais aucun contenu JSON valide. D'où le message d'erreur.

Remarque:

Les caractères utilisables dans l'identifiant possèdent les mêmes restrictions que pour une URI. D'ailleurs, en fait, l'identifiant est une URI!

Imaginons que nous souhaitions définir l'enregistrement hotel-de-la-mer avec les caractéristiques suivantes:

nom Hôtel de la Mer
chambres 12
divers
1 vue sur mer
2 accès wifi

Cela s'exprimerait ainsi en JSON:

{
    "nom": "Hôtel de la Mer",
    "chambres": 12,
    "divers": ["vue sur mer", "accès wifi"]
}

La requête adéquate devient donc:

sh$ curl -X PUT http://couchdb:5984/tourism-db/hotel-de-la-mer \
         -d '
         {
             "nom": "Hôtel de la Mer",
             "chambres": 12,
             "divers": ["vue sur mer", "accès wifi"]
         }'
{"ok":true,"id":"hotel-de-la-mer","rev":"1-7b182be1c40992eb715f6f1eb05ba109"}

Et maintenant, utilisons telnet pour ajouter le "Restaurant de la Plage":

nom Restaurant de la Plage
couverts 20
divers
1 vue sur mer
2 spécialité de fruits de mer

Ce qui devient en JSON:

{
    "nom": "Restaurant de la Plage",
    "couverts": 20,
    "divers": ["vue sur mer", "spécialité de fruits de mer"]
}

La requête adéquate serait:

sh$ telnet couchdb 5984
PUT /tourism-db/restaurant-de-la-plage
Host: couchdb:5984
Content-Type: application/json;charset=utf8
Content-Length: 126

{
   "nom": "Restaurant de la Plage",
   "couverts": 20,
   "divers": ["vue sur mer", "spécialité de fruits de mer"]
}
HTTP/1.1 201 Created
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Location: http://couchdb:5984/tourism-db/restaurant-de-la-plage
Etag: "1-a4ae52249943d93224c644af7fcc5137"
Date: Sun, 18 Apr 2010 21:58:31 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 85
Cache-Control: must-revalidate

{"ok":true,"id":"restaurant-de-la-plage","rev":"1-a4ae52249943d93224c644af7fcc5137"}
Connection closed by foreign host.

Content-Length

Avec les requêtes PUT, HTTP devient pénible à utiliser avec telnet. En effet, il est nécessaire de préciser dans l'en-tête Content-Length de la requête la taille (en octet) des données. Non seulement il faut compter – mais en plus penser à prendre en compte qu'en UTF-8 les lettres accentuées occupent 2 octets, et que le retour à la ligne est composé lui aussi de deux caractères (<CR><LF>). Autant de sources d'erreurs et de frustration...

Si vous êtes observateur vous avez peut-être remarqué que les deux documents que nous venons de créer ne sont pas dotés des mêmes attributs:

{
    "nom": "Hôtel de la Mer",
    "chambres": 12,
    "divers": ["vue sur mer", "accès wifi"]
}

{
    "nom": "Restaurant de la Plage",
    "couverts": 20,
    "divers": ["vue sur mer", "spécialité de fruits de mer"]
}

L'hôtel possède des chambres. Et le restaurant un certain nombre de couverts. Logique, non? C'est un avantage de CouchDB par rapport à une base de données relationnelle: les enregistrements peuvent être polymorphes. Pour être plus formel, comme une base CouchDB ne possède pas de schéma, chaque document peut être doté de son propre jeu d'attributs, indépendemment des autres documents.

Relire un document

Relire les données ne pose aucun problème. Vous l'avez deviné, il faut utiliser une requête GET:

sh$ curl -X GET http://couchdb:5984/tourism-db/hotel-de-la-mer
{
 "_id":"hotel-de-la-mer",
 "_rev":"1-7b182be1c40992eb715f6f1eb05ba109",
 "nom":"H\u00f4tel de la Mer",
 "chambres":12,
 "divers":["vue sur mer","acc\u00e8s wifi"]
}
sh$ telnet couchdb 5984
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /tourism-db/restaurant-de-la-plage

HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Etag: "1-a4ae52249943d93224c644af7fcc5137"
Date: Sun, 18 Apr 2010 22:01:18 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 187
Cache-Control: must-revalidate

{"_id":"restaurant-de-la-plage","_rev":"1-a4ae52249943d93224c644af7fcc5137","nom":"Restaurant de la Plage","couverts":20,"divers":["vue sur mer","sp\u00e9cialit\u00e9 de fruits de mer"]}
Connection closed by foreign host.

Modifier un document

Optimistic locking 1.png

Verrouillage optimiste — Pour illustrer le verrouillage optimiste, considérons l'exemple classique d'un système de réservations hôtelières. Dans cet exemple, deux clients chargent de manière concurrente la fiche d'un hôtel. A cette étape, les deux clients ont une copie de la révision 1 du document.

Optimistic locking 2.png

Verrouillage optimiste — Toujours de manière concurrente, les deux clients décident de faire une réservation.

Optimistic locking 3.png

Verrouillage optimiste — Quand le premier client valide la réservation, le serveur de bases de données s'assure que la version que le client souhaite modifier est bien la version courante (version 1 sur le client et le serveur). Le serveur accepte la modification, et fait passer le numéro de révision de 1 à 2.

Optimistic locking 4.png

Verrouillage optimiste — Maintenant arrive la seconde demande de modification émanant du deuxième client. Or cette modification porte aussi sur la révision 1. Mais dans la base, le document est déjà à la révision 2. Le serveur détecte le conflit et refuse la modification. C'est ensuite au client malheureux de gérer ce refus — souvent en reprenant le processus du début... On comprend donc que ce mécanisme n'est efficace que quand les risques de conflits sont faibles. D'où le qualificatif d'optimiste: on espère que deux clients ne modifieront pas simultanément les mêmes données.

Pour la saison à venir, suite à d'importants travaux, l'Hôtel de la Mer offre désormais des chambres supplémentaires. Il est tentant d'utiliser nos connaissances actuelles:

sh$ curl -X PUT http://couchdb:5984/tourism-db/hotel-de-la-mer -d '
{
 "nom": "Hôtel de la Mer",
 "chambres": 14,
 "divers": ["vue sur mer", "accès wifi"]
}'
{"error":"conflict","reason":"Document update conflict."}

Effectivement, la ressource /tourism-db/hotel-de-la-mer existe déjà. On peut donc comprendre que CouchDB refuse de l'ajouter à nouveau. Mais alors comment modifier un document?

Rassurez-vous, c'est possible. Mais avant une petite digression sur la notion de révision dans CouchDB. Comme tout serveur de bases de données, CouchDB se trouve confronté à un problème: comment gérer les modifications de données concurrentes. En clair, que se passe-t-il si deux clients décident de modifier le même document simultanément?

Pour résoudre ce problème, CouchDB met en oeuvre une technique appelée verrouillage optimiste. Celle-ci consiste à ajouter à chaque document un numéro de révision. Quand un client demande la modification d'un document, celui-ci doit obligatoirement indiquer quelle révision il souhaite modifier. CouchDB vérifie alors que la révision que veut modifier le client est bien la plus récente stockée dans la base (la révision courante). Si ce n'est pas le cas, la modification est refusée. Si au contraire, la modification est acceptée, CouchDB effectue les changements demandés, et assigne un nouveau numéro de révision au document.

Qu'est ce que ça change? Et bien imaginez deux clients qui veulent modifier un document. Tous les deux ont chargé la révision 1 de ce document. Chacun de son côté, chaque client veut modifier ce document. D'une manière ou d'une autre, une des deux demandes va être traitée la première par CouchDB. Celle-ci est acceptée. Et CouchDB fait passer la révision du document dans la base à 2. Maintenant CouchDB traite la seconde demande de modification. Or cette modification porte aussi sur la révision 1. Mais dans la base, le document est déjà à la révision 2. CouchDB détecte le conflit et refuse la modification. C'est ensuite au client malheureux de gérer ce refus...

Pour en revenir à notre problème – mettre à jour un document – CouchDB n'acceptera de le faire qu'à condition:

  1. De lui indiquer quelle révision nous souhaitons modifier;
  2. Que la révision que nous souhaitons modifier soit bien la plus récente enregistrée.

Reste une question: comment connaître le numéro de révision? Et bien, celui-ci apparaît quand on récupère (GET) le document:

sh$ curl -X GET http://couchdb:5984/tourism-db/hotel-de-la-mer
{
 "_id":"hotel-de-la-mer",
 "_rev":"1-7b182be1c40992eb715f6f1eb05ba109",
 "nom":"H\u00f4tel de la Mer",
 "chambres":12,
 "divers":["vue sur mer","acc\u00e8s wifi"]
}

En-tête ETag

CouchDB renvoie aussi en réponse aux requêtes portant sur un document le numéro de révision de ce dernier dans l'en-tête ETag.

Ceci peut s'avérer plus pratique pour un traitement automatisé. En particulier parce que selon la méthode HTTP employée, la révision dans le contenu de la réponse s'appelle parfois rev, parfois _rev:

sh$ curl -D - -X PUT http://couchdb:5984/tourism-db/camping-l-ephemere          -d '
         {
             "nom": "Camping l'\Éphémère",
             "places": 60,
             "divers": ["piscine", "commerces alimentaires"]
         }'
HTTP/1.1 201 Created
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Location: http://couchdb:5984/tourism-db/camping-l-ephemere
Etag: "1-0eaa8801c9274edd9b43caff6dc1a8e7"
Date: Mon, 19 Apr 2010 10:00:08 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 81
Cache-Control: must-revalidate

{"ok":true,"id":"camping-l-ephemere","rev":"1-0eaa8801c9274edd9b43caff6dc1a8e7"}
sh$ curl -D - -X PUT http://couchdb:5984/tourism-db/camping-l-ephemere          -d '
         {
             "nom": "Camping l'\Éphémère",
             "places": 70,
             "divers": ["piscine", "commerces alimentaires"],
             "ouverture": ["juillet", "août"],
             "_rev" : "1-0eaa8801c9274edd9b43caff6dc1a8e7"
         }'
HTTP/1.1 201 Created
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Location: http://couchdb:5984/tourism-db/camping-l-ephemere
Etag: "2-6b0ee7b21a18acc725245de0ca7f22d5"
Date: Mon, 19 Apr 2010 10:11:01 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 81
Cache-Control: must-revalidate

{"ok":true,"id":"camping-l-ephemere","rev":"2-6b0ee7b21a18acc725245de0ca7f22d5"}
sh$ curl -D - -X GET http://couchdb:5984/tourism-db/camping-l-ephemere
HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Etag: "2-6b0ee7b21a18acc725245de0ca7f22d5"
Date: Mon, 19 Apr 2010 10:11:39 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 209
Cache-Control: must-revalidate

{"_id":"camping-l-ephemere","_rev":"2-6b0ee7b21a18acc725245de0ca7f22d5","nom":"Camping l'\u00c9ph\u00e9m\u00e8re","places":70,"divers":["piscine","commerces alimentaires"],"ouverture":["juillet","ao\u00fbt"]}
sh$ curl -D - -X DELETE http://couchdb:5984/tourism-db/camping-l-ephemere?rev="2-6b0ee7b21a18acc725245de0ca7f22d5"
HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Etag: "3-9ce1e9e335c0f0240bc201bc31d21104"
Date: Mon, 19 Apr 2010 10:12:10 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 81
Cache-Control: must-revalidate

{"ok":true,"id":"camping-l-ephemere","rev":"3-9ce1e9e335c0f0240bc201bc31d21104"}

Muni de cette information, nous pouvons retenter notre requête PUT, mais en précisant le numéro de révision:

sh$ curl -X PUT http://couchdb:5984/tourism-db/hotel-de-la-mer -d ' 
{
 "nom": "Hôtel de la Mer",
 "chambres": 14,
 "divers": ["vue sur mer", "accès wifi"],
 "_rev": "1-7b182be1c40992eb715f6f1eb05ba109"
}'
{"ok":true,"id":"hotel-de-la-mer","rev":"2-6280d638ba94df12e95ea37beb5da484"}

Remarquez au passage que CouchDB renvoie systématiquement le numéro de révision du document après modification. Et vous observerez que celui-ci a logiquement changé.

Supprimer un document

Poursuivons sur notre lancée. Supprimer ≡ DELETE. Considérons donc l'exemple de l'Hôtel du Borgne – établissement rustique s'il en est...

sh$ curl -X PUT http://couchdb:5984/tourism-db/hotel-du-borgne -d ' 
{
  "nom": "Hôtel du Borgne",
  "chambres": 8,
  "divers": ["rustique"]
}'
{"ok":true,"id":"hotel-du-borgne","rev":"1-3ef45a96d51adf0bc8f76d137d9bf822"}

... et qui, décidément n'est pas digne de figurer dans notre base de données:

sh$ curl -X DELETE http://couchdb:5984/tourism-db/hotel-du-borgne
{"error":"conflict","reason":"Document update conflict."}

Hein? Impossible de supprimer cet hôtel? Malgré un standing largement inférieur à celui de nos autres prestations?

Si vous avez suivi ma digression précédente sur les révisions peut-être vous doutez-vous de la réponse: Pour supprimer un document dans CouchDB, il faut préciser non seulement le document, mais aussi la révision à supprimer.

Petite subtilité ici, la révision est précisée directement dans l'url:

sh$ curl -X DELETE http://couchdb:5984/tourism-db/hotel-du-borgne?rev=1-3ef45a96d51adf0bc8f76d137d9bf822
{"ok":true,"id":"hotel-du-borgne","rev":"2-2cb846ce73bd61cd52af9c18bcae8123"}

On peut s'assurer que le document est supprimé en essayant d'y accéder:

sh$ curl -X GET  http://couchdb:5984/tourism-db/hotel-du-borgne
{"error":"not_found","reason":"deleted"}

Piège:

Vous remarquerez que la réponse indique un nouveau numéro de révision pour le document après sa suppression. Et effectivement, vous pouvez recharger ce document explicitement:

sh$ curl -X GET  http://couchdb:5984/tourism-db/hotel-du-borgne?rev=2-2cb846ce73bd61cd52af9c18bcae8123
{"_id":"hotel-du-borgne","_rev":"2-2cb846ce73bd61cd52af9c18bcae8123","_deleted":true}

La suppression dans CouchDB revient donc à insérer une nouvelle révision du document, avec le marqueur "_deleted":true. La suppression ne sera définitive qu'au prochain compactage de la base.

Comme pour tous nos autres exemples, voici la version HTTP brute qui permet d'effectuer la suppression. Considérons donc un restaurant tout aussi douteux que notre précédent hôtel. A savoir le "bien nommé" Bistro Gastro:

sh$ curl -X PUT http://couchdb:5984/tourism-db/bistro-gastro \
          -d '
             {
                "nom": "Bistro Gastro",
                "couverts": 36
             }'
{"ok":true,"id":"bistro-gastro","rev":"1-2d2107ebfa7ea159d5f89ab2dce0235e"}

Yerk! Inutile de garder très longtemps cet établissement dans notre base:

sh$ telnet couchdb 5984
DELETE http://couchdb:5984/tourism-db/bistro-gastro
If-Match: 1-2d2107ebfa7ea159d5f89ab2dce0235e

HTTP/1.1 200 OK
Server: CouchDB/0.10.1 (Erlang OTP/R13B)
Etag: "2-06d724c997bedef525862afa89f6a7b2"
Date: Mon, 15 Mar 2010 15:01:40 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 76
Cache-Control: must-revalidate

{"ok":true,"id":"bistro-gastro","rev":"2-06d724c997bedef525862afa89f6a7b2"}

Ouf! La gastronomie est sauvée. Mais remarquez que j'en ai profité ici pour introduire une variante. En effet, pour une requête DELETE, il est possible de préciser soit la révision à supprimer dans la partie requête de l'URI (?rev=...), ou à l'aide de l'en-tête HTTP If-Match.

Conclusion

Avec la suppression se termine notre tour d'horizon des possibilités élémentaires de CouchDB. S'il y avait qu'une chose à retenir ici, ce serait qu'on accède à une base CouchDB exactement comme à un site web: même protocole—même sémantique—mêmes outils.

Bien entendu, dans la vrai vie, vous n'interrogerez certainement pas directement votre base avec curl ou en envoyant des requêtes avec telnet! Mais l'idée ici était surtout de vous montrer qu'une base CouchDB s'interroge avec des requêtes HTTP standards. Et que les réponses sont également conformes au protocole HTTP. Autrement dit, n'importe quel outil ou API cliente HTTP va permettre de se connecter à une base CouchDB. Aussi bien en lecture qu'en modification. Et quand au contenu de la base, celui-ci ne requiert qu'une bibliothèque JSON pour pouvoir être parsé facilement.

API HTTP? API JSON? En cette époque d'omniprésence du web, quelque soit votre langage de prédilection, ce serait bien le diable de ne pas trouver les bibliothèques adéquates! Alors, pourquoi attendre plus pour explorer les possibilités d'accès à CouchDB à partir de votre langage préféré...

Ressources