Scala Tutorial - Learn How To Use Traits For Dependency Injection

By Nadim Bahadoor | Last updated: March 16, 2018 at 13:25 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.

 

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 Parameters and Learn How To Extend Multiple Traits.

Steps

1. Create a trait which knows how to do create, read, update and delete operations CRUD to a given database

Let's start with defining a trait called DonutDatabase which will provide the method signatures to represent a Data Access Layer.


println("Step 1: Create a trait which knows how to do create, read, update and delete operations CRUD to a given database")
trait DonutDatabase[A] {

 def addOrUpdate(donut: A): Long

 def query(donut: A): A

 def delete(donut: A): Boolean
}

NOTE:

  • The trait is defined with type parameter DonutDatabase[A]. If you are unsure about type parameters, please review the previous tutorial.

2. Create a class which extends trait DonutDatabase and knows how to perform CRUD operations with Apache Cassandra as storage layer

Let's assume that our storage layer is Apache Cassandra. We create a class called CassandraDonutStore[A] which will facilitate all the CRUD operations by extending trait DonutDatabase[A] from step 1.


println("\nStep 2: Create a class which extends trait DonutDatabase and knows how to perform CRUD operations with Apache Cassandra as storage layer")
class CassandraDonutStore[A] extends DonutDatabase[A] {

 override def addOrUpdate(donut: A): Long = {
  println(s"CassandraDonutDatabase-> addOrUpdate method -> donut: $donut")
  1
 }

 override def query(donut: A): A = {
  println(s"CassandraDonutDatabase-> query method -> donut: $donut")
  donut
 }

 override def delete(donut: A): Boolean = {
  println(s"CassandraDonutDatabase-> delete method -> donut: $donut")
  true
 }
}

 

3. Create a trait which will define the methods for a data access layer and will require dependency injection for DonutDatabase

Next, we create a trait called DonutShoppingCartDao which will be the main API to perform CRUD operations versus some storage layer. To this end, we define a val of type DonutDatabase[A] which will be injected as shown below.


println("\nStep 3: Create a trait which will define the methods for a data access layer and will require dependency injection for DonutDatabase")
trait DonutShoppingCartDao[A] {

 val donutDatabase: DonutDatabase[A] // dependency injection

 def add(donut: A): Long = {
  println(s"DonutShoppingCartDao-> add method -> donut: $donut")
  donutDatabase.addOrUpdate(donut)
 }

 def update(donut: A): Boolean = {
  println(s"DonutShoppingCartDao-> update method -> donut: $donut")
  donutDatabase.addOrUpdate(donut)
  true
 }

 def search(donut: A): A = {
  println(s"DonutShoppingCartDao-> search method -> donut: $donut")
  donutDatabase.query(donut)
 }

 def delete(donut: A): Boolean = {
  println(s"DonutShoppingCartDao-> delete method -> donut: $donut")
  donutDatabase.delete(donut)
 }

}

NOTE:

  • By defining: val donutDatabase: DonutDatabase[A], we are not tying ourselves with any particular storage layer. Instead, the class DonutShoppingCartDao only mandates a type of DonutDatabase[A].
  • In the previous example, we have only seen traits which had method signatures. Scala allows traits to also contain method implementations as shown above.

4. Create a trait which will define the methods for checking donut inventory and will require dependency injection for DonutDatabase

Similar to the previous examples, we create another trait which will be responsible to checking donut inventory.


println("\nStep 4: Create a trait which will define the methods for checking donut inventory and will require dependency injection for DonutDatabase")
trait DonutInventoryService[A] {

 val donutDatabase: DonutDatabase[A] // dependency injection

 def checkStockQuantity(donut: A): Int = {
  println(s"DonutInventoryService-> checkStockQuantity method -> donut: $donut")
  donutDatabase.query(donut)
  1
 }

}

NOTE:

  • Trait DonutInventoryService[A] also mandates a type DonutDatabase: val donutDatabase: DonutDatabase[A]

5. Create a trait which will act as a facade and extends multiple traits namely trait DonutShoppingCartDao and trait DonutInventoryService. 

We now create a facade which extends multiple traits namely trait DonutShoppingCartDao and trait DonutInventoryService. We also inject an implementation of our storage layer which in this case is an instance of CassandraDonutStore


println("\nStep 5: Create a trait which will act as a facade which extends multiple traits namely trait DonutShoppingCartDao and trait DonutInventoryService. It also inject the correct DonutDatabase implementation - a CassandraDonutStore")
trait DonutShoppingCartServices[A] extends DonutShoppingCartDao[A] with DonutInventoryService[A] {
 override val donutDatabase: DonutDatabase[A] = new CassandraDonutStore[A]()
}

NOTE:

  • We made use of the override val keywords.

6. Create a DonutShoppingCart class which extends a single facade named DonutShoppingCartServices to expose all the underlying features required by a DonutShoppingCart

With the facade DonutShoppingCartServices[A] defined in Step 5, we can now create a class DonutShoppingCart[A] which extends it.


println("\nStep 6: Create a DonutShoppingCart class which extends a single facade named DonutShoppingCartServices to expose all the underlying features required by a DonutShoppingCart")
 class DonutShoppingCart[A] extends DonutShoppingCartServices[A] {

}

 

7. Create an instance of DonutShoppingCart and call the add, update, search and delete methods

You can now create an instance of DonutShoppingCart and call the add, update, search and delete methods which were inherited from trait DonutShoppingCartDao.


println("\nStep 7: Create an instance of DonutShoppingCart and call the add, update, search and delete methods")
val donutShoppingCart: DonutShoppingCart[String] = new DonutShoppingCart[String]()
donutShoppingCart.add("Vanilla Donut")
donutShoppingCart.update("Vanilla Donut")
donutShoppingCart.search("Vanilla Donut")
donutShoppingCart.delete("Vanilla Donut")

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


Step 7: Create an instance of DonutShoppingCart and call the add, update, search and delete methods
DonutShoppingCartDao-> add method -> donut: Vanilla Donut
CassandraDonutDatabase-> addOrUpdate method -> donut: Vanilla Donut
DonutShoppingCartDao-> update method -> donut: Vanilla Donut
CassandraDonutDatabase-> addOrUpdate method -> donut: Vanilla Donut
DonutShoppingCartDao-> search method -> donut: Vanilla Donut
CassandraDonutDatabase-> query method -> donut: Vanilla Donut
DonutShoppingCartDao-> delete method -> donut: Vanilla Donut
CassandraDonutDatabase-> delete method -> donut: Vanilla Donut

 

8. Call the checkStockQuantity method

You can now call the checkStockQuantity() method which was inherited from trait DonutInventoryService.


println("\nStep 8: Call the checkStockQuantity method")
donutShoppingCart.checkStockQuantity("Vanilla Donut")

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


Step 8: Call the checkStockQuantity method
DonutInventoryService-> checkStockQuantity method -> donut: Vanilla Donut
CassandraDonutDatabase-> query method -> donut: Vanilla Donut

This concludes our tutorial on Learn How To Use Traits For Dependency Injection 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:

  • Create a trait which knows how to do create, read, update and delete operations CRUD to a given database
  • Create a class which extends trait DonutDatabase and knows how to perform CRUD operations with Apache Cassandra as storage layer
  • Create a trait which will define the methods for a data access layer and will require dependency injection for DonutDatabase
  • Create a trait which will define the methods for checking donut inventory and will require dependency injection for DonutDatabase
  • Create a trait which will act as a facade which extends multiple traits namely trait DonutShoppingCartDao and trait DonutInventoryService
  • Create a DonutShoppingCart class which extends a single facade named DonutShoppingCartServices to expose all the underlying features required by a DonutShoppingCart
  • Create an instance of DonutShoppingCart and call the add, update, search and delete methods
  • Call the checkStockQuantity method

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 go over Dependency Injection in further details and show how to avoid the pitfalls of the infamous Cake Pattern.

Nadim Bahadoor on FacebookNadim Bahadoor on GithubNadim Bahadoor on LinkedinNadim Bahadoor on Twitter
Nadim Bahadoor
Technology and Finance Consultant with over 14 years of hands-on experience building large scale systems in the Financial (Electronic Trading Platforms), Risk, Insurance and Life Science sectors. I am self-driven and passionate about Finance, Distributed Systems, Functional Programming, Big Data, Semantic Data (Graph) and Machine Learning.
Other allaboutscala.com tutorials you may like: