7
Dec/09
0

Mnesia, un dictionnaire transactionnel

Mnesia est une bête différente d’une base de données relationnelle. Il s’agit d’un dictionnaire clé/valeur transactionnel.

La conséquence de cette phrase est que toutes les opérations doivent être exécutées dans une transaction (même les lectures) et que les meilleures structures de données exploitables sont les enregistrements dénormalisés (pas de contrainte d’intégrité).

De même, il n’existe pas de système de requête type SQL, et la maîtrise des comprehension list est nécessaire afin de sélectionner des éléments.

Gestion des identités

Comme pour toute base de données, c’est une très mauvaise idée d’utiliser des données utilisateur en tant que clé d’identification de l’enregistrement.

Pour l’enregistrement suivant :

-record(user, {name, pass_hash, salt}).

la clé primaire correspond automatiquement au premier champ, donc le « nom » de l’utilisateur. Il est courant dans les différents sites de ne pas pouvoir changer son login une fois identifié (si utilisé comme clé primaire) ; cela permet de « simplifier » quelques requêtes quand à l’unicité du compte, mais je ne considère pas cela comme une bonne pratique.

Le mieux est de générer un identifiant indépendant et sans signification. Cet identifiant sera un UUID afin de rester au plus simple et au plus fiable au niveau implémentation.

Pour cela, on utilise un module de génération disponible sur le net : le notre sera celui de akreiling trouvé sur github (pour info, ce choix s’est fait après une recherche google seulement, sans trop d’analyse supplémentaire pour l’instant !!!! oui, je n’en suis pas très fier).

L’enregistrement devient :

-record(user, {uid, name, pass_hash, salt}).

et la création se fait en ajoutant un uuid :

#user{uid=uuid:srandom(), ...}

Gestion des erreurs

Une transaction mnesia ne semble pas générer d’erreurs. Pour faire planter le programme il est important de déclencher l’exception via une clause incorrecte.

Obligation de créer une API

Les fonctions mnesia sont vraiment très basiques. Il est obligatoire pour tout programme erlang de créer une API d’utilisation un peu plus flexible.

Toutes les applications seront donc plus ou moins obligées de redéfinir les méthodes de lecture, écriture, récupération globale d’éléments d’une table, de transaction, etc.

De plus, une des grosses difficultés de mnesia (c’est subjectif, je vous l’accorde) est de pouvoir écrire le code d’une façon lisible. On passe un temps trop important sur cette partie.

On va donc redéfinir ces opérations basiques comme suit :

write(Record) ->
  mnesia:transaction(fun() ->
    mnesia:write(Record)
  end).
 
tx(F) ->
  {atomic, Result} = mnesia:transaction(F),
  case Result of
    {error, _} -> throw(Result);
    _ -> Result
  end.
 
find(Q) ->
  F = fun() -> qlc:e(Q) end,
  case mnesia:transaction(F) of
    {atomic, Result} -> Result;
    _ -> []
  end.
 
find_all(Table) -> find(qlc:q([X || X <- mnesia:table(Table)])).

Si on ne fait pas ces méthodes, il sera beaucoup plus compliqué et lourd d’utiliser ce dictionnaire.

Ces fonctions ont été induites par l’utilisation de mnesia dans l’application. Un exemple d’utilisation de ces fonctions sera fourni ultérieurement.

Afin de pouvoir compiler le programme, il est important de pouvoir inclure les définitions des QLC. Selon le système, il peut être nécessaire d’introduire ce code en début de fichier :

-include("/usr/lib/erlang/lib/stdlib-1.16.1/include/qlc.hrl").

Le problème est l’utilisation d’un chemin en dur. Il est préférable de créer un lien symbolique au niveau du répertoire source :

cd src
ln -s /usr/lib/erlang/lib/stdlib-1.16.1/include

Puis d’utiliser ce chemin relatif dans le source :

-include("include/qlc.hrl").
Filed under: Erlang, mnesia