Scala Tutorial - Learn How To Use Traits For Dependency Injection Part 2 - Avoid Cake Pattern

By Nadim Bahadoor | Last updated: July 25, 2017 at 14:30 pm

Overview

In this tutorial, we will learn how to use traits for dependency injection using the standard Scala library and without having to import any third party tools and libraries.

 

This tutorial is a continuation from the previous tutorial on Learn How to Use Traits for Dependency Injection Part 1. In particular, we will show how to avoid the infamous Cake Pattern and instead opt for a Facade solution. As you become more fluent in Functional Programming, the Facade solution be very handy to help you  design Monadic Functions.

 

We will build on what we have learned from the previous tutorials on Learn How To Create And Extend TraitLearn How to Create Trait With Type ParametersLearn How To Extend Multiple Traits and Learn How to Use Traits for Dependency Injection Part 1.

 

Steps

1. How to define a class to encapsulate inventory services

Let's start with defining a class named DonutInventoryService which will encapsulate methods to check for Donut inventory.


println("Step 1: How to define a class to encapsulate inventory services")
class DonutInventoryService[T] {
 def checkStock(donut: T): Boolean = {
  println("DonutInventoryService->checkStock")
  true
 }
}

NOTE:

  • Instead of a class, we could have used a trait. We are keeping this example simple and will make use of traits for the main facade.

2. How to define a class to encapsulate pricing services

We create another class named DonutPricingService which will have all the methods on how to calculate the price for a donut.


println("\nStep 2: How to define a class to encapsulate pricing services")
class DonutPricingService[T] {
 def calculatePrice(donut: T): Double = {
  println("DonutPricingService->calculatePrice")
  2.50
 }
}

NOTE:

  • Instead of a class, we could have used a trait. We are keeping this example simple and will make use of traits for the main facade.

3. How to define a class to encapsulate creating a donut order

We create a third class named DonutOrderService which will be responsible for saving a donut order to some underlying database.


println("\nStep 3: How to define a class to encapsulate creating a donut order")
class DonutOrderService[T] {
 def createOrder(donut: T, quantity: Int, price: Double): Int = {
  println(s"Saving donut order to database: donut = $donut, quantity = $quantity, price = $price")
  100 // the id of the booked order
 }
}

NOTE:

  • Instead of a class, we could have used a trait. We are keeping this example simple and will make use of traits for the main facade.

4. How to define a class to encapsulate shopping cart services

With our services defined from Step 1, 2 and 3, we create a DonutShoppingCartService class which requires a DonutInventoryService, a DonutPricingService and a DonutOrderService. It provides a method called bookOrder(...) which will check for donut inventory. If we have that donut in stock, it will then calculate the price for the donut and finally save an order to some underlying database.


println("\nStep 4: How to define a class to encapsulate shopping cart services")
class DonutShoppingCartService[T] (
  donutInventoryService: DonutInventoryService[T],
  donutPricingService: DonutPricingService[T],
  donutOrderService: DonutOrderService[T]) {

 def bookOrder(donut: T, quantity: Int): Int = {
  println("DonutShoppingCartService->bookOrder")

  donutInventoryService.checkStock(donut) match {
  case true =>
   val price = donutPricingService.calculatePrice(donut)
   donutOrderService.createOrder(donut, quantity, price) // the id of the booked order

  case false =>
   println(s"Sorry donut $donut is out of stock!")
   -100 // return some error code to identify out of stock
  }
 }
}

 

5. How to define a trait to encapsulate all the services for Donut store

We now create a trait similar to the previous tutorial, which initializes all the donut services from Step 1, 2, 3 and 4.


println("\nStep 5: How to define a trait to encapsulate all the services for Donut store")
trait DonutStoreServices {
 val donutInventoryService = new DonutInventoryService[String]
 val donutPricingService = new DonutPricingService[String]
 val donutOrderService = new DonutOrderService[String]
 val donutShoppingCartService = new DonutShoppingCartService(donutInventoryService, donutPricingService, donutOrderService)
}

 

6. How to define a facade to expose functionality of DonutStoreServices

We can now create a facade named DonutStoreAppController which will expose the functionality for DonutStoreServices defined in the previous Step 5.


println("\nStep 6: How to define a facade to expose functionality of DonutStoreServices")
trait DonutStoreAppController {
  this: DonutStoreServices =>

 def bookOrder(donut: String, quantity: Int): Int = {
  println("DonutStoreAppController->bookOrder")
  donutShoppingCartService.bookOrder(donut, quantity)
 }
}

 

7. How to create a Donut store app which extends the facade with the required services

Similar to the previous tutorial, we create an entry point for our donut application named DonutStoreApp which extends the DonutStoreAppController and injects the required DonutStoreServices as follows:


println("\nStep 7: How to create a Donut store app which extends facade from Step 5 and injects the required donut services from Step 4")
object DonutStoreApp extends DonutStoreAppController with DonutStoreServices

8. How to call the bookOrder method of the DonutStoreApp

You can now call the bookOrder() method from the DonutStoreApp as follows:


println("\nStep 8: How to call the bookOrder method of the Donut store app from Step 7")
DonutStoreApp.bookOrder("Vanilla Donut", 10)

You should see the following output when you run your Scala application in IntelliJ:


Step 8: How to call the bookOrder method of the Donut store app from Step 7
DonutStoreAppController->bookOrder
DonutShoppingCartService->bookOrder
DonutInventoryService->checkStock
DonutPricingService->calculatePrice
Saving donut order to database: donut = Vanilla Donut, quantity = 10, price = 2.5

 

9. Test DonutStoreApp by injecting a mocked version of DonutStoreServices

You may be asking yourself why did we go through all the trouble of having a single facade? One of the most obvious benefit is to make our DonutStoreApp easily testable. All you need to do is to create a trait which will mock the DonutStoreServices.

println("\nStep 9: Test DonutStoreApp by injecting a mocked version of DonutStoreServices")
trait MockedDonutStoreServices extends DonutStoreServices {
  override val donutInventoryService: DonutInventoryService[String] = ???
  override val donutPricingService: DonutPricingService[String] = ???
  override val donutOrderService: DonutOrderService[String] = ???
  override val donutShoppingCartService: DonutShoppingCartService[String] = new DonutShoppingCartService[String](
  donutInventoryService, donutPricingService, donutOrderService)
}

NOTE:

  • The mock versions for each service have no implementations by using the syntax ???
  • In a real application, you will most likely be using your favourite Mocking Framework such as Mockito.

10. Create a Mocked Donut Store App and inject mocked donut services

Using the MockedDonutStoreServices trait from Step 9, you can easily create a mocked version for a Donut store application as follows:

println("\nStep 1: Create a Mocked Donut Store App and inject mocked donut services")
object MockedDonutStoreApp extends DonutStoreAppController with MockedDonutStoreServices

This concludes our tutorial on Learn How To Use Traits For Dependency Injection Part 2 - Avoid Cake Pattern and I hope you've found it useful!

 

Stay in touch via Facebook and Twitter for upcoming tutorials!

 

Don't forget to like and share this page :)

Summary

In this tutorial, we went over the following:

  • How to define a class to encapsulate inventory services
  • How to define a class to encapsulate pricing services
  • How to define a class to encapsulate creating a donut order
  • How to define a class to encapsulate shopping cart services
  • How to define a trait to encapsulate all the services for Donut store
  • How to define a facade to expose functionality of DonutStoreServices
  • How to create a Donut store app which extends the facade with the required services
  • How to call the bookOrder method of the DonutStoreApp
  • Test DonutStoreApp by injecting a mocked version of DonutStoreServices
  • Create a Mocked Donut Store App and inject mocked donut services

Tip

  • We've kept the trait type parameters example simple but it would be good to review variance namely covariance and contra-variance type parameters.
  • In upcoming tutorials in this Chapter, we will also show how to use traits to build some pure Functional Programming constructs such as Monoids and Functors and much more!

Source Code

The source code is available on the allaboutscala GitHub repository.

 

What's Next

In the next tutorial, I will show you how to use traits to implement the Factory Pattern.

Nadim Bahadoor on FacebookNadim Bahadoor on GithubNadim Bahadoor on LinkedinNadim Bahadoor on Twitter
Nadim Bahadoor
Senior Software Developer | Nephila Capital
Founder of allaboutscala.com. I have over 10 years of experience in building large scale real-time trading systems in the financial industry. Passionate about Distributed Systems, Scala, Big Data and Functional Programming. Stay in touch for upcoming tutorials!
Other allaboutscala.com tutorials you may like:

Share this article on