SlideShare une entreprise Scribd logo
1  sur  26
Refined, des Types sur mesure
Scala IO 2019
@_mhdkassir
Pourquoi Refined ?
Même dans un langage fortement typé on est souvent amené à faire un effort de
validation sur les données
Prenons le cas de String
- Type très permissif
- On peut mettre tout et n’importe quoi
Et pourtant dans la plupart des cas on vise une partie de domain des valeurs possibles
Pourquoi Refined ?
Mais il y a toujours moyen d’envoyer des données pas cohérentes
=> Ajouter une étape de validation mais avec un modèle plus complexe cela devient du
boilerplate code
def registerSpeaker( name: String, handle: String) = {...}
Exemple 1
registerSpeaker("@johnDoe", "John Doe") // ça peut arriver :(
registerSpeaker("John Doe", "@johnDoe") // OK :)
Pourquoi Refined ?
Exemple 2
// Fichier de configuration
api {
timout-sec = -10 #timeout négative !
port = 9000
url = "htp://myapi.com" #invalid protocol !
}
final case class ApiSettings(timoutSec: Int, port: Int , url: String )
val conf = pureconfig.loadConfig[ApiSettings]("api")
=> L’objet de configuration est bien chargé, mais on aura des problèmes Runtime
Comment peut-on :
- Renforcer la précision des types
- Réduire le code de validation
- Détecter les incohérences le plus tôt possible (compile-time ?)
Refined, le concept
Au lieu de chercher à valider toute valeur possible dans le domaine d’un Type basique,
on peut intégrer la validation au niveau de la définition de Type!
Type Refined = Type Basique + Predicate
=> Le domaine de ce nouveau type est réduit aux valeurs qui respectent la règle de
validation associée
Refined En Pratique
La librairie Refined en Scala:
● https://github.com/fthomas/refined
● Un projet TypeLevel
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Positive
import eu.timepit.refined.refineMV
type PositiveInt = Int Refined Positive
Pour déclarer un refined type:
Pour instancier un refined value à partir d’un literal
val positive : PositiveInt = refineMV[Positive](5)
// positive: PositiveInt = 5
val fail = refineMV[Positive](-5)
// ERREUR de compilation : Predicate failed (-5 > 0)
Refined En Pratique
import eu.timepit.refined.api.Refined
import eu.timepit.refined.numeric.Positive
import eu.timepit.refined.auto._
type PositiveInt = Int Refined Positive
// avec l’import auto plus besoin d’utiliser explicitement refineMV[]
val positive: PositiveInt = 5
//positive: PositiveInt = 5
val result : Int = 15 + positive
// sans l'import d'auto il faut passer par .value
val result : Int = 15 + positive.value
L’objet auto permet la conversion automatique entre le refined type son type basique
Validation au compile-time
import eu.timepit.refined.api.{Refined, RefinedTypeOps}
import eu.timepit.refined.auto._
import eu.timepit.refined.W
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.string.StartsWith
type Name = String Refined NonEmpty
object Name extends RefinedTypeOps[Name,String]
type Handle = String Refined StartsWith[W.`"@"`.T]
object Handle extends RefinedTypeOps[Handle,String]
val name : Name = Name("John Doe") //name: Name = John Doe
val handle = Handle("@johnDoe") // handle: Handle = @johnDoe
val fail_handle = Handle("johnDoe")
// COMPILATION ERROR !
// Predicate failed: "johnDoe".startsWith("@").
Validation au compile-time
import eu.timepit.refined.api.{Refined, RefinedTypeOps}
import eu.timepit.refined.auto._
import eu.timepit.refined.W
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.string.StartsWith
type Name = String Refined NonEmpty
object Name extends RefinedTypeOps[Name,String]
type Handle = String Refined StartsWith[W.`"@"`.T]
object Handle extends RefinedTypeOps[Handle,String]
val name : Name = Name("John Doe") //name: Name = John Doe
val handle = Handle("@johnDoe") // handle: Handle = @johnDoe
val fail_handle = Handle("johnDoe")
// COMPILATION ERROR !
// Predicate failed: "johnDoe".startsWith("@").
L’objet compagnon défini en utilisant RefinedTypeOps permet d’avoir
Validation au run-time
Avec les Refined Types
- On ne peut plus passer des données incohérentes
- Les types sont précises et ne prennent que des valeurs pertinents
=> On passe d’un “Stringly Typed” modèle vers un “Strongly Typed” modèle
val input_name : String // valorisé au runtime
val input_handle: String // valorisé au runtime
val result: Either[String, Name] = Name.from(input_name)
def registerSpeaker(name:Name, handle: Handle) = {
....
}
val speaker: Either[String, Speaker] = for{
name <- Name.from(input_name)
handle <- Handle.from(input_handle)
} yield registerSpeaker(name, handle)
L’objet compagnon fournit aussi la méthode “from”
Predicates Numeriques
Dans le package eu.timepit.refined.numeric
Predicates simples par rapport à une valeur
Less[N] LessEqual[N]
Greater[N] GreaterEqual[N]
Positive Negative
Predicates sur une intervale
Interval.Open[L, H] Interval.OpenClosed[L, H]
Interval.ClosedOpen[L, H] Interval.Closed[L, H]
Predicates Logiques
Dans le package eu.timepit.refined.boolean
Not[P] negation of the predicate P
And[A, B] conjunction des Predicates A et B
Or[A, B] disjunction des Predicates A et B
AllOf[PS] conjunction des predicates dans PS
AnyOf[PS] disjunction des predicates dans PS
OneOf[PS] disjunction exclusive des predicates dans PS
Predicates de String & Collection
Dans le package eu.timepit.refined.string
StartsWith[S] EndsWith[S] Uri
MatchesRegex[S] Regex Url
Dans le package eu.timepit.refined.collection
Empty NonEmpty
Forall[P] Exists[P]
MinSize[N] MaxSize[N]
Exemples
type ID256 = String Refined And[NonEmpty, MaxSize[W.`256`.T]]
type CodePostale = String Refined MatchesRegex[W.`"[0-9]{5}"`.T]
type Latitude = Double Refined Interval.Closed[W.`-90d`.T, W.`90d`.T]
et ça peut aller loin ..
import shapeless.{::, HNil}
type TwitterHandle = String Refined AllOf[
StartsWith[W.`"@"`.T] ::
Not[MatchesRegex[W.`"(?i:.*twitter.*)"`.T]] ::
Not[MatchesRegex[W.`"(?i:.*admin.*)"`.T]] ::
Tail[Or[LetterOrDigit, Equal[W.`'_'`.T]]] ::
HNil ]
Intégration avec des librairies
● refined-pureconfig : lire les configuration avec des Refined types
● refined-scopt : lire les options de ligne de commande avec des Refined types
● refined-jsonpath : fournir un JSONPath predicate pour vérifier qu’un String est
un JSONPath valide
● refined-scalacheck : générer des valeurs arbitraires pour les Refined types avec
ScalaCheck.
● refined-cats
● refined-scalaz
Exemple PureConfig
// Fichier de configuration
api {
timout-sec = -10 #timeout négative
port = 9000
url = "htp://myapi.com" #invalid protocol
}
// case class avec des Refined Types
final case class RefinedApiSettings(timoutSec: PosInt,
port: Int Refined And[Greater[W.`1023`.T],Less[W.`65536`.T]],
url: String Refined Url)
val conf = pureconfig.loadConfig[ApiSettings]("api")
Exemple PureConfig
val conf = pureconfig.loadConfig[ApiSettings]("api")
//*
Left(
ConfigReaderFailures(
CannotConvert(
"-10",
"eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Greater[shapeless.nat._0]]",
"Predicate failed: (-10 > 0).",
),
Some("api.timout-sec")
),
List(
CannotConvert(
""htps://myapi.com"",
"eu.timepit.refined.api.Refined[String,eu.timepit.refined.string.Url]",
"Url predicate failed: unknown protocol: htps"
),
Some("api.url")
)
)
Refined avec Play
Pour utiliser les Refined types avec Play on utilise une librairie complémentaire
- https://github.com/kwark/play-refined
- SBT dependency : "be.venneborg" %% "play26-refined" % "0.3.0"
qui propose les fonctionnalités suivantes :
- Sérialisation/Désérialisation de JSON sous forme des Refined Types
- Binding/Unbinding des Refined Types dans les Forms
- Path/Query binding de Refined Types
- Traduire les erreurs Refined en erreurs Play standards
REX : Valider l’input d’un API Play avec Refined
import eu.timepit.refined.api.Refined
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.string.{StartsWith, Url}
import eu.timepit.refined.W
import play.api.libs.json.Json
case class SpeakerDTO (name: String Refined NonEmpty,
url: String Refined StartsWith[W.`"@"`.T])
object SpeakerDTO {
import be.venneborg.refined.play.RefinedJsonFormats._
implicit val speakerDTOfmt = Json.format[SpeakerDTO]
}
1- Définir les DTO avec des Refined Types :
REX Refined avec Play
import eu.timepit.refined.api.Refined
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.string.{StartsWith, Url}
import eu.timepit.refined.W
import play.api.libs.json.Json
case class SpeakerDTO (name: String Refined NonEmpty,
url: String Refined StartsWith[W.`"@"`.T])
object SpeakerDTO {
import be.venneborg.refined.play.RefinedJsonFormats._
implicit val speakerDTOfmt = Json.format[SpeakerDTO]
}
1- Définir les DTO avec des Refined Types :
2- Utiliser les Refined DTO dans la signature de services
def register = Action.async(parsers.json(SpeakerDTO.speakerDTOfmt)){
implicit req => Future{
// call backend service
createSpeaker(req.body)
Created
}
}
3- Utiliser les Refined DTO dans la signature de services
def createSpeaker(speakerDTO: SpeakerDTO) = {
.....
}
REX : Valider l’input d’un API Play avec Refined
// Status : 400 Bad Request
// Body :
{
"obj.handle": [
{
"msg": [
"error.invalid"
],
"args": [
"List(not starting with: @)"
]
}
]
}
Requete
Réponse
POST : http://localhost:9000/api/speaker
Body :
{
"name" : "John Doe",
"handle" : "invalid_handle"
}
Conclusion
Type Refined = Type Basique + Predicate
L’utilisation de Refined Types permet de :
- Intégrer les règles business dans la définition des Types
- Centraliser et simplifier le processus de validation des données
- Detecter plus rapidement les incoherences
References
- https://github.com/fthomas/refined
- http://fthomas.github.io/talks/2016-05-04-refined
- https://vlovgr.github.io/refined-types
- https://www.beyondthelines.net/programming/refined-types/
- https://github.com/kwark/play-refined
- https://kwark.github.io/refined-in-practice
- https://underscore.io/blog/posts/2017/03/07/refined-data-config-database.html
- https://medium.com/@Methrat0n/wtf-is-refined-5008eb233194
- https://blog.colisweb.com/type-your-business-6c39ddc84963
Merci :)
Questions ?
@_mhdkassir

Contenu connexe

Tendances

Outils pour développeur·se Android
Outils pour développeur·se Android Outils pour développeur·se Android
Outils pour développeur·se Android Macha DA COSTA
 
Monitoring d'applications/environnements PHP: APM et Pinba
Monitoring d'applications/environnements PHP: APM et PinbaMonitoring d'applications/environnements PHP: APM et Pinba
Monitoring d'applications/environnements PHP: APM et PinbaPatrick Allaert
 
Scala : programmation fonctionnelle
Scala : programmation fonctionnelleScala : programmation fonctionnelle
Scala : programmation fonctionnelleMICHRAFY MUSTAFA
 
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...
Interface fonctionnelle, Lambda expression, méthode par défaut,  référence de...Interface fonctionnelle, Lambda expression, méthode par défaut,  référence de...
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...MICHRAFY MUSTAFA
 
Javascript ne se limite pas à jquery
Javascript ne se limite pas à jqueryJavascript ne se limite pas à jquery
Javascript ne se limite pas à jqueryneuros
 
Communications Réseaux et HTTP avec PHP
Communications Réseaux et HTTP avec PHPCommunications Réseaux et HTTP avec PHP
Communications Réseaux et HTTP avec PHPjulien pauli
 
Per lameliorer
Per lameliorerPer lameliorer
Per lameliorerTECOS
 
Présentation Groovy
Présentation GroovyPrésentation Groovy
Présentation Groovyguest6e3bed
 
PHPTour 2011 - PHP5.4
PHPTour 2011 - PHP5.4PHPTour 2011 - PHP5.4
PHPTour 2011 - PHP5.4julien pauli
 
PHP 7.0 : aperçu des nouveautés
PHP 7.0 : aperçu des nouveautésPHP 7.0 : aperçu des nouveautés
PHP 7.0 : aperçu des nouveautésDidcode
 
Partie 4: Fonctions - Programmation orientée objet en C++
Partie 4: Fonctions - Programmation orientée objet en C++Partie 4: Fonctions - Programmation orientée objet en C++
Partie 4: Fonctions - Programmation orientée objet en C++Fabio Hernandez
 
Partie 10: Classes Génériques — Programmation orientée objet en C++
Partie 10: Classes Génériques — Programmation orientée objet en C++Partie 10: Classes Génériques — Programmation orientée objet en C++
Partie 10: Classes Génériques — Programmation orientée objet en C++Fabio Hernandez
 
Java (8) eXperiments - DevoxxFR 2016
Java (8) eXperiments - DevoxxFR 2016Java (8) eXperiments - DevoxxFR 2016
Java (8) eXperiments - DevoxxFR 2016François Sarradin
 
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...Frederic Hardy
 

Tendances (19)

Outils pour développeur·se Android
Outils pour développeur·se Android Outils pour développeur·se Android
Outils pour développeur·se Android
 
Monitoring d'applications/environnements PHP: APM et Pinba
Monitoring d'applications/environnements PHP: APM et PinbaMonitoring d'applications/environnements PHP: APM et Pinba
Monitoring d'applications/environnements PHP: APM et Pinba
 
Scala : programmation fonctionnelle
Scala : programmation fonctionnelleScala : programmation fonctionnelle
Scala : programmation fonctionnelle
 
Ch07
Ch07Ch07
Ch07
 
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...
Interface fonctionnelle, Lambda expression, méthode par défaut,  référence de...Interface fonctionnelle, Lambda expression, méthode par défaut,  référence de...
Interface fonctionnelle, Lambda expression, méthode par défaut, référence de...
 
La programmation fonctionnelle en javascript / PF
La programmation fonctionnelle en javascript / PFLa programmation fonctionnelle en javascript / PF
La programmation fonctionnelle en javascript / PF
 
Javascript ne se limite pas à jquery
Javascript ne se limite pas à jqueryJavascript ne se limite pas à jquery
Javascript ne se limite pas à jquery
 
Communications Réseaux et HTTP avec PHP
Communications Réseaux et HTTP avec PHPCommunications Réseaux et HTTP avec PHP
Communications Réseaux et HTTP avec PHP
 
Per lameliorer
Per lameliorerPer lameliorer
Per lameliorer
 
Présentation Groovy
Présentation GroovyPrésentation Groovy
Présentation Groovy
 
Ruby Pour RoR
Ruby Pour RoRRuby Pour RoR
Ruby Pour RoR
 
PHPTour 2011 - PHP5.4
PHPTour 2011 - PHP5.4PHPTour 2011 - PHP5.4
PHPTour 2011 - PHP5.4
 
PHP 7.0 : aperçu des nouveautés
PHP 7.0 : aperçu des nouveautésPHP 7.0 : aperçu des nouveautés
PHP 7.0 : aperçu des nouveautés
 
Playing With PHP 5.3
Playing With PHP 5.3Playing With PHP 5.3
Playing With PHP 5.3
 
Nouveautés php 7
Nouveautés php 7Nouveautés php 7
Nouveautés php 7
 
Partie 4: Fonctions - Programmation orientée objet en C++
Partie 4: Fonctions - Programmation orientée objet en C++Partie 4: Fonctions - Programmation orientée objet en C++
Partie 4: Fonctions - Programmation orientée objet en C++
 
Partie 10: Classes Génériques — Programmation orientée objet en C++
Partie 10: Classes Génériques — Programmation orientée objet en C++Partie 10: Classes Génériques — Programmation orientée objet en C++
Partie 10: Classes Génériques — Programmation orientée objet en C++
 
Java (8) eXperiments - DevoxxFR 2016
Java (8) eXperiments - DevoxxFR 2016Java (8) eXperiments - DevoxxFR 2016
Java (8) eXperiments - DevoxxFR 2016
 
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
 

Similaire à Refined, des types sur mesure

Visual Studio 2008 Overview
Visual Studio 2008 OverviewVisual Studio 2008 Overview
Visual Studio 2008 OverviewGregory Renard
 
Les nouveautés de Groovy 2 -- Mix-IT 2013
Les nouveautés de Groovy 2 -- Mix-IT 2013Les nouveautés de Groovy 2 -- Mix-IT 2013
Les nouveautés de Groovy 2 -- Mix-IT 2013Guillaume Laforge
 
Node.js, le pavé dans la mare
Node.js, le pavé dans la mareNode.js, le pavé dans la mare
Node.js, le pavé dans la mareValtech
 
ZendFramework2 - Présentation
ZendFramework2 - PrésentationZendFramework2 - Présentation
ZendFramework2 - Présentationjulien pauli
 
Introduction à JavaScript
Introduction à JavaScriptIntroduction à JavaScript
Introduction à JavaScriptAbdoulaye Dieng
 
ALT.Net Juin 2012 - Specflow
ALT.Net Juin 2012 - SpecflowALT.Net Juin 2012 - Specflow
ALT.Net Juin 2012 - SpecflowMathias Kluba
 
Développer sereinement avec Node.js
Développer sereinement avec Node.jsDévelopper sereinement avec Node.js
Développer sereinement avec Node.jsJulien Giovaresco
 
Patterns and OOP in PHP
Patterns and OOP in PHPPatterns and OOP in PHP
Patterns and OOP in PHPjulien pauli
 
Enrichir vos contenus Wordpress avec les API - WPTech 2015
Enrichir vos contenus Wordpress avec les API - WPTech 2015Enrichir vos contenus Wordpress avec les API - WPTech 2015
Enrichir vos contenus Wordpress avec les API - WPTech 2015PXNetwork
 
02 Spécificité du C++ COURS SYS SYSSSSSS
02 Spécificité du C++  COURS SYS SYSSSSSS02 Spécificité du C++  COURS SYS SYSSSSSS
02 Spécificité du C++ COURS SYS SYSSSSSSAyoubElmrabet6
 
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...fdussert
 
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !Paris Salesforce Developer Group
 
Partie1 TypeScript
Partie1 TypeScriptPartie1 TypeScript
Partie1 TypeScriptHabib Ayad
 
Php 7.4 2020-01-28 - afup
Php 7.4   2020-01-28 - afupPhp 7.4   2020-01-28 - afup
Php 7.4 2020-01-28 - afupJulien Vinber
 
Php_Mysql.pdf
Php_Mysql.pdfPhp_Mysql.pdf
Php_Mysql.pdfETTAMRY
 
PHP 5.3, PHP Next
PHP 5.3, PHP NextPHP 5.3, PHP Next
PHP 5.3, PHP NextSQLI
 

Similaire à Refined, des types sur mesure (20)

Visual Studio 2008 Overview
Visual Studio 2008 OverviewVisual Studio 2008 Overview
Visual Studio 2008 Overview
 
Les nouveautés de Groovy 2 -- Mix-IT 2013
Les nouveautés de Groovy 2 -- Mix-IT 2013Les nouveautés de Groovy 2 -- Mix-IT 2013
Les nouveautés de Groovy 2 -- Mix-IT 2013
 
Node.js, le pavé dans la mare
Node.js, le pavé dans la mareNode.js, le pavé dans la mare
Node.js, le pavé dans la mare
 
Introduction à Symfony
Introduction à SymfonyIntroduction à Symfony
Introduction à Symfony
 
ZendFramework2 - Présentation
ZendFramework2 - PrésentationZendFramework2 - Présentation
ZendFramework2 - Présentation
 
Introduction à JavaScript
Introduction à JavaScriptIntroduction à JavaScript
Introduction à JavaScript
 
ALT.Net Juin 2012 - Specflow
ALT.Net Juin 2012 - SpecflowALT.Net Juin 2012 - Specflow
ALT.Net Juin 2012 - Specflow
 
Développer sereinement avec Node.js
Développer sereinement avec Node.jsDévelopper sereinement avec Node.js
Développer sereinement avec Node.js
 
Patterns and OOP in PHP
Patterns and OOP in PHPPatterns and OOP in PHP
Patterns and OOP in PHP
 
Dynamic Languages
Dynamic LanguagesDynamic Languages
Dynamic Languages
 
Vs2008 Linq
Vs2008 LinqVs2008 Linq
Vs2008 Linq
 
Enrichir vos contenus Wordpress avec les API - WPTech 2015
Enrichir vos contenus Wordpress avec les API - WPTech 2015Enrichir vos contenus Wordpress avec les API - WPTech 2015
Enrichir vos contenus Wordpress avec les API - WPTech 2015
 
Chapitre 04 : les fonctions
Chapitre 04 : les fonctionsChapitre 04 : les fonctions
Chapitre 04 : les fonctions
 
02 Spécificité du C++ COURS SYS SYSSSSSS
02 Spécificité du C++  COURS SYS SYSSSSSS02 Spécificité du C++  COURS SYS SYSSSSSS
02 Spécificité du C++ COURS SYS SYSSSSSS
 
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
Atoum, le framework de tests unitaires pour PHP 5.3 simple, moderne et intuit...
 
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
La Tooling API, est-ce pour moi ? Bien sûr, viens voir pourquoi !
 
Partie1 TypeScript
Partie1 TypeScriptPartie1 TypeScript
Partie1 TypeScript
 
Php 7.4 2020-01-28 - afup
Php 7.4   2020-01-28 - afupPhp 7.4   2020-01-28 - afup
Php 7.4 2020-01-28 - afup
 
Php_Mysql.pdf
Php_Mysql.pdfPhp_Mysql.pdf
Php_Mysql.pdf
 
PHP 5.3, PHP Next
PHP 5.3, PHP NextPHP 5.3, PHP Next
PHP 5.3, PHP Next
 

Refined, des types sur mesure

  • 1. Refined, des Types sur mesure Scala IO 2019 @_mhdkassir
  • 2. Pourquoi Refined ? Même dans un langage fortement typé on est souvent amené à faire un effort de validation sur les données Prenons le cas de String - Type très permissif - On peut mettre tout et n’importe quoi Et pourtant dans la plupart des cas on vise une partie de domain des valeurs possibles
  • 3. Pourquoi Refined ? Mais il y a toujours moyen d’envoyer des données pas cohérentes => Ajouter une étape de validation mais avec un modèle plus complexe cela devient du boilerplate code def registerSpeaker( name: String, handle: String) = {...} Exemple 1 registerSpeaker("@johnDoe", "John Doe") // ça peut arriver :( registerSpeaker("John Doe", "@johnDoe") // OK :)
  • 4. Pourquoi Refined ? Exemple 2 // Fichier de configuration api { timout-sec = -10 #timeout négative ! port = 9000 url = "htp://myapi.com" #invalid protocol ! } final case class ApiSettings(timoutSec: Int, port: Int , url: String ) val conf = pureconfig.loadConfig[ApiSettings]("api") => L’objet de configuration est bien chargé, mais on aura des problèmes Runtime
  • 5. Comment peut-on : - Renforcer la précision des types - Réduire le code de validation - Détecter les incohérences le plus tôt possible (compile-time ?)
  • 6. Refined, le concept Au lieu de chercher à valider toute valeur possible dans le domaine d’un Type basique, on peut intégrer la validation au niveau de la définition de Type! Type Refined = Type Basique + Predicate => Le domaine de ce nouveau type est réduit aux valeurs qui respectent la règle de validation associée
  • 7. Refined En Pratique La librairie Refined en Scala: ● https://github.com/fthomas/refined ● Un projet TypeLevel import eu.timepit.refined.api.Refined import eu.timepit.refined.numeric.Positive import eu.timepit.refined.refineMV type PositiveInt = Int Refined Positive Pour déclarer un refined type: Pour instancier un refined value à partir d’un literal val positive : PositiveInt = refineMV[Positive](5) // positive: PositiveInt = 5 val fail = refineMV[Positive](-5) // ERREUR de compilation : Predicate failed (-5 > 0)
  • 8. Refined En Pratique import eu.timepit.refined.api.Refined import eu.timepit.refined.numeric.Positive import eu.timepit.refined.auto._ type PositiveInt = Int Refined Positive // avec l’import auto plus besoin d’utiliser explicitement refineMV[] val positive: PositiveInt = 5 //positive: PositiveInt = 5 val result : Int = 15 + positive // sans l'import d'auto il faut passer par .value val result : Int = 15 + positive.value L’objet auto permet la conversion automatique entre le refined type son type basique
  • 9. Validation au compile-time import eu.timepit.refined.api.{Refined, RefinedTypeOps} import eu.timepit.refined.auto._ import eu.timepit.refined.W import eu.timepit.refined.collection.NonEmpty import eu.timepit.refined.string.StartsWith type Name = String Refined NonEmpty object Name extends RefinedTypeOps[Name,String] type Handle = String Refined StartsWith[W.`"@"`.T] object Handle extends RefinedTypeOps[Handle,String] val name : Name = Name("John Doe") //name: Name = John Doe val handle = Handle("@johnDoe") // handle: Handle = @johnDoe val fail_handle = Handle("johnDoe") // COMPILATION ERROR ! // Predicate failed: "johnDoe".startsWith("@").
  • 10. Validation au compile-time import eu.timepit.refined.api.{Refined, RefinedTypeOps} import eu.timepit.refined.auto._ import eu.timepit.refined.W import eu.timepit.refined.collection.NonEmpty import eu.timepit.refined.string.StartsWith type Name = String Refined NonEmpty object Name extends RefinedTypeOps[Name,String] type Handle = String Refined StartsWith[W.`"@"`.T] object Handle extends RefinedTypeOps[Handle,String] val name : Name = Name("John Doe") //name: Name = John Doe val handle = Handle("@johnDoe") // handle: Handle = @johnDoe val fail_handle = Handle("johnDoe") // COMPILATION ERROR ! // Predicate failed: "johnDoe".startsWith("@"). L’objet compagnon défini en utilisant RefinedTypeOps permet d’avoir
  • 11. Validation au run-time Avec les Refined Types - On ne peut plus passer des données incohérentes - Les types sont précises et ne prennent que des valeurs pertinents => On passe d’un “Stringly Typed” modèle vers un “Strongly Typed” modèle val input_name : String // valorisé au runtime val input_handle: String // valorisé au runtime val result: Either[String, Name] = Name.from(input_name) def registerSpeaker(name:Name, handle: Handle) = { .... } val speaker: Either[String, Speaker] = for{ name <- Name.from(input_name) handle <- Handle.from(input_handle) } yield registerSpeaker(name, handle) L’objet compagnon fournit aussi la méthode “from”
  • 12. Predicates Numeriques Dans le package eu.timepit.refined.numeric Predicates simples par rapport à une valeur Less[N] LessEqual[N] Greater[N] GreaterEqual[N] Positive Negative Predicates sur une intervale Interval.Open[L, H] Interval.OpenClosed[L, H] Interval.ClosedOpen[L, H] Interval.Closed[L, H]
  • 13. Predicates Logiques Dans le package eu.timepit.refined.boolean Not[P] negation of the predicate P And[A, B] conjunction des Predicates A et B Or[A, B] disjunction des Predicates A et B AllOf[PS] conjunction des predicates dans PS AnyOf[PS] disjunction des predicates dans PS OneOf[PS] disjunction exclusive des predicates dans PS
  • 14. Predicates de String & Collection Dans le package eu.timepit.refined.string StartsWith[S] EndsWith[S] Uri MatchesRegex[S] Regex Url Dans le package eu.timepit.refined.collection Empty NonEmpty Forall[P] Exists[P] MinSize[N] MaxSize[N]
  • 15. Exemples type ID256 = String Refined And[NonEmpty, MaxSize[W.`256`.T]] type CodePostale = String Refined MatchesRegex[W.`"[0-9]{5}"`.T] type Latitude = Double Refined Interval.Closed[W.`-90d`.T, W.`90d`.T] et ça peut aller loin .. import shapeless.{::, HNil} type TwitterHandle = String Refined AllOf[ StartsWith[W.`"@"`.T] :: Not[MatchesRegex[W.`"(?i:.*twitter.*)"`.T]] :: Not[MatchesRegex[W.`"(?i:.*admin.*)"`.T]] :: Tail[Or[LetterOrDigit, Equal[W.`'_'`.T]]] :: HNil ]
  • 16. Intégration avec des librairies ● refined-pureconfig : lire les configuration avec des Refined types ● refined-scopt : lire les options de ligne de commande avec des Refined types ● refined-jsonpath : fournir un JSONPath predicate pour vérifier qu’un String est un JSONPath valide ● refined-scalacheck : générer des valeurs arbitraires pour les Refined types avec ScalaCheck. ● refined-cats ● refined-scalaz
  • 17. Exemple PureConfig // Fichier de configuration api { timout-sec = -10 #timeout négative port = 9000 url = "htp://myapi.com" #invalid protocol } // case class avec des Refined Types final case class RefinedApiSettings(timoutSec: PosInt, port: Int Refined And[Greater[W.`1023`.T],Less[W.`65536`.T]], url: String Refined Url) val conf = pureconfig.loadConfig[ApiSettings]("api")
  • 18. Exemple PureConfig val conf = pureconfig.loadConfig[ApiSettings]("api") //* Left( ConfigReaderFailures( CannotConvert( "-10", "eu.timepit.refined.api.Refined[Int,eu.timepit.refined.numeric.Greater[shapeless.nat._0]]", "Predicate failed: (-10 > 0).", ), Some("api.timout-sec") ), List( CannotConvert( ""htps://myapi.com"", "eu.timepit.refined.api.Refined[String,eu.timepit.refined.string.Url]", "Url predicate failed: unknown protocol: htps" ), Some("api.url") ) )
  • 19. Refined avec Play Pour utiliser les Refined types avec Play on utilise une librairie complémentaire - https://github.com/kwark/play-refined - SBT dependency : "be.venneborg" %% "play26-refined" % "0.3.0" qui propose les fonctionnalités suivantes : - Sérialisation/Désérialisation de JSON sous forme des Refined Types - Binding/Unbinding des Refined Types dans les Forms - Path/Query binding de Refined Types - Traduire les erreurs Refined en erreurs Play standards
  • 20. REX : Valider l’input d’un API Play avec Refined import eu.timepit.refined.api.Refined import eu.timepit.refined.collection.NonEmpty import eu.timepit.refined.string.{StartsWith, Url} import eu.timepit.refined.W import play.api.libs.json.Json case class SpeakerDTO (name: String Refined NonEmpty, url: String Refined StartsWith[W.`"@"`.T]) object SpeakerDTO { import be.venneborg.refined.play.RefinedJsonFormats._ implicit val speakerDTOfmt = Json.format[SpeakerDTO] } 1- Définir les DTO avec des Refined Types :
  • 21. REX Refined avec Play import eu.timepit.refined.api.Refined import eu.timepit.refined.collection.NonEmpty import eu.timepit.refined.string.{StartsWith, Url} import eu.timepit.refined.W import play.api.libs.json.Json case class SpeakerDTO (name: String Refined NonEmpty, url: String Refined StartsWith[W.`"@"`.T]) object SpeakerDTO { import be.venneborg.refined.play.RefinedJsonFormats._ implicit val speakerDTOfmt = Json.format[SpeakerDTO] } 1- Définir les DTO avec des Refined Types :
  • 22. 2- Utiliser les Refined DTO dans la signature de services def register = Action.async(parsers.json(SpeakerDTO.speakerDTOfmt)){ implicit req => Future{ // call backend service createSpeaker(req.body) Created } } 3- Utiliser les Refined DTO dans la signature de services def createSpeaker(speakerDTO: SpeakerDTO) = { ..... } REX : Valider l’input d’un API Play avec Refined
  • 23. // Status : 400 Bad Request // Body : { "obj.handle": [ { "msg": [ "error.invalid" ], "args": [ "List(not starting with: @)" ] } ] } Requete Réponse POST : http://localhost:9000/api/speaker Body : { "name" : "John Doe", "handle" : "invalid_handle" }
  • 24. Conclusion Type Refined = Type Basique + Predicate L’utilisation de Refined Types permet de : - Intégrer les règles business dans la définition des Types - Centraliser et simplifier le processus de validation des données - Detecter plus rapidement les incoherences
  • 25. References - https://github.com/fthomas/refined - http://fthomas.github.io/talks/2016-05-04-refined - https://vlovgr.github.io/refined-types - https://www.beyondthelines.net/programming/refined-types/ - https://github.com/kwark/play-refined - https://kwark.github.io/refined-in-practice - https://underscore.io/blog/posts/2017/03/07/refined-data-config-database.html - https://medium.com/@Methrat0n/wtf-is-refined-5008eb233194 - https://blog.colisweb.com/type-your-business-6c39ddc84963

Notes de l'éditeur

  1. Aujourd’hui même avec un langage fortement typé comme Scala ou autre, au niveau du modèle des données on est souvent amené à faire un effort de validation sur les données car les types basiques , comme String par exemple sont très permissif Dans un string on peut mettre tout ce qu’on veut .. un adresse mail, un numéro de tel, un code Postale, voir même tout un paragraphe. En fait le domaine des valeurs possible est très large Et pourtant dans la plupart des cas, ce qu’on cherche est est une partie bien spécifique de ce large domaine
  2. Prenons cet exemple, on a une méthode de service pour enregistrer un Speaker Le speaker est identifié par son Nom et son Handle Twitter, qu’on a modélisé en String On appel la méthode avec le nom et le handle tout va bien Mais il y a toujours moyen d’envoyer des données pas cohérentes .. ça peut être une chaîne vide , un handle twitter pas valide On peut tout simplement se tromper au moment de l’appel et inverser les paramètres .. Cela implique une erreur runtime, voir pire stocker des données pas cohérentes Une solution est d’ajouter une étape de validation quelque part dans notre implémentation , pour ce cas la ça parait simple Mais avec un modèle complexe cela devient un boilerplate code à gérer et ça peut même devenir une source d’erreur
  3. Le concept de Refined peut répondre à ces besoins. Au lieu de chercher à valider toute valeur possible dans le domaine, on peut intégrer la validation au niveau de la définition de Type Comment faire , on définit un nouveau Type raffiné (Refined Type) , à partir d’un Type Basique et Un Predicate Le Predicate est la ou les règles de validation qu’on souhaite associer Le résultat est un nouveau type avec un domaine qui est réduit au valeurs qui respecte la règle
  4. Revenons sur notre exemple de Name et Handler pour le speaker , on va pouvoir définir 2 types : Name est un String qui ne doit pas être vide Handle est un string qui doit commencer par @
  5. On a défini aussi des objets compagnon,avec l’aide de RefinedTypeOps Avec l’objet compagnon, on va avoir un apply méthode de disponible qu’on utilise dans cet exemple
  6. C’est bien d’avoir la validation au niveau de compile time, mais en général on a très peu de literals dans nos programmes La validation doit se faire au runtime aussi. Pour notre exemple de speaker, quelque part on va recevoir deux String comme input Avec l’objet compagnon défini auparavant, la méthode .from permet de valider le type basique en Runtime , et nous retourne either la méthode registerSpeaker utilise les refined types comme paramètres , et avec un bloc de for comprehension on peut composer les Either et appeler la méthode
  7. L’objet de configuration ne sera pas ce charge Le message d’erreur est très parlant
  8. Maintenant on va voir comment on peut utiliser Refined avec le framework Play.Cela est possible avec une libraires complémentaire : play-refined Dans notre utilisation de Refined avec Play on a utilisé les fonctionnalités 1 et 2 En fait nous avons construite un API Rest avec play, donc on recevait des appels avec de JSON en input , et il y avait pas mal des règles de gestion à valider sur les données reçu.
  9. On a commencé par construire tous nos DTO en utilisant des RefinedTypes, du coup toutes les règles métiers font partie de la définition des types On peut appliquer cela sur notre exemple de Speaker
  10. Grace à la libraire play-refined, notamment l’import de RefineJsonFormats, on peut générer automatiquement les Reader et Writer pour transformer le JSON directement
  11. Toutes les méthodes de services prennent les Refined-DTO en paramètre Et enfin au niveau des Actions , on va parser le body de request pour créer le DTO et le passer au service Avec tout ça en place, on a une validation qui se fait en runtime , sans écrire du code de validation
  12. Et si on reçoit un appel avec des données pas valides, l’API renvoie un message d’erreur assez parlant