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

Post on 19-Jun-2015

1.654 views 0 download

Tags:

description

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 Sources: https://github.com/yanns/TPA Sources of the final version: https://github.com/yanns/TPA/tree/master/frontend/TBA_05_final

Transcript of Structure your Play application with the cake pattern (and test it)

Structure your Playapplication withthe cake pattern

and test it

Structure a Play application like a cake

We'll build a website for

Toys Basketball Association

Architecture

Player Backend

GET http://localhost:9001/players/1

HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8Content-Length: 91

{"id":1,"name":"James P. Sullivan","height":"34 cm","weight":"370 g","team":"Monstropolis"}

Player Backend

GET http://localhost:9001/players/1/photo

HTTP/1.1 200 OKLast-Modified: Thu, 12 Dec 2013 13:11:02 GMTEtag: "4911f28b55213..."Content-Length: 1753583Cache-Control: max-age=3600Content-Type: image/jpegDate: Mon, 23 Dec 2013 10:01:15 GMT

<binary>

Video Backend

GET http://localhost:9002/videos/top

HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8Content-Length: 83

[{"id":1,"summary":"dunk","players":[1]},...]

Video Backend

GET http://localhost:9002/videos/1/stream.mp4range: bytes=200-280

HTTP/1.1 206 Partial ContentContent-Range: bytes 200-280/2154777Accept-Ranges: bytesConnection: keep-aliveContent-Length: 81Content-Type: video/mp4

http:////www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0

Let's code

• first version

Proud of ourselves?

let's refactor

Tests with the new version?

Test pyramid

unit tests

def f(input): output

assert output

Test pyramid

component tests

Test pyramid

integration tests

Which tests are possible?

with the actual version

Component hierarchy

how to avoid that?

Introducing components

• Live coding

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

Introducing components

Introducing components

✔ Components expose services only to other components

✔ One component must depend from another one to use the exposed service

Testing with components

unit tests

Testing with components

component tests

Testing with components

component tests

components as traits

✔ Components with exposed services and dependencies

✔ Unit tests

✔ Component tests

✔ Integration tests

But...

• Testing is not optimal

• Which dependency should be mocked?

• The compiler can check that for us

some drawbacks

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

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

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

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}

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

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

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]}

Traits with abstract methods

✔ Components with exposed services and dependencies

✔ Dependencies checked by compiler

✔ Unit tests

✔ Component tests

✔ Integration tests

drawback of traits inheritance

Introducing self type

trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp {

def topVideoService: TopVideoService

[...]}

trait TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp =>

def topVideoService: TopVideoService

[...]}

Dependencies

Provided service

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

Traits with self types

✔ Components with exposed services and only explicit dependencies

✔ Unit tests

✔ Component tests

✔ Integration tests

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>}

Dependencies Injection

• „side effect“ of Cake pattern

• dependencies checked by compiler

Alternatives for DI

• Spring, Guice...

• DI with macros: macwirehttp://typesafe.com/activator/template/macwire-activator

Resolving dependencies at runtime

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

Resolving dependencies at runtime

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

Resolving dependencies at runtime

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

Resolving dependencies at runtime

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

DI with cake pattern

• dependencies are resolved at compile time

• no surprise at runtime

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

traits with self type and implementation

ex with top videos

traits with self type and implementation

• decouple interface / implementation

Let's fix it

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}

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 servicedefinition

service impl

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

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

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

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

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

Downside of Cake pattern (1)

What do you need?

• only DI?

• multiple alternative implementations of same service?

Downside of Cake pattern (2)

• compiler error

• let's minimize it

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

Downside of Cake pattern (3)

• compilation speed

✔ minimize it with (abstract) class

✔ let's remove some traits

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}

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

About testing

different strategies

About testing

About testing

Do no over-use it!

DI ease unit testing

further discussion

• make cake pattern more manageable with https://github.com/sullivan-/congeal

trait UService extends hasDependency[URepository] {  ...}

Questions?

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

Yann SimonSoftware Engineer

Blücherstr. 2210961 Berlin

yann.simon@leanovate.de

twitter: @simon_yann

Credits

• http://www.flickr.com/photos/cefeida/2306611187/62/366: Cake, redux (Magic Madzik)

• http://www.flickr.com/photos/jason_burmeister/2125022193Iced Tree (Jason)

• http://www.flickr.com/photos/leandrociuffo/6270204821Berlin 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/