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