ZIO-HTTP: Partie 2 – injection de dépendances

Dans la partie 1 de cette série d’articles, nous avons présenté la librairie ZIO-HTTP.

ZIO prend en charge l’injection de dépendances au niveau de la définition de toutes les actions de type “IO”. 

Un résultat de type ZIO[R, E, A] représente la description d’un traitement, qui produit un résultat de type A, ou une erreur de type E, et qui a besoin d’un environnement de type R pour être exécuté.

Ce type R peut représenter la couche d’accès un service externe, à une base de données, ou tout autre interaction externe : on parle bien d’I/O (input/output) avec des entrées/sorties depuis/vers l’extérieur.

Dans le cadre d’une application Web, on pourra donc gérer les couches d’accès aux bases de données ou à tout autre service externe avec l’environnement R de ZIO, et grâce à l’API ZLayer qui offre une gestion fine des couches de dépendances à travers des modules.

Nous allons par exemple créer un service “Hello World”  qui renvoie l’heure courante : 

object HelloWorldService {
 
  val clock = ZIO.access[Clock](_.get) // <- injection de dépendance
 
  def helloWorld: ZIO[Clock, DateTimeException, String] =
    for {
      c <- clock
      time <- c.localDateTime
    } yield s"Hello world, the current date is $time"
}

Ce service dépend lui même du service Clock fourni par ZIO, qui pourra être “branché” sur la vraie horloge du système ou sur une implémentation de test pour faciliter nos tests unitaires (il est plus simple de tester des résultats contenant une heure fixe dans un jeu de données qu’une vraie heure courante).

Notre application principale pourra ensuite utiliser ce service comme ceci : 

object HelloWorld extends App {
  
  val app = Http.collectM[Request] {
    case Method.GET -> Root / "hello" =>
      HelloWorldService.helloWorld.fold({
        // cas d’erreur :
        case dateTimeError: DateTimeException =>
          Response.fromHttpError(InternalServerError("Internal error : " + dateTimeError.getMessage))
      },
        // cas de succès :
        message => Response.text(message)
      )
   }
 
  override def run(args: List[String]) = {
    // on branche l’implémentation de Clock
    val serverIO = Server.start(8090, app).provideLayer(Clock.live)    
    serverIO.exitCode
  }
}

L’implémentation “live” (réelle) de l’horloge est branchée en utilisant la méthode “provideLayer”.

Dans la partie 3, nous parlerons d’authentification et de CORS.