Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using Cockroach With Scala: An Introduction

DZone's Guide to

Using Cockroach With Scala: An Introduction

CockroachDB is a distributed SQL database built on top of a transactional and consistent key-value store. Learn how to use it with Scala.

· Database Zone ·
Free Resource

RavenDB vs MongoDB: Which is Better? This White Paper compares the two leading NoSQL Document Databases on 9 features to find out which is the best solution for your next project.  

Today, we are going to discuss how to use Scala with CockroachDB, a distributed SQL database built on top of a transactional and consistent key-value store. Before starting the journey, you may want to check out this introduction to CockroachDB first.

Before getting started with the code, set up Cockroach DB on your local environment by following these steps.

Download CockroachDB and follow the instructions provided there. Then, run the following commands for starting the Nodes:

# Start node 1:

cockroach start --insecure \
--store=CockroachTest-1 \
--host=localhost

# In a new terminal, start node 2:

cockroach start --insecure \
--store=CockroachTest-2 \
--host=localhost \
--port=26258 \
--http-port=8081 \
--join=localhost:26257

# In a new terminal, start node 3:

cockroach start --insecure \
--store=CockroachTest-3 \
--host=localhost \
--port=26259 \
--http-port=8082 \
--join=localhost:26257

CockroachDB is up and running. Check localhost:8080 to see the Cockroach UI.

Now, create the user:

cockroach user set maxroach --insecure

After creating the user, create the database and set privileges:

cockroach sql --insecure -e 'CREATE DATABASE test'

cockroach sql --insecure -e 'GRANT ALL ON DATABASE test TO maxroach'

Run the following commands for creating the table:

CREATE TABLE IF NOT EXISTS test.user 
  ( 
     id    VARCHAR(10) PRIMARY KEY, 
     name  TEXT, 
     email VARCHAR(60) 
  ); 

After running all these commands, we are ready with our local environment set up on port 8080.

Let's go step by step to develop this REST API. After doing so, you'll able to run this API on your server and start hitting REST endpoints.

When we start creating projects, we have to use the following dependencies:

"com.typesafe.akka"		%%	"akka-http-core"  	%   "10.0.7",
"com.typesafe.slick"	%%	"slick"				%   "3.2.0",
"com.typesafe.slick" 	%%	"slick-hikaricp"	%   "3.2.0"1,
"io.spray"     			%%  "spray-json"        %   "1.3.3",
"net.codingwell"		%%  "scala-guice"       %   "4.1.0",
"org.postgresql"		%%  "postgresql"       	%   "9.4.1212",
"io.spray"       		%%  "spray-json"        %	"1.3.3",
"net.codingwell" 		%%	"scala-guice"       %	"4.1.0",

Create a DB component with Postgres and Slick to connect with the database:

trait PostgresDbComponent extends DBComponent {

  val driver = PostgresProfile
  import driver.api.Database

  val db: Database = DBConnection.connectionPool
}

object DBConnection {
  val connectionPool = Database.forConfig("db")
}
import slick.jdbc.JdbcProfile

trait DBComponent {

  val driver: JdbcProfile

  import driver.api._

  val db: Database
}

Create mappings for binding the data with the case class:

package com.knoldus.DAO.user.mappings

import com.knoldus.DAO.db.DBComponent
import slick.lifted.ProvenShape

case class User(id: String = "", name: String, email: String)

trait UserMapping {
  this: DBComponent =>

  import driver.api._

  class UserMapping(tag: Tag) extends Table[User](tag, "user") {
    def id: Rep[String] = column[String]("id", O.PrimaryKey)

    def name: Rep[String] = column[String]("name")

    def email: Rep[String] = column[String]("email", O.Unique)

    def * : ProvenShape[User] = (
      id,
      name,
      email
    ) <> (User.tupled, User.unapply)
  }

  val userInfo: TableQuery[UserMapping] = TableQuery[UserMapping]
}

Create a User Component that will interact with CockroachDB to perform the DB operations:

package com.knoldus.DAO.user

import com.google.inject.ImplementedBy
import com.knoldus.DAO.db.{DBComponent, PostgresDbComponent}
import com.knoldus.DAO.user.mappings.{User, UserMapping}

import scala.concurrent.Future

@ImplementedBy(classOf[UserPostgresComponent])
trait UserComponent extends UserMapping {

  this: DBComponent =>

  import driver.api._

  /**
    * Inserts user into database
    *
    * @param user
    * @return
    */
  def insert(user: User): Future[Int] = {
    db.run(userInfo += user)
  }

  /**
    * Fetches user detail using email
    *
    * @param email
    * @return Future[Option[User]]
    **/
  def getUserByEmail(email: String): Future[Option[User]] = {
    db.run(userInfo.filter(user => user.email === email).result.headOption)
  }

  /**
    * Fetches user record with the help of userId
    *
    * @param userId
    * @return Option[User]
    */
  def getUserByUserId(userId: String): Future[Option[User]] = {
    db.run(userInfo.filter(user => user.id === userId).result.headOption)
  }

  /**
    * Fetches All user from DB
    *
    * @return
    */
  def getAllUsers: Future[List[User]] = {
    db.run(userInfo.to[List].result)
  }

  /**
    * Checks if user with user id exists
    *
    * @param userId
    * @return
    */
  def isUserIdExists(userId: String): Future[Boolean] = {
    val query = userInfo.filter(user => user.id === userId).exists
    db.run(query.result)
  }
}

class UserPostgresComponent extends UserComponent with PostgresDbComponent

Create a User Service that will work as a bridge between the Routes API and User Component:

package com.knoldus.service

import com.google.inject.Inject
import com.knoldus.DAO.user.UserComponent
import com.knoldus.DAO.user.mappings.User

import scala.concurrent.Future

class UserService @Inject()(userComponent: UserComponent){

  /**
    * Inserts user object in db
    *
    * @param user
    */
  def insert(user: User): Future[Int] = userComponent.insert(user)

  /**
    * Get user by user id
    *
    * @param id
    * @return
    */
  def getUserByUserId(id: String): Future[Option[User]] = userComponent.getUserByUserId(id)

  /**
    * Get user by email id
    *
    * @param email
    * @return
    */
  def getUserByEmail(email: String): Future[Option[User]] = userComponent.getUserByEmail(email)

  /**
    * Get list of all users
    *
    * @return
    */
  def getAllUsers(): Future[List[User]] = userComponent.getAllUsers

  /**
    * Check whether user exists with user id
    *
    * @param userId
    * @return
    */
  def isUserIdExists(userId: String): Future[Boolean] = userComponent.isUserIdExists(userId)

}

Finally, create Routes:

package com.knoldus.api

import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import com.google.inject.Inject
import com.knoldus.DAO.user.mappings.User
import com.knoldus.service.UserService
import com.knoldus.util.JsonHelper

import scala.util.{Failure, Success}

class UserApi@Inject()(userService: UserService) extends JsonHelper {

  /**
    * Default route
    *
    * @return
    */
  def welcomeRoute: Route = path("") {
    get {
      complete("Cockroach Db starter kit with Akka http")
    }
  }

  /**
    * Creates http route to insert user object
    *
    * @return
    */
  def insertUser: Route = path("user" / "add") {
    (post & entity(as[User])) { user =>
      onComplete(userService.isUserIdExists(user.id)) {
        case Success(res) => validate(!res, s"User with id '${user.id}' already exists") {
          onComplete(userService.insert(user)) {
            case Success(result) => complete("User added successfully")
            case Failure(ex) => complete(HttpResponse(StatusCodes.InternalServerError, entity = ex.getMessage))
          }
        }
        case Failure(ex) => complete(HttpResponse(StatusCodes.InternalServerError, entity = ex.getMessage))
      }
    }
  }

  /**
    * Creates http route to get user by user id
    *
    * @return
    */
  def getUserByUserId: Route = path("user" / "get") {
    get {
      parameters("id") { id =>
        onComplete(userService.getUserByUserId(id)) {
          case Success(userOpt) => userOpt match {
            case Some(user) => complete(user)
            case None => val msg = s"No user found with user id: ${id}"
              complete(HttpResponse(StatusCodes.BadRequest, entity = msg))
          }
          case Failure(ex) => complete(HttpResponse(StatusCodes.InternalServerError, entity = ex.getMessage))
        }
      }
    }
  }

  /**
    * Creates http route to get user by email
    *
    * @return
    */
  def getUserByEmail: Route = path("user" / "get") {
    get {
      parameters("email") { email =>
        onComplete(userService.getUserByEmail(email)) {
          case Success(userOpt) => userOpt match {
            case Some(user) => complete(user)
            case None => val msg = s"No user found with email: ${email}"
              complete(HttpResponse(StatusCodes.BadRequest, entity = msg))
          }
          case Failure(ex) => complete(HttpResponse(StatusCodes.InternalServerError, entity = ex.getMessage))
        }
      }
    }
  }

  /**
    * Creates http route to get list of all users
    *
    * @return
    */
  def getAllUsers: Route = path("user" / "get" / "all") {
    get {
      onComplete(userService.getAllUsers()) {
        case Success(users) => complete(users)
        case Failure(ex) => complete(HttpResponse(StatusCodes.InternalServerError, entity = ex.getMessage))
      }
    }
  }


  /**
    * Creates http route to check whether user exists by given user Id
    *
    * @return
    */
  def isUserIdExists: Route = path("user" / "exists") {
    get {
      parameters("userId") { userId =>
        onComplete(userService.isUserIdExists(userId)) {
          case Success(users) => complete(users.toString)
          case Failure(ex) => complete(HttpResponse(StatusCodes.InternalServerError, entity = ex.getMessage))
        }
      }
    }
  }

val routes = welcomeRoute ~ insertUser ~ getUserByUserId ~ getUserByEmail ~ getAllUsers ~ isUserIdExists
}

This is the basics of how to use CockroachDB with Scala. You can get the above working example from the GitHub repo.

Do you pay to use your database? What if your database paid you? Learn more with RavenDB.

Topics:
database ,cockroach ,scala ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}