♥ Play Framework is an open-source web application framework for Java and Scala that follows the model-view-controller (MVC) architectural pattern. It supports dependency injection, routing, and asynchronous programming. Some key features include routing, controllers, database access using Slick, evolutions for database schema changes, and support for functional programming concepts like Option, Either, and Future.
2. Play Framework Nul ne sait qui nous sommes
Mélanie 2 janvier 2017
Kévin 4 décembre 2017
Développeurs Back-End
3. Play Framework Shh .. we have a plan !
La philosophie, le créateur, l’histoire
La structure d'un projet
Les configurations
Le cheminement d’une requête HTTP
Le routing
Les contrôleurs
La couche DAO avec Slick
Les tests
+ Bonus Où retrouve-t-on les
éléments du langage fonctionnel
chers à nos 💓 ?
Option / Either / Future
4. Play Framework Révisons sinon c’est la session 2
● Option
● Either
● Future
Option[T]
Some[T] None
Either[A, B]
Left[A, B] Right[A, B]
Future
6. Play Framework C’est quoi les bails ?
2007: Guillaume Bort (Zengularity SA → Fabernovel Technologies)
Play 1.0: Mai 2008 - Octobre 2009
Play 2.0: Fin 2011 - Mars 2012 (+Sadek Drobi)
Play 2.5: Mars 2016 - Akka Streams
Play 2.6: 23 Juin 2017 - Akka HTTP serveur backend par défaut + Scala 2.12 [...]
Stateless Scala I/O asynchrones
8. Play Framework Les configurations
sbt
● Outil de build
● Ajout des librairies, configuration version Scala/SBT, ajout de tâches
> run
> compile
> test
> test-only maClasseDeTesterCestDouter.scala
> testQuick <mesTestsUnitairesTasVu>
> doc
9. Play Framework Les configurations
/build.sbt
import Dependencies._
name := """poc-play"""
organization := "com.mrgueritte"
version := "1.0-SNAPSHOT"
scalaVersion := "2.12.6"
lazy val root = (project in file(".")).enablePlugins(PlayScala)
libraryDependencies += "com.typesafe.play" %% "play-slick-evolutions"
% "3.0.3"
resolvers += Repositories.sonatypeOss
10. Play Framework Les configurations
application.conf
● Paramétrage divers : BD, Filtres, Taille buffers ...
● Format clé-valeur, HOCON
● Système héritage application.conf -> production.conf
● Injecté automatiquement dans le paramétrage de Play
● Possibilité de le charger en custom
11. Play Framework Les configurations
# Database configuration
# ~~~~~
include "db.conf"
# Evolutions
# ~~~~~
play.evolutions.db.default.autoApply=true
play.evolutions.db.default.autoApplyDowns=true
# Authentication
# ~~~~~
http.secure = false
13. Play Framework Painless HTTP Routing
Route = HTTP Verb + URI → Controller Method
# Routes
# This files defines all application routes (Higher priority routes first)
# https://www.playframwork.com/documentation/latest/ScalaRouting
# ~~~~
GET /api/cat/:id controllers.CatsController.get(id: CatId)
-> / order.Routes
-> / owner.Routes
14. Play Framework Les routeurs exemples
GET /clients/:id controllers.Clients.show(id: Long)
GET /api/list-all controllers.Api.list(version: Option[String])
GET /items/$id<[0-9]+> controllers.Items.show(id: Long)
GET / controllers.Application.show(page = "home")
GET /:page controllers.Application.show(page)
GET /clients controllers.Clients.list(page: Int ?= 1)
GET /api/cat/:id controllers.CatsController.get(id: CatId) ?
18. Play Framework Dans les contrôleurs y a de l’action
play.api.mvc.Request =>
play.api.mvc.Result
val echo = Action { request =>
Ok(s"Got request [$request]")
}
19. Play Framework Dans les contrôleurs y a de l’action
Action { Ok("Hello world") }
def hello(name: String) = Action {
Ok("Hello " + name)
}
def echo = Action { request =>
Ok("Got request [" + request + "]")
}
def createHouse() = Action(parse.json[House]) {request =>
Ok(request.body)//House
}
20. Play Framework Dans les contrôleurs y a de l’action
def createHouseAsync = Action().async { request =>
for {
design <- getPlans()
wood <- getMaterial()
} yield Ok(Json.obj("House plans" -> design, "material" -> wood))
}
21. Play Framework Authentification
Support pour OAuth2
Guest
Member
OrganizationMember.(Read)
PlatformAdmin
Api(Scope)
[...]
def list() = OrganizationMember(READ)("cats.list").async { implicit request =>
catsService
.listByOrganization(request.organizationId)
.map(Ok(_))
}
=> Future[Result]
22. Play Framework Les resultats c’est pas du bois
Ok("Hello World!")
→ Content-Type header to text/plain
Ok(<message>Hello World!</message>)
→ application/xml
Ok(<h1>Hello World!</h1>)
→ application/html
Ok(Json.obj(”foo” -> “bar”)) -> application/json
val result = Ok("Hello World!").withHeaders(
CACHE_CONTROL -> "max-age=3600",
ETAG -> "xx")
28. Play Framework Les requêtes : “tout est
collection”> SELECT * FROM cats WHERE id = ?
override def getById(id: CatId): DBIO[Option[Cat]] = Schemas.cats.filter(_.id === id).headOption
> INSERT INTO cats VALUES (?,?,? …)
override def create(cat: Cat): DBIO[Unit] = Schemas.cats += cat
> SELECT c.* FROM pedigrees p JOIN cat c ON c.pedigreeId = p.id WHERE p.name = ?
override def getByPedigreeName(name: String): DBIO[Seq[Cat]] = {
for {
cat <- Schemas.cats
pedigree <- Schemas.pedigrees
if pedigree.id === cat.pedigreeId && pedigree.pedigree === name
} yield cat
}
29. Play Framework Les DBIO
override def getByPedigreeName(name: String): Future[Seq[Cat]] = {
val transaction = (for {
cat <- catsDAO.getByPedigreeName(name)
_ <- DBIO.seq(cat.map(c => catsDAO.delete(c.catId)): _*)
} yield cat).transactionally
db.run(transaction)
}
> Les compositions de requêtes & les transactions
30. Play Framework Les évolutions
● Situé dans le dossier /conf/evolutions/<dbName>/
● Chaque script est composé de deux parties :
○ Ups! : SQL d’évolution de la BD (INSERT, CREATE …)
○ Downs! : Retour arrière de l’évolution (DROP, ALTER …)
● Une table `play_evolutions` est créée sur la DB => versionnage
# --- !Ups
CREATE TABLE pedigrees(
id UUID NOT NULL,
name VARCHAR(32) NOT NULL
);
…
# --- !Downs
DROP TABLE cats;
DROP TABLE owners;
DROP TABLE pedigrees;
31. Play Framework Les évolutions, “j’ai tout casséhéhé”
● Modification du script en question
● Marquer le script comme résolu => Application du `!Downs` puis du `!Ups`
32. Play Framework Injections de dépendances
● Guice est embarqué => utilisation à spécifier build.sbt
● Auto-gestion des dépendances avec @Inject() (java !)
● Runtime
“Use another compile time dependency injection!”
33. Play Framework Injections de dépendances
ApplicationLoader
● Point entrée unique facilement customisable => `application.conf`
● Chargement de toutes les “parties” de l’application => `Components`
class PocPlayApplicationLoader extends ApplicationLoader {
override def load(context: Context): Application = {
new PocPlayComponents(context).application
}
}
play.application.loader= "di.PocPlayApplicationLoader"
34. Play Framework Injections de dépendances
Components
class PocPlayComponents(context: Context) extends
BuiltInComponentsFromContext(context)
with HttpFiltersComponents // Add defaults HTTP Filters
with SlickComponents // Add Slick API (connect play to database)
with EvolutionsComponents { // Add Evolutions system
override lazy val dbApi: DBApi = SlickDBApi(slickApi)
applicationEvolutions.start()
...
}
35. PlayFramework On a parcouru le chemin, on a tenu la distance
class CatsController( catsService: CatsService [...]
def buy(catId: CatId, ownerId: OwnerId) = Action.async { implicit request =>
catsService.buy(catId, ownerId)[...] }
class CatsServiceImpl( catsDAO: CatsDAO, db: DbExecutor [...]
override def buy(catId: CatId, ownerId: OwnerId): Future[Either[ApiError, Void]] =
[...] db.run(catsDAO.updateOwner(catId, ownerId)) [...]
case class CatsDAOSlick()(implicit ec: ExecutionContext) extends CatsDAO {
override def updateOwner(catId: CatId, ownerId: OwnerId): DBIO[Void] = [...]
36. Play Framework Injections de dépendances
Components - Add dependency injection with macwire
class PocPlayComponents(context: Context) extends
BuiltInComponentsFromContext(context)
with HttpFiltersComponents // Add defaults HTTP Filters
with SlickComponents // Add Slick API (connect play to database)
with EvolutionsComponents { // Add Evolutions system
...
lazy val db: DbExecutor =
slickApi.dbConfig[JdbcProfile](DbName("default")).db
lazy val catsDAO: CatsDAO = new CatsDAOSlick()
lazy val catsService: CatsService = wire[CatsServiceImpl]
lazy val catsController: CatsController = wire[CatsController]
}
37. Play Framework Tests
Scala Test - Flat Spec et d’autres WordSpec/FreeSpec etc.
class HelloWorldSpec extends Specification {
"The 'Hello world' string" should {
"contain 11 characters" in {
"Hello world" must have size(11)
}
"start with 'Hello'" in {
"Hello world" must startWith('Hello')
}
}
}
[info] HelloWorldSpec:
[info] The 'Hello world' string
[info] - should contain 11 characters
[info] - should start with 'Hello'
[info] ScalaTest
[info] Run completed in 2 seconds, 888 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0,
ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 2, Failed 0, Errors 0, Passed 2
[success] Total time: 5 s, completed 3 juin 2018
22:13:54
38. Play Framework Tests
Mock API’s
import play.api.test.Helpers._
class CatsApiSpec extends PocPlayOneAppPerSuite {
…
def createCatApi(body: JsValue) = FakeRequest("POST", "/api/cats").withBody(body)
"Cats API" must {
"have a valid endpoint `create cat API`" in {
...
val Some(response) = route(app, createCatApi(catsBody))
status(response) == CREATED
val json = contentAsJson(response)
...
}
}
}
39. Play Framework Tests
Mock context
trait PocPlayOneAppPerSuite extends PlaySpec with GuiceOneAppPerSuite { self =>
override lazy val app = {
val context = ApplicationLoader.createContext(new Environment(new java.io.File("."),
ApplicationLoader.getClass.getClassLoader, Mode.Test))
new TestComponents(context).application
}
}
class TestComponents(context: Context) extends PocPlayComponents(context: Context) {
override lazy val configuration = {
val databseConfig = ConfigFactory.parseString(
"""slick.dbs.default.db.url = <database test>""".stripMargin)
context.initialConfiguration.copy(underlying = databseConfig)
}
}
40. Play Framework Fun with tests
Property-based testing - Scala Check
def catJsonGenerator: Gen[JsObject] = {
for {
name <- Gen.alphaStr.suchThat(_.size < 32)
gender <- Gen.oneOf(const(Gender.Female), const(Gender.Male))
pedigreeId <- const(PedigreeId(UUID.fromString("cc58095b-8d51-4a22-a110-7ace2d921c2c")))
ownerId <- option(const(OwnerId(UUID.fromString("76065e7d-8604-4995-a6ed-33fca30517aa"))))
dateOfBirth <- const(DateTime.now())
} yield buildCatJson(name, gender, pedigreeId, ownerId, dateOfBirth, None)
}
"have a valid endpoint `create cat API`" in {
check {
Prop.forAll(catJsonGenerator) { catsBody: JsObject =>
val Some(response) = route(app, createCatApi(catsBody))
status(response) == CREATED
}
}
}
42. Play Framework Merci d’être là, super sympa
https://github.com/kevin-margueritte/poc-playframework
Des questions ?
Vous êtes sûrs ? On va être coupés, y a un tunnel.
Editor's Notes
Support code -> https://github.com/kevin-margueritte/poc-playframework