APIs & Microservices

with Scala and Play

Toby Sullivan

Lead Engineer at Hootsuite

@tobyjsullivan

What this talk is about

  • Microservices can be easy and fast
  • Demonstrate one way to build them
  • Stop wedging features into monolithic apps

What this talk is not about

  • Convincing you Scala & Play are the best choice
    • Many other options
      • Scala & Spray
      • Python & Flask
  • Best practices
  • Selling you on service oriented architectures
    • Only for a lack of time

What most apps look like

Inherint problems with a monolithic design

  • Difficult and inefficient to scale
  • Code gets harder to reason about
  • All-or-nothing deploys

New feature? Consider a microservice

Benefits of a microservice

  • Do only one thing and do it well
  • Strongly encourages decoupling
  • Low-friction component re-use between apps

Our scenario

  • An app for people to find fitness studios
  • Going to add feature to “favourite” a studio
  • Build the new feature as a microservice

What We'll Cover

  • RESTful HTTP API
  • JSON
  • MySQL with Anorm
  • Caching
  • Akka Actors
  • Web Services

Chapter 0.0

A New Service

Initialize a skeleton service in one command

activator new favourites-service play-scala

You can run it immediately

activator run

DEMO: App skeleton

Find the code on Github

Chapter 1.0

Designing the API

The API endpoints

Our new service will support these operations

# Favourite a studio
POST    /users/:userId/favouriteStudios/:studioId

# Unfavourite a studio
DELETE  /users/:userId/favouriteStudios/:studioId

# Get a list (index) of all studios a user has favourited
GET     /users/:userId/favouriteStudios

# Check if a studio has been favourited
# Responds 200 if yes, otherwise 404
GET     /users/:userId/favouriteStudios/:studioId

Define a controller and action


/* app/controllers/FavouriteStudios.scala */

package controllers

import play.api.mvc.{Controller, Action}

object FavouriteStudios extends Controller {
  def add(userId: Int, studioId: Int) = Action {
    // TODO: Add favourite

    Ok(???)
  }
}
          

Add a route

# conf/routes

POST    /users/:userId/favouriteStudios/:studioId       
        controllers.FavouriteStudios.add(userId: Int, studioId: Int)
          

Chapter 1.1

JSON

Define a Write Combinator


/* app/models/FavouriteStudio.scala */

import play.api.libs.json._
import play.api.libs.functional.syntax._

object FavouriteStudio {
  implicit val favouriteStudioWrites: Writes[FavouriteStudio] = (
    (JsPath \ "userId").write[Int] and
    (JsPath \ "studioId").write[Int]
  )(unlift(FavouriteStudio.unapply))
}
          

DEMO: CuRL our new endpoint

Find the code on Github

Chapter 2.0

MySQL with Anorm

Easy configuration


# conf/application.conf

db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:mysql://10.0.0.3:3306/favourites-svc"
db.default.user="favourites-svc"
db.default.password="s0meP@ssW0rd"
          

Define a table in MySQL


CREATE TABLE `favouriteStudio` (
    `userId` INT(7) NOT NULL,
    `studioId` INT(7) NOT NULL,
    PRIMARY KEY `idx_user_studio` (`userId`, `studioId`),
    INDEX `idx_user` (`userId`)
);
          

Define a Data Access Object

/* app/models/dao/FavouriteStudioDAO.scala */

object FavouriteStudioDAO {
  def create(favourite: FavouriteStudio) =
    DB.withConnection { implicit c =>
      SQL(
        """
          | INSERT IGNORE INTO `favouriteStudio` (`userId`, `studioId`)
          | VALUES
          |   ({userId}, {studioId});
        """.stripMargin).on(
          "userId" -> favourite.userId,
          "studioId" -> favourite.studioId
        ).executeInsert()
    }
}

DEMO: CuRL data to the DB

Find the code on Github

DEMO: Repeat implementation for other CRUD endpoints

Find the code on Github

Chapter 3.0

Caching

Improve response times in a single line

Add Caching to an Action

/* app/controllers/FavouriteStudios.scala */

def find(userId: Int, studioId: Int) = 
  Cached("find_" + userId + "_" + studioId) {
    Action {
      val oFavourite = FavouriteStudio.find(userId, studioId)

      oFavourite match {
        case None => NotFound(Json.obj("error" -> "NOT_FOUND"))
        case Some(favourite) => Ok(Json.obj("result" -> favourite))
      }
    }
  }

DEMO: See the performance difference

Find the code on Github

Chapter 4.0

Actors

  • Allow us to perform tasks asyncronously and reliably
  • In this case, send off push notifications to friends

Define an Actor

/* app/actors/NotificationActor.scala */

object NotificationActor {
  def props: Props = Props(new NotificationActor)
}

class NotificationActor extends Actor {
  def receive = {
    case favourite: FavouriteStudio =>
      notifyFriends(favourite)
  }

  private def notifyFriends(favourite: FavouriteStudio): Unit = {
    // TODO: Lookup list of friends and send a push notification
  }
}

Send a Message to the Actor

/* app/models/FavouriteStudio.scala */

def addFavourite(userId: Int, studioId: Int): FavouriteStudio = {
  val favourite = FavouriteStudio(userId, studioId)

  FavouriteStudioDAO.create(favourite)

  // Send a message to the notification actor describing the added favourite
  notificationActor ! favourite

  favourite
}

Chapter 5.0

Web Services

  • Easily consume HTTP services like other microservices
  • In this case, read friend data from our monolithic app

Read from a Web Service

/* app/models/dao/FriendDAO.scala */

def index(userId: Int): Future[List[Friend]] = {
  val holder: WSRequestHolder = WS.url(friendsServiceUrl)
    .withQueryString("userId" -> userId.toString)

  // Execute the web service request and get a future of a response
  val fResponse = holder.get()

  fResponse.map { response =>
    // The response will be JSON so parse out the list of friends' IDs
    val friendIds: List[Int] = (response.json \ "result").as[List[Int]]

    // Map the list of IDs to a list of friends
    friendIds.map(Friend.apply)
  }
}

DEMO: Tying it all together.

Find the code on Github

What We've Covered

  • RESTful HTTP API
  • JSON
  • MySQL with Anorm
  • Caching
  • Akka Actors
  • Web Services

Further reading:

Microservices by Martin Fowler
(martinfowler.com)

Building a REST API using Play by Saba El-Hilo
(code.hootsuite.com)

Thank You

Questions? Suggestions?

 

If you found this interesting, we're hiring.

tobyjsullivan.github.io/applicative-slides