Forum |  HardWare.fr | News | Articles | PC | Prix | S'identifier | S'inscrire | Shop Recherche
1910 connectés 

 


 

 Mot :   Pseudo :  
 
Bas de page
Auteur Sujet :

Blabla@Scala

n°2371253
DDT
Posté le 11-12-2020 à 18:55:55  profilanswer
 

MP public entre LeRiton et moi pour l'instant, pour éviter d'entremêler les questions relatives à Scala dans blabla@prog. :D
 
Je tenterai de faire quelque chose du premier post s'il y a de la demande pour ça.
 
Sujets bienvenus:
- écosystèmes fonctionnels: Scalaz, Typelevel, ZIO
- systèmes distribués: Finagle, Akka,  
- frameworks web: Play, Akka HTTP, http4s, ...
- bibliothèques et outils en tout genre
- resources pour l'apprentissage
- Scala 3  [:frog sad]  
- Spark [:ponaygay:4]  
- drama dans la communauté [:sord:5]


---------------
click clack clunka thunk
mood
Publicité
Posté le 11-12-2020 à 18:55:55  profilanswer
 

n°2371461
DDT
Posté le 14-12-2020 à 00:56:29  profilanswer
 

Question du Riton, pourquoi utiliser Tapir, ici avec http4s.
 
Petit exemple, une API qui prend un code postal suisse en paramètre, qui ont la propriété d'être compris entre 1000 et 9999, ça va être plus simple qu'un code postal français pour la démo. :D
 
Un value class pour emballer l'entier qui représente le code postal, et son codec en partant du décodeur d'entier fourni par Tapir:

Code :
  1. case class PostalCode(id: Int) extends AnyVal
  2. object PostalCode {
  3.   implicit val postalCodeCodec: Codec[String, PostalCode, CodecFormat.TextPlain] =
  4.     Codec.int
  5.       .validate(Validator.min(1000).and(Validator.max(9999)))
  6.       .map(i => PostalCode(i))(_.id)
  7. }


 
Gestion custom des erreurs pour leur attacher un statut HTTP:
 

Code :
  1. sealed trait CustomError extends Product with Serializable
  2. case class PostalCodeNotFound(code: PostalCode) extends CustomError
  3. case class ServerError(reason: String) extends CustomError
  4. val errorHandling = oneOf[CustomError](
  5.     statusMappingFromMatchType(StatusCode.NotFound, jsonBody[PostalCodeNotFound]),
  6.     statusMappingFromMatchType(StatusCode.BadGateway, jsonBody[ServerError])
  7.   )


 
Les extracteurs pour les requêtes, un path param, un query param qui accepte une liste non-vide de codes postaux:

Code :
  1. val pathParam = path[PostalCode]("postalCode" )
  2. val queryParam = query[List[PostalCode]]("codes" ).validate(Validator.minSize(1))


 
Les routes:

Code :
  1. val base = endpoint.get.in("tapir" )
  2. val getOne = base.in("area" / pathParam).out(jsonBody[Response]).errorOut(errorHandling)
  3. val getMany = base.in("areas" / queryParam).out(jsonBody[List[Response]]).errorOut(errorHandling)


 
La doc OpenAPI autogénérée:

openapi: 3.0.3
info:
  title: Swiss postal codes
  version: '1.0'
servers:
- url: http://localhost:8080
paths:
  /tapir/area/{postalCode}:
    get:
      operationId: getTapirAreaPostalcode
      parameters:
      - name: postalCode
        in: path
        required: true
        schema:
          type: integer
          minimum: 1000
          maximum: 9999
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Response'
        '404':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostalCodeNotFound'
        '502':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerError'
  /tapir/areas:
    get:
      operationId: getTapirAreas
      parameters:
      - name: codes
        in: query
        required: false
        schema:
          type: array
          items:
            type: integer
            minimum: 1000
            maximum: 9999
          minItems: 1
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Response'
        '404':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PostalCodeNotFound'
        '502':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ServerError'
components:
  schemas:
    PostalCodeNotFound:
      required:
      - code
      type: object
      properties:
        code:
          type: integer
    ServerError:
      required:
      - reason
      type: object
      properties:
        reason:
          type: string
    Response:
      required:
      # schéma de la réponse
      type: object
      properties:
        # types des champs


 
Et il n'y a plus qu'à connecter les endpoints à la logique qui appelle un service ou une BDD qui retourne un IO[Either[CustomError, Response]] par exemple.


---------------
click clack clunka thunk
n°2371469
LeRiton
skishop-lelex.com
Posté le 14-12-2020 à 08:51:19  profilanswer
 

C'est top, merci !
Je vous tiendrais au jus des choix et difficultés :o


---------------
SkiShop Lélex, location de ski, snowboard, snowscoot & VTT sur le domaine Mijoux / Lélex (Jura)
n°2371551
LeRiton
skishop-lelex.com
Posté le 14-12-2020 à 20:11:38  profilanswer
 

DDT a écrit :


Un value class pour emballer l'entier qui représente le code postal, et son codec en partant du décodeur d'entier fourni par Tapir:


J'ai un peu de mal sur le rôle du Codec, notamment comment il complète la partie serialization. Le Codec se suppléé au Decoder Circe ? Ou il le remplace ? Je comprend bien que le rôle du Codec est plus large (validation, transfo), mais est-ce qu'il descend aussi bas ?
 
La serialization est un vrai sujet dans mon cas, on doit bosser avec des classes Java très complexes (bean avec un max d'héritage) et on aimerait autant que possible renforcer les I/O de l'API en fonction de ces beans.


---------------
SkiShop Lélex, location de ski, snowboard, snowscoot & VTT sur le domaine Mijoux / Lélex (Jura)
n°2371558
DDT
Posté le 14-12-2020 à 21:44:35  profilanswer
 

Les codecs de Tapir sont nécessaires à transformer les entrées/sorties de tes endpoints; la sérialisation en JSON des entités en entrée et/ou sortie de tes requêtes et réponses n'est qu'une des combinaisons possibles. Dans certains cas Tapir le fait directement, dans d'autres comme le JSON, tu as besoin d'une autre bibliothèque.
Ici par exemple, le binding entre les deux codecs est la méthode jsonBody, dans le module tapir-json-circe (si tu veux utiliser Circe).
 
Dans mon code plus haut je veux construire deux endpoints qui extraient un ou plusieurs codes postaux directement depuis l'URL de la requête.
Donc on a String -> Int qui est déjà fourni par Codec.int, puis Int -> PostalCode que je construis moi-même.
Dans l'autre sens, si je voulais utiliser mes endpoints avec le client STTP par exemple, le codec permet de faire PostalCode -> String pour construire la requête.
 
Avec le Codec attaché aux E/S de ton endpoint, tu as une instance optionnelle de Validator et aussi de Schema qui est dérivé de manière automatique.
Mais tu peux décrire tes schémas explicitement si tu n'es pas satisfait du YAML généré. J'ai pas de Java, mais j'ai des hiérarchies parfois un peu trop complexes pour la dérivation automatique.


---------------
click clack clunka thunk
n°2372101
LeRiton
skishop-lelex.com
Posté le 21-12-2020 à 14:49:10  profilanswer
 

Tu utilises ce pattern pour l'implémentation Tapir <=> http4s ?
https://github.com/hejfelix/tapir-h [...] tion.scala
 
C'est franchement aride pour qui n'a pas 10 ans de cats derrière lui, ça tire énormément de concepts, et le fait que ça dépende d'explicits à foison n'arrange rien.


---------------
SkiShop Lélex, location de ski, snowboard, snowscoot & VTT sur le domaine Mijoux / Lélex (Jura)
n°2372108
DDT
Posté le 21-12-2020 à 16:07:49  profilanswer
 

Oui je fais des choses similaires.
 
Note qu'ici y a plein de choses dans la même classe, en vrai tu pourrais simplifier pas mal
 
- La route pour la doc et Swagger UI est déjà fournie par le module swagger-ui-http4s, pas besoin d'aller mélanger le DSL à ton API.
- T'es pas obligé d'utiliser un F générique et tagless final, même si c'est une bonne pratique. Utiliser IO concret partout va réduire le nombre d'implicites.
- ... voire considérer BIO si tu te retrouves à gérer beaucoup de IO[Either[Throwable, R]]
- Le reduceK pour combiner toutes les routes d'un coup c'est malin (je devrais faire ça tiens :D) mais tu peux très bien combiner les routes une à une comme dans les exemples moins compliqués.
- Tous les F.delay ici c'est pour illustrer les effets de bord, en pratique tu devrais déjà recevoir un IO quand tu connectes les routes à ta logique (par exemple si tu appelles doobie).
 


---------------
click clack clunka thunk
n°2373371
LeRiton
skishop-lelex.com
Posté le 07-01-2021 à 16:25:43  profilanswer
 

DDT a écrit :


- T'es pas obligé d'utiliser un F générique et tagless final, même si c'est une bonne pratique. Utiliser IO concret partout va réduire le nombre d'implicites.
[...]
- Le reduceK pour combiner toutes les routes d'un coup c'est malin (je devrais faire ça tiens :D) mais tu peux très bien combiner les routes une à une comme dans les exemples moins compliqués.


C'est typiquement l'exemple où sans la compréhension de F[_] / cats / FP / whatever je butte et je ne trouve rien de pertinent.
C'est sans doute simple, mais comment tu combines plusieurs HttpRoutes[IO] (pas générique) ? J'ai ni reduceK ni <+> a dispo ou alors je vois pas comment.
 
Il y a une barrière à l'entrée assez haute pour qui ne maîtrise pas le FP a utiliser ces outils, on parle pourtant juste de dev une API.
 


---------------
SkiShop Lélex, location de ski, snowboard, snowscoot & VTT sur le domaine Mijoux / Lélex (Jura)
n°2373410
DDT
Posté le 07-01-2021 à 23:06:05  profilanswer
 

Oui t'as raison pour <+> (combineK) il faut importer les méthodes d'extension pour SemigroupK

Code :
  1. import cats.syntax.semigroupk._


 
Tu peux toujours partir avec un:

Code :
  1. import cats.syntax.all._


voire en dernier recours:

Code :
  1. import cats.implicits._


s'il te manque aussi l'instance de la typeclass, puis essayer de raffiner tes imports ensuite.
 
Pour celui-là, faut le savoir, ou aller voir un exemple dans la doc, c'est vrai. :jap:
 
Pour le reste, si je vois ce que j'utilise dans ma codebase, c'est surtout des map/flatMap/sequence/traverse, exactement comme je le ferais avec des Future, et 2-3 trucs qui viennent assez naturellement, comme *> (productR) dans Apply qui est très pratique. Et là si tu utilises directement IO, tu n'auras pas besoin d'imports.
 
Je te conseille https://www.scala-exercises.org/cats/
C'est vite fait et ça devrait suffire comme introduction. Il n'y a pas besoin de connaître Cats de bout en bout pour utiliser http4s confortablement. Par exemple HttpRoutes est simplement un type alias qui utilise un Kleisli, mais t'as pas vraiment besoin de le savoir.
 
Et le livre d'Underscore si tu veux approfondir ou juste avoir une référence sous le coude: https://www.scalawithcats.com/
 
Et pourquoi s'embêter avec tout ça finalement? De mon expérience c'est dur d'anticiper le moment ou tu vas faire face à une limitation du framework.
Et là si je dois faire un truc un peu plus bas niveau, c'est vraiment appréciable de pouvoir utiliser Cats Effect (Resource est vraiment génial par exemple) ou fs2 directement, de manière parfaitement intégrée au reste.


---------------
click clack clunka thunk
n°2373690
LeRiton
skishop-lelex.com
Posté le 11-01-2021 à 17:47:20  profilanswer
 

DDT a écrit :

Oui t'as raison pour <+> (combineK) il faut importer les méthodes d'extension pour SemigroupK

Code :
  1. import cats.syntax.semigroupk._


 
Tu peux toujours partir avec un:

Code :
  1. import cats.syntax.all._


voire en dernier recours:

Code :
  1. import cats.implicits._




 
C'est bien ce que j'ai essayé, mais j'arrive à rien même avec un exemple minimaliste :

Code :
  1. import cats.effect.{ContextShift, IO, Sync, Timer}
  2. import cats.syntax.semigroupk._
  3. import cats.implicits._
  4. import cats.syntax.all._
  5. import org.http4s.HttpRoutes
  6. import org.http4s.dsl.Http4sDsl
  7. import sttp.tapir.server.http4s.{Http4sServerOptions, RichHttp4sHttpEndpoint}
  8. class FooService(endpoints: FooEndpoints)
  9.                          (implicit serverOptions: Http4sServerOptions[IO], fs: Sync[IO], timer: Timer[IO], fcs: ContextShift[IO]) {
  10.  object dsl extends Http4sDsl[IO]
  11.  import dsl._
  12.  val routes = testRoute <+> testRoute
  13.  
  14.  private def testRoute: HttpRoutes[IO] =
  15.    HttpRoutes.of[IO] {
  16.      case GET -> Root / "youpi" => Ok("youpla" )
  17.    }
  18. }


 
J'importe la terre entière, il n'y a strictement aucune code et j'obtiens ça :
 

value <+> is not a member of org.http4s.HttpRoutes[cats.effect.IO]


je n'ai évidemment pas possibilité d'appeler combineK sur l'objet non plus.
 
Je suis en Scala 2.12, j'ai bien -Ypartial-unification dans les options de compilation.
Franchement je sèche. Je passe à côté d'une évidence ?
 

DDT a écrit :


Je te conseille https://www.scala-exercises.org/cats/
C'est vite fait et ça devrait suffire comme introduction. Il n'y a pas besoin de connaître Cats de bout en bout pour utiliser http4s confortablement. Par exemple HttpRoutes est simplement un type alias qui utilise un Kleisli, mais t'as pas vraiment besoin de le savoir.
 
Et le livre d'Underscore si tu veux approfondir ou juste avoir une référence sous le coude: https://www.scalawithcats.com/
 
Et pourquoi s'embêter avec tout ça finalement? De mon expérience c'est dur d'anticiper le moment ou tu vas faire face à une limitation du framework.
Et là si je dois faire un truc un peu plus bas niveau, c'est vraiment appréciable de pouvoir utiliser Cats Effect (Resource est vraiment génial par exemple) ou fs2 directement, de manière parfaitement intégrée au reste.


:jap:
J'étais tombé sur le livre d'Underscore, il est dans ma todo. Je vais commencer par scala-exercices/cats
Merci encore pour ton aide !
 


---------------
SkiShop Lélex, location de ski, snowboard, snowscoot & VTT sur le domaine Mijoux / Lélex (Jura)
mood
Publicité
Posté le 11-01-2021 à 17:47:20  profilanswer
 

n°2373805
DDT
Posté le 13-01-2021 à 00:22:34  profilanswer
 


import cats.effect.{IO, Sync}
import cats.syntax.semigroupk._
import org.http4s.HttpRoutes
import org.http4s.dsl.Http4sDsl
 
class FooService()(implicit fs: Sync[IO]) extends Http4sDsl[IO] {
 
  val routes: HttpRoutes[IO] = testRoute <+> testRoute
 
  private def testRoute: HttpRoutes[IO] =
    HttpRoutes.of[IO] { case GET -> Root / "youpi" =>
      Ok("youpla" )
    }
}
 


 
Ça doit compiler.
 
- Les imports faut choisir: soit tu importes uniquement les méthodes d'extension pour SemigroupK, soit toutes (cats.syntax.all), soit tous les implicites (qui correspond à syntax.all et instances.all). Si tu importes plusieurs fois les mêmes implicites ça compile pas.
- Le trait Http4sDsl tu peux l'utiliser comme mix-in, c'est plus propre.
 
Mais sinon ça devrait marcher.
 
Pour les paramètres de scalac j'utilise https://github.com/DavidGregory084/sbt-tpolecat
Ça force à garder le code relativement propre.


---------------
click clack clunka thunk
n°2373806
LeRiton
skishop-lelex.com
Posté le 13-01-2021 à 08:45:55  profilanswer
 

Haha, un énorme merci tu m'as aidé à trouver le problème :D

DDT a écrit :


- Les imports faut choisir: soit tu importes uniquement les méthodes d'extension pour SemigroupK, soit toutes (cats.syntax.all), soit tous les implicites (qui correspond à syntax.all et instances.all). Si tu importes plusieurs fois les mêmes implicites ça compile pas.


Bien sûr, j'ai mis la totale dans mon exemple pour montrer ce que j'avais essayé :jap:

 

C'était bien un problème de compilateur et pas d'imports au final. Important pour la suite : j'utilise Maven (because reasons) sur Intellij, pas sbt.
Le `-Ypartial-unification` était dans les settings scalac de maven, pas ceux de l'IDE.
Ton exemple minimaliste et l'assurance que ça devait compiler m'ont aidé à isoler le problème, merci beaucoup. C'est ce qui est compliqué en débutant sur un truc très riche, j'ai tendance à remettre en question mon code plutôt que le setup.

 

Edit : bon ça build dans l'IDE mais c'est toujours rouge dans le code. Au moins le problème est isolé :D non rien :o


Message édité par LeRiton le 13-01-2021 à 09:00:18

---------------
SkiShop Lélex, location de ski, snowboard, snowscoot & VTT sur le domaine Mijoux / Lélex (Jura)

Aller à :
Ajouter une réponse
 

Sujets relatifs
Lunch@blabla@devoxxblabla@django
Blabla@Progue[Topic unique] .Net @ Prog
Page Jsp qui affiche le ${ blabla }case in blabla (KSH) ===> fichier CSV
BlaBla@SQLSuivant / Précédant dans appli js (non c'est pas history.blabla ...)
Blabla@Python \o/SBP: le Système d'unités de Blabla@Prog
Plus de sujets relatifs à : Blabla@Scala


Copyright © 1997-2018 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR