Getting Started Cockroach with Scala: An Introduction

Table of contents
Reading Time: 2 minutes

Today we are going to discuss that how can we use the Scala with the Cockroach DB? As we all know that Cockroach DB is a distributed SQL database built on top of a transactional and consistent key-value store and now we are going to use it with the Scala. But before starting the journey, To those who have caught the train late,😉 this is what has happened till now:

  1. An Introduction to CockroachDB !!

Now before starting the code please setup Cockroach DB on your local environment.

We have to follow these steps for setting up CockroachDB in your local environment.

  • We can download the Cockroach DB from here and follow the instruction which mentions there.
  • Now 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

Now your Cockroach DB is UP and in running conditions. Please check the localhost:8080 to see the Cockroach UI.

  • Now create the user:
cockroach user set maxroach --insecure
  • After creating the user, create Database and set Privilege
cockroach sql --insecure -e 'CREATE DATABASE test'

cockroach sql --insecure -e 'GRANT ALL ON DATABASE test TO maxroach'
  • Now 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 on port 8080.

We will talk step by step to develop this Rest API after which you will able to run this API on your server and start hitting Rest end points.

When we start creating a project we have to use 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 DB Component with Postgres and Slick for making the connection with Database:



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


trait PostgresDbComponent extends DBComponent {
val driver = PostgresProfile
import driver.api.Database
val db: Database = DBConnection.connectionPool
}
object DBConnection {
val connectionPool = Database.forConfig("db")
}



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


import slick.jdbc.JdbcProfile
trait DBComponent {
val driver: JdbcProfile
import driver.api._
val db: Database
}
  • Create the mapping for binding the data with the case class:



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


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  User Component which will interact with Cockroach DB for performing the DB operations:



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


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 which will work as a bridge between the Routes API and User Component:



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


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)
}
  • Now finally create routes:



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


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
}
view raw

UserApi.scala

hosted with ❤ by GitHub

So this is the basic idea that how we can use Cockroach DB with scala.
You can get the above working example from the GitHub repo, checkout: GitHub
If You have any questions you can contact me here or on Twitter: @anuragknoldus


knoldus-advt-sticker


Written by 

Anurag is the Sr. Software Consultant @ Knoldus Software LLP. In his 3 years of experience, he has become the developer with proven experience in architecting and developing web applications.

1 thought on “Getting Started Cockroach with Scala: An Introduction3 min read

Comments are closed.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading