Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Structure a Play application with the cake patt...

Yann Simon
January 16, 2014

Structure a Play application with the cake pattern (and test it)

A challenge during the development of an application is how to add new functions without compromising existing ones.

Using the Cake Pattern, the application can be structured into logical components, thus minimizing the coupling between them and controlling the effects of changes.

You will learn what this pattern is, and how to introduce it step by step in a Play Application. You will be shown how an application designed that way is easy to test, especially with the Play testing API.

Finally, the talk will describe the common pitfalls of the Cake Pattern and how to avoid them.

Video of the talk: http://www.ustream.tv/recorded/42775808

Yann Simon

January 16, 2014
Tweet

More Decks by Yann Simon

Other Decks in Programming

Transcript

  1. 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"}
  2. 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 <binary>
  3. 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
  4. 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
  5. Introducing components ✔ Components expose services only to other components

    ✔ One component must depend from another one to use the exposed service
  6. components as traits ✔ Components with exposed services and dependencies

    ✔ Unit tests ✔ Component tests ✔ Integration tests
  7. But... • Testing is not optimal • Which dependency should

    be mocked? • The compiler can check that for us some drawbacks
  8. 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
  9. 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
  10. 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
  11. 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 }
  12. 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
  13. 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
  14. 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] }
  15. Traits with abstract methods ✔ Components with exposed services and

    dependencies ✔ Dependencies checked by compiler ✔ Unit tests ✔ Component tests ✔ Integration tests
  16. Introducing self type trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp {

    def topVideoService: TopVideoService [...] } trait TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => def topVideoService: TopVideoService [...] } Dependencies Provided service
  17. 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
  18. Traits with self types ✔ Components with exposed services and

    only explicit dependencies ✔ Unit tests ✔ Component tests ✔ Integration tests
  19. Parallel @Inject / traits trait HttpClientComp { def httpClient: HttpClient

    class HttpClient { ... } } trait PlayerGatewayComp { self: HttpClientComp => <use httpClient> } trait VideoGatewayComp { self: HttpClientComp => <use httpClient> } public class HttpClient { ... } public class PlayerGateway { @Inject private HttpClient httpClient; <use httpClient> } public class VideoGateway { @Inject private HttpClient httpClient; <use httpClient> }
  20. Alternatives for DI • Spring, Guice... • DI with macros:

    macwire http://typesafe.com/activator/template/macwire-activator
  21. 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
  22. 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 }
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. Downside of Cake pattern (3) • compilation speed ✔ minimize

    it with (abstract) class ✔ let's remove some traits
  31. 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 }
  32. 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
  33. 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/