In this post I’m gonna discuss about scala
cake pattern, its usage and some real world examples of
cake pattern. One of the main usage of
cake pattern is for dependency injection(there are other usages as well). As an alternative for
cake pattern we can use
Reader Monad for the dependency injection. I have discussed about using
Reader Monad for dependency injection in another post.
Following are the main areas that I’m gonna discuss in this post. More theoretical information about the cake pattern can be found in the Scalable Component Abstractions research paper.
- Scala Traits
- Self typed annotation
- Cake pattern with layered components
- Dependency injection via cake pattern
All the source codes which related to this posts available at gitlab. Please clone the repo and continue the post.
Traits can identifies as “interfaces that can provide concrete members”. Traits are designed to build modular components and having modular component compositions.
1.1 Traits vs Interfaces
Traits can have abstract members(both fields and methods) as well as concrete members. This is the main difference between Java interfaces and traits.
1.2 Traits vs Abstract classes
Traits doesn’t comes with constructor. That is the main difference between traits and abstract classes.
1.3 Traits for multiple inheritance
Traits are multiple inheritance friendly. They avoid
diamond problem in multiple inheritance. Rule to avoid diamond problem is
If there are multiple implementors of a given member, the implementation in the super type that is furthest to the right (in the list of super types) wins.
2. Self typed annotation
Self typed annotation allows to composition of modular components which build via traits. It means self typed annotation provides a way to mixing the functionality of different/multiple traits.
In this scenario we mix
Lambda trait with
Calculus trait via self typed annotation.
The meaning of above expression is
Calculus trait requires
Lambda trait. When instantiating Calculus trait we have to provide a Lambda trait(trait mixing). Other wise it will gives compilation error. Following is the way that trait mixing happens on instantiating.
val universe = new Calculus with Lambda
2.2 Self typed annotation vs Inheritance
Someone can think that why use self typed annotation instead of inheritance. Can’t we achieve the same functionality via inheritance?
Yes we can. Following is the way we implement above scenario via inheritance.
But there are some problems with using inheritance.
- Subclassing will leaks the functionality of super class
- Inheritance breaks encapsulations (Its often said that :))
2.3 Why inheritance is bad
Consider following example. Mixing three traits via self typed annotation.
Main thing to notice here is.
Turing Only requires
Calculus not requires
Lambda. If we access members of
Lambda from the
Turing it will be a compilation failure.
How could we achieve this trait mixing via inheritance?
In here by extending
Calculus trait from
Turing can access
Lambda trait members(Self typed annotation prevents that.). Thats why we say’s inheritance leaks the encapsulation.
3. Cake pattern
Cake pattern allows to build system as a layered components.
System = Composition of multiple components
To build a component it uses traits. Component consists with multiple layers. Because of the layered architecture of a component, it named as
Components are injecting(mixing) to other components via
self typed annotation. We call it dependency injection.
Dependency injection is only a one feature which can achieve via cake pattern. Main purpose of having cake pattern is building layered components.
Layered component is the building block of the cake pattern. Consider following scenario.
A system deals with employees. User can enter employees via command line. Then the system validates the entered employee and save it in the database. Finally employee will uploaded to cloud(via REST API).
If we apply cake pattern to build this system, first we have to identifies the components. In here we can have two main components.
- Employee DB component(Which deals with get, insert, update, delete employee records via the database)
- Employee Service component(Which deals with POST employee details to cloud REST service)
3.2 Employee DB component
Following is the layered design(according to cake pattern) of the Employee DB component.
Actual database related functions comes with
EmployeeDb trait. The functionality of the
EmployeeDb wraps with
EmployeeDbComp trait. We gives a single entry point to
EmployeeDb trait from
val employeeDb: EmployeeDB
EmployeeDb component is an abstract component. We can have multiple implementations of this component. For an instance we can have
EmployeeDbcomponent for unit tests
Following is the
EmployeeDb component implementation.
3.3 Employee Service Component
Following is the layered design of the Employee Service component. Main functionality of this component is dealing with the could REST API in order to
Following is the
Spray based implementation of the Employee Service.
4. Dependency injection
As mentioned previously dependency injection is only a one feature provides by cake patterns. Main purpose of cake pattern is developing layered components.
In previous section we have discussed about the components(layered components) of our system. We have setup two components. Now we need to integrate these components in order to achieve the system functionality.
Assume there is a handler class(
EmployeeHandler) which directly takes user inputs, validates them, and manages employee creation(in database) and uploading. In order to creates and upload employees, handler class needs to have a dependencies to
EmployeeServiceComp. Following is the
We can injecting dependencies to EmployeeHandler via self typed annotations.
this: EmployeeDbComp with EmployeeServiceComp =>
When instantiating the employee handler we have to provide
EmployeeServiceComp. Following is the way to do that.
val employeeHandler = new EmployeeHandler with CassandraEmployeeDbComp with SprayEmployeeServiceComp
In here we are mixing following components with EmployeeHandler
4.2 Component initializing on the fly
There could be some scenarios where you need to have mocked/sample versions of the components. For an instance, while doing the unit tests on
EmployeeHandler. In this scenario you can initialize sample components(
EmployeeServiceComp) on the of the fly.