Scala cake pattern

Be simple, look stylish

(λx.x)eranga
Effectz.AI

--

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.

  1. Scala Traits
  2. Self typed annotation
  3. Cake pattern with layered components
  4. 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.

  1. Subclassing will leaks the functionality of super class
  2. 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.

  1. Employee DB component(Which deals with get, insert, update, delete employee records via the database)
  2. 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

  1. Appache cassandra based EmployeeDb component
  2. MongoDB based EmployeeDb component
  3. Mocked EmployeeDb component for unit tests
  4. 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

  1. EmployeeDbComp implementation(CassandraEmployeeDbComp)
  2. 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

  1. http://lampwww.epfl.ch/~odersky/papers/ScalableComponent.pdf
  2. https://kubuszok.com/2018/cake-antipattern/
  3. https://coderwall.com/p/t_rapw/cake-pattern-in-scala-self-type-annotations-explicitly-typed-self-references-explained
  4. https://www.clianz.com/2016/04/26/scala-cake-pattern/
  5. https://github.com/davidmoten/cake-pattern

--

--