from polling to real time: scala, akka, and websockets from scratch

67
Letgo chat From polling to real time Scala, Akka, and WebSockets from scratch @SergiGP @GVico46 @JavierCane #scbcn16 - Software Craftsmanship Barcelona 2016

Upload: sergi-gonzalez-perez

Post on 05-Apr-2017

660 views

Category:

Engineering


2 download

TRANSCRIPT

Page 1: From polling to real time: Scala, Akka, and Websockets from scratch

Letgo chat From polling to real timeScala, Akka, and WebSockets from scratch

@SergiGP @GVico46

@JavierCane#scbcn16 - Software Craftsmanship Barcelona 2016

Page 2: From polling to real time: Scala, Akka, and Websockets from scratch
Page 3: From polling to real time: Scala, Akka, and Websockets from scratch

@JavierCane@SergiGP

Welcome!

@GVico46

Page 4: From polling to real time: Scala, Akka, and Websockets from scratch
Page 5: From polling to real time: Scala, Akka, and Websockets from scratch

Contents

Context(not Bounded)

Legacy

Getting started

Pain Points

From PHP to Scala

Page 6: From polling to real time: Scala, Akka, and Websockets from scratch

1. Context (not Bounded)

Page 7: From polling to real time: Scala, Akka, and Websockets from scratch
Page 8: From polling to real time: Scala, Akka, and Websockets from scratch
Page 9: From polling to real time: Scala, Akka, and Websockets from scratch
Page 10: From polling to real time: Scala, Akka, and Websockets from scratch
Page 11: From polling to real time: Scala, Akka, and Websockets from scratch

App downloads

Messages sent monthly growth

Messages sent every day

30M

20 - 40%

~4M

Page 12: From polling to real time: Scala, Akka, and Websockets from scratch

Context (not Bounded) Where do we come

● Mobile first

◕ Internal REST API ● Startup with less than 2 years

◕ Externalize services (Parse, Kahuna…) ● Funding: $200M

◕ Ads in TV in USA and Turkey

Page 13: From polling to real time: Scala, Akka, and Websockets from scratch

2. Legacy

Page 14: From polling to real time: Scala, Akka, and Websockets from scratch

Legacy

● PHP ● No test ● New Features

Page 15: From polling to real time: Scala, Akka, and Websockets from scratch

Legacy REST API in PHP

Do I have new messages? No

And now?

And now?

And now?

And now?

No

No!!

NO!!

😑 🔫 💣

Page 16: From polling to real time: Scala, Akka, and Websockets from scratch

Legacy No test

● Rebuild a system without tests => 🦄💩💣💀

● Coupled system => Acceptance tests

◕ Learning what the system does

◕ Find existing weird behaviors

Page 17: From polling to real time: Scala, Akka, and Websockets from scratch

Background: Given there are test users: | user_object_id | user_name | user_token | | 19fd3160-8643-11e6-ae22-56b6b6499611 | seller | sellerToken | | 120291b2-8643-11e6-ae22-56b6b6499611 | buyer | buyerToken | And user "seller" has a product with: | id | objectId | | 120291b2-8643-11e6-ae22-56b6b6499611 | SuperProductId | Scenario: A user can get messages from another user associated to product Given user "seller" has a conversation related to product "SuperProductId" with user "buyer" When user "seller" asks for messages related to product "SuperProductId" from user "buyer" Then the response status code should be 200 And the response should be in JSON And the JSON should be valid according to the schema "messages.schema"

Acceptance test with Behat

Page 18: From polling to real time: Scala, Akka, and Websockets from scratch

Legacy Taking advantage of backwards compatibility

Leaving The Monolith thanks to #EventSourcing @ #scpna

Page 19: From polling to real time: Scala, Akka, and Websockets from scratch

Legacy New Features

● Product always want more features ● Negotiation:

◕ Archive conversations

◕ Mute interlocutor

◕ Stickers

Page 20: From polling to real time: Scala, Akka, and Websockets from scratch

3. Getting started

Page 21: From polling to real time: Scala, Akka, and Websockets from scratch

Getting started

● Why and how to switch to Scala ● Scala and Akka crash course ● Takeaways

Page 22: From polling to real time: Scala, Akka, and Websockets from scratch

Why and how to switch to Scala

Page 23: From polling to real time: Scala, Akka, and Websockets from scratch

We want a WhatsApp inside

Letgo

Page 24: From polling to real time: Scala, Akka, and Websockets from scratch

LOL

I’ve payed $22 Billion for WhatsApp

Page 25: From polling to real time: Scala, Akka, and Websockets from scratch

Getting started Why and how to switch to Scala

● Realtime (WebSockets) ● Akka ● Scale!

Why How

● Learning a lot ● External consultancy ● Akka :) ● Backwards Compatible

Page 26: From polling to real time: Scala, Akka, and Websockets from scratch

Scala quick start

Page 27: From polling to real time: Scala, Akka, and Websockets from scratch

Getting started Scala quick start

● Case classes ● Functional ● Optionals ● Futures ● OOP

Page 28: From polling to real time: Scala, Akka, and Websockets from scratch

class User { private $id; private $name; public function __construct(Uuid $id, string $name) { $this!→id = $id; $this!→name = $name; } public function id() : Uuid { return $this!→id; } public function name() : string

Case Class

Page 29: From polling to real time: Scala, Akka, and Websockets from scratch

{ return $this!→id; } public function name() : string { return $this!→name; } public function setId(Uuid $id) : self { return new static($id, $this!→name); } public function setName(string $name) : self { return new static($this!→id, $name); }}

Case Class

Page 30: From polling to real time: Scala, Akka, and Websockets from scratch

rafa.name = "Santi"val santi = rafa.copy(name = "Santi")println(santi.name) #$ Santi

val rafa = User(UUID.randomUUID(), "Rafa")println(rafa.name) #$ Rafa

case class User(id: UUID, name: String)

Case classes

Does not compile

Usage

Immutability

Page 31: From polling to real time: Scala, Akka, and Websockets from scratch

val users = List( User(UUID.randomUUID(), "Rafa"), User(UUID.randomUUID(), "Santi"), User(UUID.randomUUID(), "Jaime"), User(UUID.randomUUID(), "Diyan"))

Functional

Mutable state

val names = users.map(user %& user.name)val names = users.map(_.name)

List[String] names = new ArrayList();for (User user: users) { names.add(user.name)}

Procedural

Page 32: From polling to real time: Scala, Akka, and Websockets from scratch

Option

Option[A]

Some(x: A) None

Page 33: From polling to real time: Scala, Akka, and Websockets from scratch

def searchUser(id: UUID): Option[User] = { #$ …search user in database (blocking) Some(rafa)}

Option

searchUser(userId) match { case Some(user) %& #$ do stuff case None %& #$ user not found}

Usage (pattern matching)

Page 34: From polling to real time: Scala, Akka, and Websockets from scratch

Option usage (functional)

searchUser(userId) match { case Some(user) %& #$ do stuff case None %& #$ user not found}

searchUser(userId).map { userFound %& #$ do stuff}

Page 35: From polling to real time: Scala, Akka, and Websockets from scratch

Futures

def searchUser(id: UUID): Future[Option[User]] = { Future { Thread.sleep(1000) Some(rafa) } }

Page 36: From polling to real time: Scala, Akka, and Websockets from scratch

Futures usage

searchUser(userId).onComplete { case Success(Some(user)) %& #$ do stuff case Success(None) %& #$ user not found case Failure(exception) %& #$ future has crashed}searchUser(userId).map { case Some(user) %& #$ do stuff case None %& #$ user not found}

searchUser(userId).map(_.map(_.name))

Page 37: From polling to real time: Scala, Akka, and Websockets from scratch

trait UserRepository { def search(id: UUID): Future[Option[User]]}trait ConsoleLogger { def warning(message: String) = { println(message) }}

OOP - DIP

Page 38: From polling to real time: Scala, Akka, and Websockets from scratch

OOP - DIP

class MysqlUserRepository extends UserRepository with ConsoleLogger { def search(id: UUID): Future[Option[User]] = { #$ implementation warning("user not found") Future(Some(rafa)) }}

Page 39: From polling to real time: Scala, Akka, and Websockets from scratch

OOP - Companion object

object UserId { def random: UserId = { UserId(UUID.randomUUID()) }}case class UserId(id: UUID)

val userId = UserId.randomprintln(userId.id) case class User(id: UserId, name: String)

Usage

Page 40: From polling to real time: Scala, Akka, and Websockets from scratch

Akka (actor model)

Page 41: From polling to real time: Scala, Akka, and Websockets from scratch

Scala quick start Akka (actor model)

● Concept ● Introductory examples ● Chat actors architecture

Page 42: From polling to real time: Scala, Akka, and Websockets from scratch

Scala quick start Akka (actor model) - Concept

● Mailbox (1 each time) ● receive to handle incoming messages ● ActorRef ● Tell or ask methods to interact with the ActorRef ● Location transparency

Page 43: From polling to real time: Scala, Akka, and Websockets from scratch

final class ConnectionActor extends Actor { }

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

Building our first actor

Instantiationval connection: ActorRef = context.actorOf(ConnectionActor.props)

Page 44: From polling to real time: Scala, Akka, and Websockets from scratch

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

final class ConnectionActor extends Actor {

override def receive: Receive = { case PingQuery %& } }

Building our first actor

Instantiationval connection: ActorRef = context.actorOf(ConnectionActor.props)

Page 45: From polling to real time: Scala, Akka, and Websockets from scratch

final class ConnectionActor(webSocket: ActorRef) extends Actor { override def receive: Receive = { case PingQuery %& webSocket ! PongResponse } }

Tell (Fire & forget)

object ConnectionActor { def props(webSocket: ActorRef): Props = Props(new ConnectionActor(webSocket)) }

Building our first actor

Instantiationval connection: ActorRef = context.actorOf(ConnectionActor.props(webSocket))

Page 46: From polling to real time: Scala, Akka, and Websockets from scratch

case class ConnectionActorState( lastRequestSentAt: Option[DateTime]) { def requestSent: ConnectionActorState = copy(lastRequestSentAt = Some(DateTime.now))}

Dealing with state

Page 47: From polling to real time: Scala, Akka, and Websockets from scratch

case class ConnectionActorState( lastRequestSentAt: Option[DateTime]) { def requestSent: ConnectionActorState = copy(lastRequestSentAt = Some(DateTime.now))}

final class ConnectionActor(webSocket: ActorRef) extends Actor { var state = ConnectionActorState(lastRequestSentAt = None) override def receive: Receive = { case PingQuery(requestId) %& state = state.requestSent webSocket.actorRef ! PongResponse }

Dealing with state

State model

Akka: 1 message at a time (no race conditions)

Page 48: From polling to real time: Scala, Akka, and Websockets from scratch

final class ConnectionActor(webSocket: ActorRef) extends Actor { var state = ConnectionActorState(lastRequestSentAt = None) override def preStart(): Unit = { context.system.scheduler.schedule( initialDelay = 1.minute, interval = 1.minute, receiver = self, message = CheckWebSocketTimeout ) } override def receive: Receive = { case PingQuery(requestId) %& state = state.requestSent

Lifecycle

Page 49: From polling to real time: Scala, Akka, and Websockets from scratch

override def preStart(): Unit = { context.system.scheduler.schedule( initialDelay = 1.minute, interval = 1.minute, receiver = self, message = CheckWebSocketTimeout ) } override def receive: Receive = { case PingQuery(requestId) %& state = state.requestSent webSocket ! PongResponse() case CheckWebSocketTimeout %& if (state.hasBeenIdleFor(5.minutes)) { self ! PoisonPill } }}

Lifecycle

Page 50: From polling to real time: Scala, Akka, and Websockets from scratch

override def receive: Receive = { case PingQuery %& Future { Thread.sleep(1000) sender() ! PongResponse }}

Akka and Futures - SHIT HAPPENS

sender() could have changed

Page 51: From polling to real time: Scala, Akka, and Websockets from scratch

Be careful dealing with futures - sender()

override def receive: Receive = { case PingQuery %& Future { Thread.sleep(1000) PongResponse }.pipeTo(sender())}

sender() outside Future

Same happens with self

Page 52: From polling to real time: Scala, Akka, and Websockets from scratch

Chat actors architecture

TalkerS

ConversationSJ

TalkerJ

ConnectionS1 ConnectionJ1

Page 53: From polling to real time: Scala, Akka, and Websockets from scratch

Maintains consistency between 2 talkers : 1 conversation

Kill connections if shit happens

Chat actors architecture

TalkerS

ConversationSJ

ConnectionS1CS2CS3

Connection Supervisor

Talker Provider

Conversation Provider

Maintains consistency between N connections : 1 talker

“Singleton” actor

Non “singleton” actor

Page 54: From polling to real time: Scala, Akka, and Websockets from scratch

Takeaways

Page 56: From polling to real time: Scala, Akka, and Websockets from scratch

4. Pain Points

Page 57: From polling to real time: Scala, Akka, and Websockets from scratch

Pain Points

● MaxScale ● Slick ● Deploy ● Dependency Injection ● Sync between chats

Page 58: From polling to real time: Scala, Akka, and Websockets from scratch

Chat protocol

SERVER TO CLIENT

Events

Commands

Queries

ACK

RESPONSE

Events

CLIENT TO SERVER

Page 59: From polling to real time: Scala, Akka, and Websockets from scratch

SERVER TO CLIENTCommands

typing_started

typing_stopped

interlocutor_typing_started

interlocutor_typing_stopped interlocutor_message_sent

interlocutor_reception_confirmed

interlocutor_read_confirmed

Events

Queries

Events

fetch_conversations

fetch_conversation_details

fetch_messages fetch_messages_newer_than_id

fetch_messages_older_than_id

confirm_reception

confirm_read

archive_conversations

unarchive_conversations

CLIENT TO SERVER

Chat protocol

authenticate

create_conversation

send_message

Page 60: From polling to real time: Scala, Akka, and Websockets from scratch

DB initial import

Page 61: From polling to real time: Scala, Akka, and Websockets from scratch

DB initial import

Page 62: From polling to real time: Scala, Akka, and Websockets from scratch

Legacy events

workerN

Legacy events

worker…

Legacy events

worker2

Scaling domain events workers

Legacy events

worker1

Auto scaling supervisor actor

SQS

Page 63: From polling to real time: Scala, Akka, and Websockets from scratch

Scaling domain events workers

Legacy events

worker1

Legacy events

worker2

Legacy events

worker…

Legacy events

workerN

Auto scaling supervisor actor

SQS

Page 64: From polling to real time: Scala, Akka, and Websockets from scratch

5. From PHP to Scala

Page 65: From polling to real time: Scala, Akka, and Websockets from scratch

From PHP to Scala

● Language community ● Composer vs SBT

◕ Semantic Versioning (scalaz, play…) ● Developer eXperience

◕ Not descriptive errors

◕ Scala and IntelliJ ● Learning Curve ● Loving and hating the compiler ● Another set of problems

Page 66: From polling to real time: Scala, Akka, and Websockets from scratch

Questions?

Thanks!Contact

@JavierCane@SergiGP@GVico46

Page 67: From polling to real time: Scala, Akka, and Websockets from scratch

Credits

● Presentation base template by SlidesCarnival ● Graphics generated using draw.io