Slide 1

Slide 1 text

Structure your Play application with the cake pattern and test it

Slide 2

Slide 2 text

Structure a Play application like a cake

Slide 3

Slide 3 text

We'll build a website for Toys Basketball Association

Slide 4

Slide 4 text

Architecture

Slide 5

Slide 5 text

Player Backend GET http://localhost:9001/players/1 HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Content-Length: 91 {"id":1,"name":"James P. Sullivan","height":"34 cm","weight":"370 g","team":"Monstropolis"}

Slide 6

Slide 6 text

Player Backend GET http://localhost:9001/players/1/photo HTTP/1.1 200 OK Last-Modified: Thu, 12 Dec 2013 13:11:02 GMT Etag: "4911f28b55213..." Content-Length: 1753583 Cache-Control: max-age=3600 Content-Type: image/jpeg Date: Mon, 23 Dec 2013 10:01:15 GMT

Slide 7

Slide 7 text

Video Backend GET http://localhost:9002/videos/top HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Content-Length: 83 [{"id":1,"summary":"dunk","players":[1]},...]

Slide 8

Slide 8 text

Video Backend GET http://localhost:9002/videos/1/stream.mp4 range: bytes=200-280 HTTP/1.1 206 Partial Content Content-Range: bytes 200-280/2154777 Accept-Ranges: bytes Connection: keep-alive Content-Length: 81 Content-Type: video/mp4 http:////www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0

Slide 9

Slide 9 text

Let's code • first version

Slide 10

Slide 10 text

Proud of ourselves? let's refactor

Slide 11

Slide 11 text

Tests with the new version?

Slide 12

Slide 12 text

Test pyramid unit tests def f(input): output assert output

Slide 13

Slide 13 text

Test pyramid component tests

Slide 14

Slide 14 text

Test pyramid integration tests

Slide 15

Slide 15 text

Which tests are possible? with the actual version

Slide 16

Slide 16 text

Component hierarchy how to avoid that?

Slide 17

Slide 17 text

Introducing components • Live coding

Slide 18

Slide 18 text

Dependencies between components class TopVideoService { val videoGateway = new VideoGateway val playerGateway = new PlayerGateway def topVideos(): [...] = { videoGateway.top() [...] } } trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { val topVideoService = new TopVideoService class TopVideoService { def topVideos(): [...] = { videoGateway.top() [...] } } } Dependencies Provided service

Slide 19

Slide 19 text

Introducing components

Slide 20

Slide 20 text

Introducing components ✔ Components expose services only to other components ✔ One component must depend from another one to use the exposed service

Slide 21

Slide 21 text

Testing with components unit tests

Slide 22

Slide 22 text

Testing with components component tests

Slide 23

Slide 23 text

Testing with components component tests

Slide 24

Slide 24 text

components as traits ✔ Components with exposed services and dependencies ✔ Unit tests ✔ Component tests ✔ Integration tests

Slide 25

Slide 25 text

But... • Testing is not optimal • Which dependency should be mocked? • The compiler can check that for us some drawbacks

Slide 26

Slide 26 text

Exposed service abstract trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { def topVideoService: TopVideoService [...] } trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { val topVideoService = new TopVideoService [...] } Dependencies Provided service

Slide 27

Slide 27 text

Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { def topVideoService: TopVideoService [...] } trait Application extends Controller with TopVideoServiceComp { def index = [...] } object Application extends Application

Slide 28

Slide 28 text

Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { def topVideoService: TopVideoService [...] } trait Application extends Controller with TopVideoServiceComp { def index = [...] } object Application extends Application compilation error compilation error

Slide 29

Slide 29 text

Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { def topVideoService: TopVideoService [...] } trait Application extends Controller with TopVideoServiceComp { def index = [...] } object Application extends Application compilation error compilation error object Application extends Application { override val topVideoService = new TopVideoService }

Slide 30

Slide 30 text

Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { def topVideoService: TopVideoService [...] } trait Application extends Controller with TopVideoServiceComp { def index = [...] } object Application extends Application compilation error compilation error object Application extends Application { override val topVideoService = new TopVideoService } trait TopVideoServiceCompImpl extends TopVideoServiceComp { override val topVideoService = new TopVideoService } object Application extends Application with TopVideoServiceCompImpl

Slide 31

Slide 31 text

Introducing Registry object Application extends Application with PlayerGatewayCompImpl with VideoGatewayCompImpl with HttpClientCompImpl with TopVideoServiceCompImpl object Players extends Players with PlayerGatewayCompImpl with VideoGatewayCompImpl with HttpClientCompImpl with TopVideoServiceCompImpl trait Registry extends HttpClientComp with PlayerGatewayComp with VideoGatewayComp with TopVideoServiceComp trait RuntimeEnvironment extends Registry with VideoGatewayCompImpl with HttpClientCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl object Application extends Application with RuntimeEnvironment object Players extends Players with RuntimeEnvironment

Slide 32

Slide 32 text

Runtime and Test Registries trait Registry extends HttpClientComp with PlayerGatewayComp with VideoGatewayComp with TopVideoServiceComp trait RuntimeEnvironment extends Registry with VideoGatewayCompImpl with HttpClientCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl trait MockEnvironment extends Registry with Mockito { override val httpClient = mock[HttpClient] override val playerGateway = mock[PlayerGateway] override val videoGateway = mock[VideoGateway] override val topVideoService = mock[TopVideoService] }

Slide 33

Slide 33 text

Traits with abstract methods ✔ Components with exposed services and dependencies ✔ Dependencies checked by compiler ✔ Unit tests ✔ Component tests ✔ Integration tests

Slide 34

Slide 34 text

drawback of traits inheritance

Slide 35

Slide 35 text

Introducing self type trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { def topVideoService: TopVideoService [...] } trait TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => def topVideoService: TopVideoService [...] } Dependencies Provided service

Slide 36

Slide 36 text

self type annotation „Any concrete class that mixed in the trait must ensure that its type conforms to the trait's self type“ source: http://docs.scala-lang.org/glossary/#self_type

Slide 37

Slide 37 text

Traits with self types ✔ Components with exposed services and only explicit dependencies ✔ Unit tests ✔ Component tests ✔ Integration tests

Slide 38

Slide 38 text

Parallel @Inject / traits trait HttpClientComp { def httpClient: HttpClient class HttpClient { ... } } trait PlayerGatewayComp { self: HttpClientComp => } trait VideoGatewayComp { self: HttpClientComp => } public class HttpClient { ... } public class PlayerGateway { @Inject private HttpClient httpClient; } public class VideoGateway { @Inject private HttpClient httpClient; }

Slide 39

Slide 39 text

Dependencies Injection • „side effect“ of Cake pattern • dependencies checked by compiler

Slide 40

Slide 40 text

Alternatives for DI • Spring, Guice... • DI with macros: macwire http://typesafe.com/activator/template/macwire-activator

Slide 41

Slide 41 text

Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/

Slide 42

Slide 42 text

Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/

Slide 43

Slide 43 text

Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/

Slide 44

Slide 44 text

Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/

Slide 45

Slide 45 text

DI with cake pattern • dependencies are resolved at compile time • no surprise at runtime

Slide 46

Slide 46 text

traits with self type and implementation • mix interface / implementation • difficult to provide alternative runtime implementation • cannot provide component dependency only for one implementation some drawbacks

Slide 47

Slide 47 text

traits with self type and implementation ex with top videos

Slide 48

Slide 48 text

traits with self type and implementation • decouple interface / implementation Let's fix it

Slide 49

Slide 49 text

Decouple service definition / impl trait TopVideoServiceComp { def topVideoService: TopVideoService trait TopVideoService { def topVideos(): Future[Option[Seq[TopVideo]]] } } trait TopVideoServiceCompImpl extends TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => override val topVideoService = new TopVideoServiceImpl class TopVideoServiceImpl extends TopVideoService { def topVideos(): Future[Option[Seq[TopVideo]]] = videoGateway.top() [...] } } trait TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => def topVideoService: TopVideoService [impl of TopVideoService] } trait TopVideoServiceCompImpl extends TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => override val topVideoService = new TopVideoService }

Slide 50

Slide 50 text

Decouple service definition / impl trait TopVideoServiceComp { def topVideoService: TopVideoService trait TopVideoService { def topVideos(): Future[Option[Seq[TopVideo]]] } } trait TopVideoServiceCompImpl extends TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => override val topVideoService = new TopVideoServiceImpl class TopVideoServiceImpl extends TopVideoService { def topVideos(): Future[Option[Seq[TopVideo]]] = { videoGateway.top() [...] } } } Dependencies specific to impl Provided service definition service impl

Slide 51

Slide 51 text

comparison of all variants trait VideoGatewayComp extends HttpClientComp { val videoGateway = new VideoGateway sealed trait TopVideosResponse [...] class VideoGateway { def top(): Future[TopVideosResponse] = [...] } } ✔ simple ✗ alternative impl very difficult ✗ forget what to override (in tests) 1st version

Slide 52

Slide 52 text

comparison of all variants trait VideoGatewayComp extends HttpClientComp { def videoGateway: VideoGateway sealed trait TopVideosResponse [...] class VideoGateway { def top(): Future[TopVideosResponse] = [...] } } trait VideoGatewayCompImpl extends VideoGatewayComp { override val videoGateway = new VideoGateway } ✔ dependencies checked by compiler ✗ invisible inheritance of other dependencies 2nd version

Slide 53

Slide 53 text

comparison of all variants trait VideoGatewayComp { self: HttpClientComp => def videoGateway: VideoGateway sealed trait TopVideosResponse [...] class VideoGateway { def top(): Future[TopVideosResponse] = [...] } } trait VideoGatewayCompImpl extends VideoGatewayComp { self: HttpClientComp => override val videoGateway = new VideoGateway } ✔ explicit dependent components ✗ no separation interface / impl ✗ boilerplate 3rd version

Slide 54

Slide 54 text

comparison of all variants trait VideoGatewayComp { def videoGateway: VideoGateway sealed trait TopVideosResponse [...] trait VideoGateway { def top(): Future[TopVideosResponse] } } trait VideoGatewayCompImpl extends VideoGatewayComp { self: HttpClientComp => override val videoGateway: VideoGateway = new VideoGatewayImpl class VideoGatewayImpl extends VideoGateway { def top(): Future[TopVideosResponse] = [...] } } ✔ separation interface / impl ✔ flexibility ✗ boilerplate ++ 4th version

Slide 55

Slide 55 text

Number of traits in app components with abstract methods components with self type annotations components with self type annotations and real separation interface / implementation 16 16 20

Slide 56

Slide 56 text

Downside of Cake pattern (1)

Slide 57

Slide 57 text

What do you need? • only DI? • multiple alternative implementations of same service?

Slide 58

Slide 58 text

Downside of Cake pattern (2) • compiler error • let's minimize it

Slide 59

Slide 59 text

Reducing # of compiler errors class RuntimeEnvironment extends Registry with HttpClientCompImpl with VideoGatewayCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl trait RuntimeEnvironment extends Registry with HttpClientCompImpl with VideoGatewayCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl

Slide 60

Slide 60 text

Downside of Cake pattern (3) • compilation speed ✔ minimize it with (abstract) class ✔ let's remove some traits

Slide 61

Slide 61 text

Removing some traits class RuntimeEnvironment extends Registry with HttpClientCompImpl with VideoGatewayCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl trait HttpClientCompImpl extends HttpClientComp { override val httpClient = new HttpClient } trait VideoGatewayCompImpl extends VideoGatewayComp { self: HttpClientComp => override val videoGateway = new VideoGateway } trait PlayerGatewayCompImpl extends PlayerGatewayComp { [...] } trait TopVideoServiceCompImpl extends TopVideoServiceComp { [...] } class RuntimeEnvironment extends Registry { override val httpClient = new HttpClient override val playerGateway = new PlayerGateway override val videoGateway = new VideoGateway override val topVideoService = new TopVideoService }

Slide 62

Slide 62 text

Number of traits components with abstract methods components with self type annotations components with self type annotations and real separation interface / implementation 16 12 20

Slide 63

Slide 63 text

About testing different strategies

Slide 64

Slide 64 text

About testing

Slide 65

Slide 65 text

About testing

Slide 66

Slide 66 text

Do no over-use it! DI ease unit testing

Slide 67

Slide 67 text

further discussion • make cake pattern more manageable with https://github.com/sullivan-/congeal trait UService extends hasDependency[URepository] { ... }

Slide 68

Slide 68 text

Questions? source: https://github.com/yanns/TPA/

Slide 69

Slide 69 text

Yann Simon Software Engineer Blücherstr. 22 10961 Berlin [email protected] twitter: @simon_yann

Slide 70

Slide 70 text

Credits • http://www.flickr.com/photos/cefeida/2306611187/ 62/366: Cake, redux (Magic Madzik) • http://www.flickr.com/photos/jason_burmeister/2125022193 Iced Tree (Jason) • http://www.flickr.com/photos/leandrociuffo/6270204821 Berlin skyline (Leandro Neumann Ciuffo) • http://www.flickr.com/photos/8047705@N02/5668841148/ Slow and steady (John Liu) • http://www.flickr.com/photos/jcapaldi/4201550567/ Bon Appetit (Jim, the Photographer) • http://www.epicfail.com/2012/07/17/about-to-fail-26/