Structure your Play
application with
the 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 OK
Content-Type: application/json; charset=utf-8
Content-L...
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...
Video Backend
GET http://localhost:9002/videos/top
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-L...
Video Backend
GET http://localhost:9002/videos/1/stream.mp4
range: bytes=200-280
HTTP/1.1 206 Partial Content
Content-Rang...
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
Dependencies
class TopVideoService {
val videoGateway =
new VideoGateway
val playerGateway...
Introducing components
Introducing components

✔

✔

Components expose services only to
other components
One component must depend from
another o...
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

✔

Integrati...
But...
some drawbacks
•

Testing is not optimal

•

Which dependency should be mocked?

•

The compiler can check that for...
Exposed service abstract
Dependencies
trait TopVideoServiceComp
extends PlayerGatewayComp
with VideoGatewayComp {

trait T...
Defining the desired service
trait TopVideoServiceComp
extends PlayerGatewayComp
with VideoGatewayComp {

trait Applicatio...
Defining the desired service
trait TopVideoServiceComp
extends PlayerGatewayComp
with VideoGatewayComp {

trait Applicatio...
Defining the desired service
trait TopVideoServiceComp
extends PlayerGatewayComp
with VideoGatewayComp {

trait Applicatio...
Defining the desired service
trait TopVideoServiceComp
extends PlayerGatewayComp
with VideoGatewayComp {

trait Applicatio...
Introducing Registry
object Application
extends Application
with PlayerGatewayCompImpl
with VideoGatewayCompImpl
with Http...
Runtime and Test Registries
trait Registry
extends HttpClientComp
with PlayerGatewayComp
with VideoGatewayComp
with TopVid...
Traits with abstract methods
✔

Components with exposed services and
dependencies

✔

Dependencies checked by compiler

✔
...
drawback of traits inheritance
Introducing self type
Dependencies
trait TopVideoServiceComp
extends PlayerGatewayComp
with VideoGatewayComp {

trait TopV...
self type annotation

„Any concrete class that mixed in the
trait must ensure that its type conforms to
the trait's self t...
Traits with self types
✔

Components with exposed services and
only explicit dependencies

✔

Unit tests

✔

Component tes...
Parallel @Inject / traits
public class HttpClient {
...
}

trait HttpClientComp {
def httpClient: HttpClient
class HttpCli...
Dependencies Injection

•

„side effect“ of Cake pattern

•

dependencies checked by compiler
Alternatives for DI

•

Spring, Guice...

•

DI with macros: macwire
http://typesafe.com/activator/template/macwire-activa...
Resolving dependencies at runtime

source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-im...
Resolving dependencies at runtime

source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-im...
Resolving dependencies at runtime

source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-im...
Resolving dependencies at runtime

source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-im...
DI with cake pattern

•

dependencies are resolved at compile time

•

no surprise at runtime
traits with self type and implementation
some drawbacks
•

mix interface / implementation

•

difficult to provide alterna...
traits with self type and implementation
ex with top videos
traits with self type and implementation
Let's fix it
•

decouple interface / implementation
Decouple service definition / impl
trait TopVideoServiceComp {

trait TopVideoServiceComp {
def topVideoService: TopVideoS...
Decouple service definition / impl
trait TopVideoServiceComp {
def topVideoService: TopVideoService
trait TopVideoService ...
comparison of all variants
1st version

✔
✗
✗

simple
alternative impl very difficult
forget what to override (in tests)

...
comparison of all variants
2nd version

✔
✗

dependencies checked by compiler
invisible inheritance of other
dependencies
...
comparison of all variants
3rd version

✔
✗
✗

explicit dependent components
no separation interface / impl
boilerplate

t...
comparison of all variants
4th version

✔
✔

trait VideoGatewayComp {

✗

separation interface / impl
flexibility
boilerpl...
Number of traits in app

components with
abstract methods

components with
self type
annotations

components with
self typ...
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
trait RuntimeEnvironment extends Registry
with HttpClientCompImpl
with VideoGatewayCompImpl
...
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 Play...
Number of traits

components with
abstract methods

components with
self type
annotations

components with
self type
annot...
About testing
different strategies
About testing
About testing
DI ease unit testing

Do no over-use it!
further discussion

•

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

trait UService extends...
Questions?
source: https://github.com/yanns/TPA/
Yann Simon
Software Engineer
Blücherstr. 22
10961 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...
Nächste SlideShare
Wird geladen in …5
×

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

3.007 Aufrufe

Veröffentlicht am

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

Veröffentlicht in: Technologie, Bildung
0 Kommentare
3 Gefällt mir
Statistik
Notizen
  • Als Erste(r) kommentieren

Keine Downloads
Aufrufe
Aufrufe insgesamt
3.007
Auf SlideShare
0
Aus Einbettungen
0
Anzahl an Einbettungen
440
Aktionen
Geteilt
0
Downloads
20
Kommentare
0
Gefällt mir
3
Einbettungen 0
Keine Einbettungen

Keine Notizen für die Folie

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

  1. 1. Structure your Play application with the cake pattern and test it
  2. 2. Structure a Play application like a cake
  3. 3. We'll build a website for Toys Basketball Association
  4. 4. Architecture
  5. 5. 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"}
  6. 6. 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>
  7. 7. 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]},...]
  8. 8. 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
  9. 9. Let's code • first version
  10. 10. Proud of ourselves? let's refactor
  11. 11. Tests with the new version?
  12. 12. Test pyramid unit tests def f(input): output assert output
  13. 13. Test pyramid component tests
  14. 14. Test pyramid integration tests
  15. 15. Which tests are possible? with the actual version
  16. 16. Component hierarchy how to avoid that?
  17. 17. Introducing components • Live coding
  18. 18. Dependencies between components Dependencies class TopVideoService { val videoGateway = new VideoGateway val playerGateway = new PlayerGateway trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { val topVideoService = new TopVideoService def topVideos(): [...] = { videoGateway.top() [...] } class TopVideoService { def topVideos(): [...] = { videoGateway.top() [...] } } } Provided service }
  19. 19. Introducing components
  20. 20. Introducing components ✔ ✔ Components expose services only to other components One component must depend from another one to use the exposed service
  21. 21. Testing with components unit tests
  22. 22. Testing with components component tests
  23. 23. Testing with components component tests
  24. 24. components as traits ✔ Components with exposed services and dependencies ✔ Unit tests ✔ Component tests ✔ Integration tests
  25. 25. But... some drawbacks • Testing is not optimal • Which dependency should be mocked? • The compiler can check that for us
  26. 26. Exposed service abstract Dependencies trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { val topVideoService = new TopVideoService def topVideoService: TopVideoService [...] [...] } } Provided service
  27. 27. Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { trait Application extends Controller with TopVideoServiceComp { def index = [...] def topVideoService: TopVideoService } [...] } object Application extends Application
  28. 28. Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { trait Application extends Controller with TopVideoServiceComp { def index = [...] def topVideoService: TopVideoService } [...] } ro r r object Applicationn e atio extends lApplication pi om c
  29. 29. Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { trait Application extends Controller with TopVideoServiceComp { def index = [...] def topVideoService: TopVideoService } [...] } ro r r object Applicationn e atio extends lApplication pi om c object Application extends Application { override val topVideoService = new TopVideoService }
  30. 30. Defining the desired service trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { trait Application extends Controller with TopVideoServiceComp { def index = [...] def topVideoService: TopVideoService } [...] } trait TopVideoServiceCompImpl extends TopVideoServiceComp { override val topVideoService = new TopVideoService } ro r r object Applicationn e atio extends lApplication pi om c object Application extends Application { override val topVideoService = new TopVideoService } object Application extends Application with TopVideoServiceCompImpl
  31. 31. 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
  32. 32. 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] }
  33. 33. Traits with abstract methods ✔ Components with exposed services and dependencies ✔ Dependencies checked by compiler ✔ Unit tests ✔ Component tests ✔ Integration tests
  34. 34. drawback of traits inheritance
  35. 35. Introducing self type Dependencies trait TopVideoServiceComp extends PlayerGatewayComp with VideoGatewayComp { trait TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => def topVideoService: TopVideoService def topVideoService: TopVideoService [...] [...] } } Provided service
  36. 36. 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
  37. 37. Traits with self types ✔ Components with exposed services and only explicit dependencies ✔ Unit tests ✔ Component tests ✔ Integration tests
  38. 38. Parallel @Inject / traits public class HttpClient { ... } trait HttpClientComp { def httpClient: HttpClient class HttpClient { ... } public class PlayerGateway { @Inject private HttpClient httpClient; <use httpClient> } trait PlayerGatewayComp { self: HttpClientComp => } <use httpClient> public class VideoGateway { @Inject private HttpClient httpClient; <use httpClient> } } trait VideoGatewayComp { self: HttpClientComp => <use httpClient> }
  39. 39. Dependencies Injection • „side effect“ of Cake pattern • dependencies checked by compiler
  40. 40. Alternatives for DI • Spring, Guice... • DI with macros: macwire http://typesafe.com/activator/template/macwire-activator
  41. 41. Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
  42. 42. Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
  43. 43. Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
  44. 44. Resolving dependencies at runtime source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
  45. 45. DI with cake pattern • dependencies are resolved at compile time • no surprise at runtime
  46. 46. traits with self type and implementation some drawbacks • mix interface / implementation • difficult to provide alternative runtime implementation • cannot provide component dependency only for one implementation
  47. 47. traits with self type and implementation ex with top videos
  48. 48. traits with self type and implementation Let's fix it • decouple interface / implementation
  49. 49. Decouple service definition / impl trait TopVideoServiceComp { trait TopVideoServiceComp { def topVideoService: TopVideoService self: PlayerGatewayComp with VideoGatewayComp => def topVideoService: TopVideoService [impl of TopVideoService] } trait TopVideoService { def topVideos(): Future[Option[Seq[TopVideo]]] } } trait TopVideoServiceCompImpl extends TopVideoServiceComp { trait TopVideoServiceCompImpl extends TopVideoServiceComp { self: PlayerGatewayComp with VideoGatewayComp => self: PlayerGatewayComp with VideoGatewayComp => override val topVideoService = new TopVideoServiceImpl override val topVideoService = new TopVideoService class TopVideoServiceImpl extends TopVideoService { def topVideos(): Future[Option[Seq[TopVideo]]] = videoGateway.top() [...] } } }
  50. 50. Decouple service definition / impl trait TopVideoServiceComp { def topVideoService: TopVideoService trait TopVideoService { def topVideos(): Future[Option[Seq[TopVideo]]] } } Provided service definition Dependencies specific to impl 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() [...] } } } service impl
  51. 51. comparison of all variants 1st version ✔ ✗ ✗ simple alternative impl very difficult forget what to override (in tests) trait VideoGatewayComp extends HttpClientComp { val videoGateway = new VideoGateway sealed trait TopVideosResponse [...] class VideoGateway { def top(): Future[TopVideosResponse] = [...] } }
  52. 52. comparison of all variants 2nd version ✔ ✗ dependencies checked by compiler invisible inheritance of other dependencies 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 }
  53. 53. comparison of all variants 3rd version ✔ ✗ ✗ explicit dependent components no separation interface / impl boilerplate 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 }
  54. 54. comparison of all variants 4th version ✔ ✔ trait VideoGatewayComp { ✗ separation interface / impl flexibility boilerplate ++ 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] = [...] } }
  55. 55. 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
  56. 56. Downside of Cake pattern (1)
  57. 57. What do you need? • only DI? • multiple alternative implementations of same service?
  58. 58. Downside of Cake pattern (2) • compiler error • let's minimize it
  59. 59. Reducing # of compiler errors trait RuntimeEnvironment extends Registry with HttpClientCompImpl with VideoGatewayCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl class RuntimeEnvironment extends Registry with HttpClientCompImpl with VideoGatewayCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl
  60. 60. Downside of Cake pattern (3) • compilation speed ✔ minimize it with (abstract) class ✔ let's remove some traits
  61. 61. Removing some traits class RuntimeEnvironment extends Registry with HttpClientCompImpl with VideoGatewayCompImpl with PlayerGatewayCompImpl with TopVideoServiceCompImpl class RuntimeEnvironment extends Registry { override val httpClient = new HttpClient override val playerGateway = new PlayerGateway override val videoGateway = new VideoGateway override val topVideoService = new TopVideoService 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 { [...] } }
  62. 62. 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
  63. 63. About testing different strategies
  64. 64. About testing
  65. 65. About testing
  66. 66. DI ease unit testing Do no over-use it!
  67. 67. further discussion • make cake pattern more manageable with https://github.com/sullivan-/congeal trait UService extends hasDependency[URepository] {   ... }
  68. 68. Questions? source: https://github.com/yanns/TPA/
  69. 69. Yann Simon Software Engineer Blücherstr. 22 10961 Berlin yann.simon@leanovate.de twitter: @simon_yann
  70. 70. 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/

×