Au coeur d'une application, on trouve les règles métiers.

Ces règles métiers font que l’application répond aux besoins des futurs utilisateurs. Ces règles métiers vont venir s’appliquer sur un modèle de données (ensemble de cases class et sealed traits qui définissent  nos entités et répondent au vocabulaire de ce contexte métier).

En définissant ce modèle, on va avoir un ensemble d’attributs comme : nom, prénom, âge, etc.. (ces attributs seront très spécifiques au contexte métier). Un attribut se définit avec deux principaux éléments, son nom et son type. Le type va indiquer un certain nombre de propriétés sur lesquelles on se basera par la suite pour implémenter nos règles métiers.

D’une manière générale, on trouve un ensemble de types simples comme String, Int, Long, Float, etc.. et de part leur définition, ils nous permettent directement de valider certains predicats.

Par exemple, lorsque l’on définit qu’un attribut est de type String, on a la certitude que la valeur qui sera contenue dans l’attribut sera un ensemble de caractères. Le principe est intéressant, mais nos besoins en terme de règles métiers sont plus larges.

On retrouve souvent des méthodes qui vont étendre ces règles de validation, comme vérifier si l’attribut est ne contient pas une chaine vide ou que la taille maximale de la chaine est bien de X ou encore si les caractères sont en majuscule ou non.Ces règles ne sont pas très complexes à écrire mais sont souvent évaluées au runtime et se trouve en dehors de notre bien aimé système de types.

C’est précisément ici que la librairie Refined intervient.

Refined est une librairie inspirée par la librairie Haskell portant le même nom et elle  a pour objectif de porter ces règles au niveau du système de types avec le grand intérêt qu’un certain nombre de choses puissent être vérifiées et validées à la compilation.Refined va nous permettre de définir des prédicats directement dans la déclaration des types des attributs :

Par exemple, ici nous voulons spécifier que l’attribut âge ne peut pas être négatif :

scala> case class Person(age: Int refined Positive)
scala> Person(-10)
error: Predicate failed: (-10 > 0).

C’est un exemple simple. Evidement on peut aller plus loin, voici quelques exemples.

Le classique chaine non vide :

scala> refineMV[NonEmpty]("Hello")
res2: String Refined NonEmpty = Hello

scala> refineMV[NonEmpty]("")
:39: error: Predicate isEmpty() did not fail.
            refineMV[NonEmpty]("")

Définir une plage de valeur :

scala> type ZeroToOne = Not[Less[W.`0.0`.T]] And Not[Greater[W.`1.0`.T]]
defined type alias ZeroToOne

scala> refineMV[ZeroToOne](1.8)
:40: error: Right predicate of (!(1.8 < 0.0) && !(1.8 > 1.0)) failed: Predicate (1.8 > 1.0) did not fail.
       refineMV[ZeroToOne](1.8)

Définir une Regex :

scala> val r1: String Refined Regex = "(a|b)"
r1: String Refined Regex = (a|b)

scala> val r2: String Refined Regex = "(a|b"
:38: error: Regex predicate failed: Unclosed group near index 4
(a|b
    ^
      val r2: String Refined Regex = "(a|b"
                                      ^

scala> val u1: String Refined Url = "htp://example.com"
:38: error: Url predicate failed: unknown protocol: htp
       val u1: String Refined Url = "htp://example.com"

Ces exemples tirés de la page Github du projet ne sont qu’un sous ensemble de ce qu’il est possible de faire.

La librairie fournit un grand nombre de prédicats permettant d’exprimer des règles plus riches et orientés métier. Les endroits de prédilection ou l’on pourra utiliser Refined sont le parsing de données et également le mapping de données.

Refined propose un grand nombre de module permettant de s’intégrer avec ce type de librairie.

Cet article est une courte présentation de la librairie. Je vous encourage à descendre dans la mine en consultant la page du projet.

Soyez intraitable et faite travailler votre compilateur (dès que vous le pouvez 🙂 )