Scala cake pattern
Be simple, look stylish
Background
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.
1. Traits
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.
2.1 Usage
In this scenario we mix Lambda
trait with Calculus
trait via self typed annotation.
this: Lambda
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. Calculus
requires Lambda
. 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
, 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 Cake-Pattern
.
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.
3.1 Component
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 EmployeeDbComp
trait
val employeeDb: EmployeeDB
EmployeeDb
component is an abstract component. We can have multiple implementations of this component. For an instance we can have
Appache cassandra
basedEmployeeDb
componentMongoDB
basedEmployeeDb
component- Mocked
EmployeeDb
component for unit tests - etc
Following is the cassnadra
based 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 GET
, PUT
, POST
, DELETE
employees.
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.
4.1 EmployeeHandler
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 EmployeeDbComp
and EmployeeServiceComp
. Following is the EmployeeHandler
implementation
We can injecting dependencies to EmployeeHandler via self typed annotations.
this: EmployeeDbComp with EmployeeServiceComp =>
When instantiating the employee handler we have to provide EmployeeDbComp
and 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
EmployeeDbComp
implementation(CassandraEmployeeDbComp
)EmployeeServiceComp
implementation(SprayEmployeeServiceComp
)
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(EmployeeDbComp
and EmployeeServiceComp
) on the of the fly.
Reference
- http://lampwww.epfl.ch/~odersky/papers/ScalableComponent.pdf
- https://kubuszok.com/2018/cake-antipattern/
- https://coderwall.com/p/t_rapw/cake-pattern-in-scala-self-type-annotations-explicitly-typed-self-references-explained
- https://www.clianz.com/2016/04/26/scala-cake-pattern/
- https://github.com/davidmoten/cake-pattern