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
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
}
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. 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. 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. 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. 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. 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. 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. Traits with abstract methods
✔
Components with exposed services and
dependencies
✔
Dependencies checked by compiler
✔
Unit tests
✔
Component tests
✔
Integration tests
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. 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. Traits with self types
✔
Components with exposed services and
only explicit dependencies
✔
Unit tests
✔
Component tests
✔
Integration tests
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>
}
40. Alternatives for DI
•
Spring, Guice...
•
DI with macros: macwire
http://typesafe.com/activator/template/macwire-activator
41. Resolving dependencies at runtime
source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
42. Resolving dependencies at runtime
source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
43. Resolving dependencies at runtime
source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
44. Resolving dependencies at runtime
source: http://apmblog.compuware.com/2013/12/18/the-hidden-class-loading-performance-impact-of-the-spring-framework/
45. DI with cake pattern
•
dependencies are resolved at compile time
•
no surprise at runtime
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
48. traits with self type and implementation
Let's fix it
•
decouple interface / implementation
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. 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. 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. 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. 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
}
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
57. What do you need?
•
only DI?
•
multiple alternative implementations of
same service?
58. Downside of Cake pattern (2)
•
compiler error
•
let's minimize it
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. Downside of Cake pattern (3)
•
compilation speed
✔
minimize it with (abstract) class
✔
let's remove some traits
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. 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
67. further discussion
•
make cake pattern more manageable with
https://github.com/sullivan-/congeal
trait UService extends hasDependency[URepository] {
...
}