70
Structure your Play application with the cake pattern and test it

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

  • Upload
    yanns

  • View
    1.653

  • Download
    0

Embed Size (px)

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

Citation preview

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

Structure your Playapplication withthe cake pattern

and test it

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

Structure a Play application like a cake

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

We'll build a website for

Toys Basketball Association

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

Architecture

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

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

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

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>

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

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

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

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

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

Let's code

• first version

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

Proud of ourselves?

let's refactor

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

Tests with the new version?

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

Test pyramid

unit tests

def f(input): output

assert output

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

Test pyramid

component tests

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

Test pyramid

integration tests

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

Which tests are possible?

with the actual version

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

Component hierarchy

how to avoid that?

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

Introducing components

• Live coding

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

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

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

Introducing components

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

Introducing components

✔ Components expose services only to other components

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

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

Testing with components

unit tests

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

Testing with components

component tests

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

Testing with components

component tests

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

components as traits

✔ Components with exposed services and dependencies

✔ Unit tests

✔ Component tests

✔ Integration tests

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

But...

• Testing is not optimal

• Which dependency should be mocked?

• The compiler can check that for us

some drawbacks

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

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

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

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

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

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

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

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}

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

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

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

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

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

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

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

Traits with abstract methods

✔ Components with exposed services and dependencies

✔ Dependencies checked by compiler

✔ Unit tests

✔ Component tests

✔ Integration tests

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

drawback of traits inheritance

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

Introducing self type

trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp {

def topVideoService: TopVideoService

[...]}

trait TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp =>

def topVideoService: TopVideoService

[...]}

Dependencies

Provided service

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

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

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

Traits with self types

✔ Components with exposed services and only explicit dependencies

✔ Unit tests

✔ Component tests

✔ Integration tests

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

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

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

Dependencies Injection

• „side effect“ of Cake pattern

• dependencies checked by compiler

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

Alternatives for DI

• Spring, Guice...

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

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

Resolving dependencies at runtime

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

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

Resolving dependencies at runtime

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

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

Resolving dependencies at runtime

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

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

Resolving dependencies at runtime

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

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

DI with cake pattern

• dependencies are resolved at compile time

• no surprise at runtime

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

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

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

traits with self type and implementation

ex with top videos

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

traits with self type and implementation

• decouple interface / implementation

Let's fix it

Page 49: Structure your Play application with the cake pattern (and test 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}

Page 50: Structure your Play application with the cake pattern (and test 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() [...] } }}

Dependencies specific to impl

Provided servicedefinition

service impl

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

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

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

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

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

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

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

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

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

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

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

Downside of Cake pattern (1)

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

What do you need?

• only DI?

• multiple alternative implementations of same service?

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

Downside of Cake pattern (2)

• compiler error

• let's minimize it

Page 59: Structure your Play application with the cake pattern (and test 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

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

Downside of Cake pattern (3)

• compilation speed

✔ minimize it with (abstract) class

✔ let's remove some traits

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

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}

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

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

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

About testing

different strategies

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

About testing

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

About testing

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

Do no over-use it!

DI ease unit testing

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

further discussion

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

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

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

Questions?

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

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

Yann SimonSoftware Engineer

Blücherstr. 2210961 Berlin

[email protected]

twitter: @simon_yann

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

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/