Upload
yanns
View
1.653
Download
0
Tags:
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
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] { ...}
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/