Save 37% off PRO during our Black Friday Sale! »

Mehr Sicherheit durch Refined Types

Mehr Sicherheit durch Refined Types

Ein kurzer Abriß was Refined Types eigentlich sind und anschließend anhand von Beispielen Anwendungsfälle, die von deren Einsatz profitieren.

16ad3ff954790c142086a2dfde2aea48?s=128

Jens Grassel

December 12, 2019
Tweet

Transcript

  1. Mehr Sicherheit durch Refined Types Jens Grassel . . Wegtam

    GmbH
  2. Was sind Refined Types?

  3. Was sind Types? • einfach nur Datentypen • val myString:

    String • val myNumber: Int • und so weiter...
  4. Wo ist das Problem? Dann ist doch alles schick, oder?

    def parsePort(s: String): Int = s.toInt parsePort("Ups!") def startSocket(port: Int): Unit = { val serverSocket = new ServerSocket(port) // ... } startSocket(parsePort("-1")) // Ups... Dies ist nur ein Beispiel von vielen.
  5. Und was machen Refined Types? Wir erg¨ anzen die verf¨

    ugbaren Typen durch weitere Beschr¨ ankungen. type PortNumber = Int Refined Interval.Closed[W.‘1‘.T, W.‘65535‘.T] def startSocket(p: PortNumber): Unit = { val serverSocket = new ServerSocket(p) // ... } startSocket(-1) // Compiliert nicht!
  6. Also was machen Refined Types nun genau? Sie verwandeln Laufzeitprobleme

    in Compilierzeitprobleme! Und mehr, aber dazu sp¨ ater...
  7. Anwendungsf¨ alle

  8. Reduzierung des Implementierungsraums ( ) Je strikter die Typen sind,

    desto einfacher ist die Implementierung. final case class Service(host: String, port: Int) def hashName(name: String) versus final case class Service(host: String Refined IPv4, port: PortNumber) def hashName(name: String Refined NonEmpty)
  9. Reduzierung des Implementierungsraums ( ) Beispiel: Integration in ein bestehendes

    System • Produktdaten von extern • weitgehend kompatibel • Altsystem unterst¨ utzt nur Artikelnummern gr¨ oßer final case class ArtNo(n: Int) { require(n > 4096, "Illegal article number!") } Das ist beschissen! Tut das nicht!
  10. Reduzierung des Implementierungsraums ( ) Das hier ist etwas besser:

    final case class ArtNo(n: Int) object ArtNo { def apply(n: Int): Option[ArtNo] = if (n > 4096) Option(new ArtNo(n)) else None } Aber wir k¨ onnen das hier haben... :-) type ArtNo = Int Refined Greater[4096]
  11. Konfigurationsdaten ( ) Konfigurationsdateien bieten eine Vielzahl an potentiellen Fehlerquellen

    database { driver = "org.postgresql.Driver" url = "jdbc:postgresql://localhost/db" user = "username" pass = "secret" }
  12. Konfigurationsdaten ( ) Absicherung durch refined und pureconfig final case

    class DatabaseConfig(driver: DatabaseDriverName, url: DatabaseUrl, user: DatabaseLogin, pass: DatabasePassword) object DatabaseConfig { implicit val configReader: ConfigReader[DatabaseConfig] = deriveReader[DatabaseConfig] } ⇒ Validierung zur Laufzeit! (kein eigener Code n¨ otig!)
  13. Serialisierung und Deserialisierung ¨ ahnelt der Konfigurationsproblematik • Parsen oder

    Erstellen von diversen Formaten • heutzutage zumeist JSON • auch hier erh¨ ohte Sicherheit durch strengere Typisierung der Modelle • Integration von refined mit Circe (JSON Bibiliothek) • Decoder und Encoder frei Haus
  14. Weniger Tests durch bessere Typisierung ( ) Was testen wir

    eigentlich? • sehr oft Eingabewerte (erlaubte Wertebereich) "Creating a user" when { "name is empty" must { "throw an exception" in { the [IllegalArgumentException] thrownBy createUser(name = "", age = 18) must have message "Name must be present!" } } }
  15. Weniger Tests durch bessere Typisierung ( ) type UserName =

    String Refined NonEmpty type UserAge = Int Refined Positive def createUser(n: UserName)(a: UserAge): User = ??? Problem gel¨ ost! :-)
  16. Generelles Vermeidet ”stringified programming”! final case class Foo(login: String, email:

    String, collectedStars: Int) versus final case class Foo(login: LoginName, email: EMail, stars: CollectedStars) Der Compiler kann euch dann helfen!
  17. Vielen Dank! https://github.com/fthomas/refined