Cet article est publié sous licence CC BY-NC-SA
Depuis un moment j’ai envie de tester le langage Crystal. Celui-ci s’approchant de sa version 1.0, je me suis dit qu’il était temps de regarder ce qu’il était possible de faire dans le domaine du web.
Après quelques recherches, trois frameworks sortent aujourd’hui du lot :
Pour ma part j’ai choisi de faire mes premiers pas dans le web en Crystal avec le framework Lucky. Plusieurs raisons m’ont amené à faire ce choix :
En effet en arrivant sur la page d’accueil on peut y lire :
Lucky is a web framework written in Crystal. It helps you work quickly, catch bugs at compile time, and deliver blazing fast responses.
(NdT : Lucky est un framework web écrit en Crystal. Il vous aidera à avancer rapidement, repérer la plupart des bugs durant la phase compilation et servira des réponses étonnamment rapides)
Quoi de mieux pour nous donner envie. Si vous n’êtes toujours pas convaincu, je vous invite à lire la page d’introduction dédiée beaucoup plus complète que la mienne mais en anglais uniquement.
Pour une question de simplicité je vous invite à suivre les instructions détaillées fournies par la documentation. Il faudra également prévoir une installation fonctionnelle de PostgreSQL en local ou via Docker.
Pour toutes informations complémentaires je vous invite à lire la documentation sur laquelle je m’appuie pour cette partie.
Le projet en question sera une API web toute simple : Des utilisateurs et une table centralisée de bookmarks dans laquelle les utilisateurs pourront enregistrer des liens et aller les retrouver plus tard.
On commence donc par créer le projet via le CLI Lucky :
lucky init
On suit le wizard qui nous propose différentes configurations par défaut :
Project name?: bookmarks
API only or full support for HTML and Webpack? (api/full): api
Generate authentication? (y/n): y
On configure ensuite notre base de données via le fichier config/database.cr
. Le format est assez simple et la configuration de base satisfaisante dans la plupart des situations. De mon côté, la configuration par défaut était suffisante pour que je n’aie qu’à lancer un container PostgreSQL :
docker run --rm --name postgres -p 5432:5432 -e POSTGRES_PASSWORD="postgres" -d postgres
On note dans le fichier les références à Avram qui est tout simplement l’ORM de Lucky.
Il faut ensuite lancer le script de mise en place :
script/setup
Et enfin si tout s’est bien déroulé on peut démarrer notre application
lucky dev
Voilà le résultat avec un appel
curl --request GET --url http://localhost:5000/
{
"hello": "Hello World from Home::Index"
}
Le modèle User
et le système d’authentification ayant déjà été créés par le wizard nous allons voir comment utiliser ce qui a été généré. Pour créer un utilisateur :
curl --request POST \
--url http://localhost:5000/api/sign_ups \
--header 'content-type: application/json' \
--data '{
"user": {
"email":"test@example.org",
"password":"password",
"password_confirmation": "password"
}
}'
Vous devriez avoir une réponse contenant le jeton d’authentification :
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.y12ClIjF7Mk8NLa1VwHO0MhsrUtpvEIti4PwkjuYnLs"
}
Et pour générer un autre jeton d d’authentification il suffit de se connecter :
curl --request POST \
--url http://localhost:5000/api/sign_ins \
--header 'content-type: application/json' \
--data '{
"user": {
"email": "test@example.org",
"password": "password"
}
}'
Nous allons nous attaquer au Bookmark
. Pour simplifier le processus Lucky, tout comme Rails, nous propose des générateurs :
lucky gen.model Bookmark
Nous avons quatre fichiers qui ont étés générés par cette tâche, deux assez classiques :
Et enfin deux qui sortent un peu de l’ordinaire pour un développeur Rails :
Pour comprendre ce que sont ces fichiers et à quoi ils servent il faut se plonger un peu dans la philosophie de Lucky, qui comme d’autres frameworks a fait le choix de découpler au plus possible la logique métier d’une application de ces différentes entrées/sorties (en général dans le cadre du web on parle de requêtes HTTP et de bases de données). Ces deux fichiers sont donc liés directement à l’ORM (Avram) plutôt qu’au framework web (Lucky). Ici on a donc un fichier d’opération, qui contiendra les opérations nécessitant d’écrire en base (création et mise à jour) et un fichier de requête dans lequel nous pourrons écrire les différentes requêtes (SQL ici) liées à notre modèle en lecture uniquement.
Pour nos Bookmarks
, voilà la migration à écrire (comme toujours, pour plus d’information sur les migrations je vous invite à lire la documentation dédiée :
# db/migrations/20200713134247_create_bookmarks.cr
class CreateBookmarks::V20200713134247 < Avram::Migrator::Migration::V1
def migrate
# Learn about migrations at: https://luckyframework.org/guides/database/migrations
create table_for(Bookmark) do
primary_key id : Int64
add link : String, unique: true
add description : String?
add_timestamps
end
end
def rollback
drop table_for(Bookmark)
end
end
Pour information le type String?
vient de Crystal et représente une chaine de caractères nullable. Ici ça nous permettra donc de spécifier à la base de données qu’on accepte la valeur null
dans ce champs.
Sans oublier de signaler ces attributs à notre nouveau modèle (à priori pas d’inférence possible ici) :
# src/models/bookmark.cr
class Bookmark < BaseModel
table do
column link : String
column description : String?
end
end
Nous pouvons maintenant jouer la migration et relancer le serveur
lucky db.migrate && lucky dev
Pour pouvoir manipuler nos marque-pages, il va falloir passer par le concept d’action. En Lucky une action est l’équivalent d’une route et de son action dans le contrôleur. La route elle-même est inférée depuis le nom de la classe de l’action si on la nomme selon les conventions du framework (applicable uniquement pour les actions REST) (documentation). Encore une fois nous avons un générateur à disposition :
lucky gen.action.api Api::Bookmarks::Index
Et dans notre action :
# src/actions/api/bookmarks/index.cr
class Api::Bookmarks::Index < ApiAction
route do
# BookmarkQuery.new est un raccourci pour lire tous les Bookmarks, voir https://luckyframework.org/guides/database/querying-records#select-shortcuts
# BaseSerializer.for_collection est un raccourci pour sérialiser une collection, voir https://luckyframework.org/guides/json-and-apis/rendering-json#rendering-a-collection-with-serializers
json(BookmarkSerializer.for_collection(BookmarkQuery.new))
end
end
Pour finir nous devons définir notre sérialiseur :
# src/serializers/bookmark_serializer.cr
class BookmarkSerializer < BaseSerializer
def initialize(@bookmark : Bookmark)
end
def render
{link: @bookmark.link, description: @bookmark.description}
end
end
Toutes les routes étant authentifiées par défaut on n’oublie pas son jeton et ça donne :
# Il faudra bien sur changer le jeton pour le vôtre
curl --request GET \
--url http://localhost:5000/api/bookmarks \
--header 'authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.y12ClIjF7Mk8NLa1VwHO0MhsrUtpvEIti4PwkjuYnLs'
Et on reçoit bien un tableau vide parce qu’on ne sait pas encore créer des marque-pages.
[]
Comme pour l’index, on passe par le générateur :
lucky gen.action.api Api::Bookmarks::Create
Et voilà le code :
class Api::Bookmarks::Create < ApiAction
route do
# SaveModel est l'opération créée par défaut à la création d'un modèle.
# Il suffit ensuite d'en hériter pour customiser, voir: https://luckyframework.org/guides/database/validating-saving
bookmark = SaveBookmark.create!(params)
json(BookmarkSerializer.new(bookmark))
end
end
Et enfin pour tester on crée un marque-page :
# Encore une fois on change son jeton !
curl --request POST \
--url http://localhost:5000/api/bookmarks \
--header 'authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.y12ClIjF7Mk8NLa1VwHO0MhsrUtpvEIti4PwkjuYnLs' \
--header 'content-type: application/json' \
--data '{
"bookmark": {
"link": "https://synbioz.com",
"description": "Synbioz"
}
}'
Et on peut relancer notre requête d’index pour vérifier que tout fonctionne :
# C'est la dernière fois que je le dis, on oublie pas de changer son jeton !
curl --request GET \
--url http://localhost:5000/api/bookmarks \
--header 'authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.y12ClIjF7Mk8NLa1VwHO0MhsrUtpvEIti4PwkjuYnLs'
Et on reçoit bien :
[
{
"link": "https://synbioz.com",
"description": "Synbioz"
}
]
Globalement même si j’ai eu un peu de mal à me mettre dedans, je me suis rapidement fait aux différents concepts mis en avant par le framework. Je suis agréablement surpris par l’outillage mis à disposition (lucky help
) depuis le CLI et la marche d’approche qui finalement n’est pas si grande (entre autres grâce à Crystal et sa syntaxe proche du Ruby, mais pas que !). C’était attendu aussi mais les temps de réponses m’ont semblé plus que correct : sur les différentes requêtes que j’ai lancées je suis resté entre 2 et 20 millisecondes. Après il faut bien-sûr relativiser il n’y a rien de vraiment complexe dans notre application. Je voudrais aussi donner un bon point à la documentation qui, même si elle n’est pas au niveau des guides Rails, est plus que correcte et largement suffisante pour débuter (je me suis servi uniquement de celle-ci pour écrire cet article).
En revanche le point négatif inhérent à Crystal, la compilation. À la moindre commande il faut compiler. C’est un peu frustrant quand on vient de Ruby, je ne sais pas du tout comment est géré l’outillage web autour des autres langages compilés à la mode (Go, Rust, …) mais c’est quand même énervant de devoir attendre la compilation sur un simple lucky routes
(équivalent de rails routes
). Pour relativiser, j’imagine quand même que sur un projet d’envergure, la différence doit être moins flagrante vu que Rails prend aussi beaucoup de temps à charger.
J’ai aussi trouvé le processus d’installation un peu compliqué, mais au final je pense que quelqu’un qui découvre Ruby aujourd’hui dira la même chose, c’est une question d’habitude.
De mon côté je vais prendre le temps de continuer cette petite application pour la rendre plus complexe et pour mieux tester les différentes facettes de Lucky, et quand je serai plus à l’aise j’espère revenir vers vous avec un nouvel article plus poussé et différents exemples de points positifs/négatifs.
L’équipe Synbioz.
Libres d’être ensemble.
Nos conseils et ressources pour vos développements produit.