Architecture of Falcon, a new chat messaging backend system build on ScalaTanUkkii
The document describes the architecture of Falcon, a new Scala-based backend chat messaging system. The key aspects of the architecture are:
- It uses an event-driven architecture with CQRS and event sourcing principles. Writing and reading are separate with different data models and storage.
- Events are stored in Kafka for durability and consumed asynchronously by downstream systems to update read models and notify legacy systems.
- The architecture is designed for scalability, high performance, resiliency and low cost through principles like auto-sharding and parallel processing in Kafka.
The document discusses ChatWork's motivation for developing a distributed ID generator and describes how it works. The key points are:
- ChatWork migrated from MySQL to Kafka/HBase and needed scalable, sortable IDs without a single point of failure.
- Their distributed ID generator is inspired by Snowflake but uses ZooKeeper for coordination between ID workers instead of a timestamp.
- ID workers register with ZooKeeper and clients discover available workers to distribute load. Workers generate IDs using an actor model implementation for scalability and fault tolerance.
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...TanUkkii
This document discusses how ChatWork migrated from using a native blocking HBase client to the asynchronous non-blocking asynchbase client. It describes problems they faced with the blocking client, such as long running queries blocking threads. It then explains how asynchbase and Akka streams were used to build an asynchronous non-blocking interface. Performance tests showed the asynchronous approach improved throughput by 30% and reduced latency at the 95th percentile from 1000ms to 200ms. Migrating to a non-blocking client made the system more resilient to partial failures.
11. トレイト
トレイトとは?:機能の集合で、実装をもったインターフェースと思ってよい
class Point(_x: Int = 0, _y: Int = 0) {
def x = _x
def y = _y
def +(target: Point) = new Point(x + target.x, y + target.y)
override def toString = s"(${x}, ${y})"
}
trait Landscape extends Point {
override def x = super.y
override def y = super.x
}
trait Retina extends Point {
val ratio = 2
override def x = super.x*ratio
override def y = super.y*ratio
def toNormal = new Point(super.x, super.y)
}
!
class LRPoint extends Landscape with Retina
new Point(3, 5) with Landscape with Retina
//res0: Point with Landscape with Retina = (10, 6)
実装を持っているので、インス
タンス化時に直接ミックスイン
することも可能
クラスは複数のトレイトをミックスインできる
16. パターンマッチ
パターンマッチはパターンによって値と変数を束縛する。
パターンマッチを使えば代入などの副作用やキャスト、nullかどうかなどの判定
といった危険な処理を一切行わずにすむ。
例)翼を含めた動物の足の数を合計する
abstract class Animal {
val legs: Int
}
class Dog extends Animal {
val legs = 4
}
class Bird extends Animal {
val legs = 2
val wings = 2
}
val animals = List(new Dog, new Bird)
//animals: List[Animal] = List(Dog@1e339ce8, Bird@4e8252d5)
animals.map({
case bird: Bird => bird.legs + bird.wings
case animal: Animal => animal.legs
}).reduce(_ + _)
//res0: Int = 8
17. 第一級オブジェクトとしての関数
第一級オブジェクトとは
オブジェクトがもつべきあらゆる能力をもつオブジェクト
•変数に代入することができる
scala> val sum = (x: Int, y: Int) => x + y
sum: (Int, Int) => Int = <function2>
•関数の引数に渡すことができる
scala> List(1,2,3).map(_ * 2)
res0: List[Int] = List(2, 4, 6)
•関数の返り値として返すことができる
scala> def optioned[A, B](f: A => B)
: Option[A] => Option[B] = _ map f
optioned: [A, B](f: A => B)Option[A] => Option[B]
•メンバーをもつ
scala> ((x: Int) => x + 1).compose((x: Int) => x * 2)
res1: Int => Int = <function1>
18. 関数適用ができるオブジェクト
オブジェクトは呼び出すものではないが、Scalaでは引数を適用で
きる
scala> :paste
Entering paste mode (ctrl-D to finish)
class Applicable(val s: String)
!
object Applicable {
def apply(s: String) = new Applicable(s)
}
Exiting paste mode, now interpreting.
defined class Applicable
defined object Applicable
!
scala> Applicable("abc")
res0: Applicable = Applicable@1cb285b
コンパイラはオブジェクトへの引数の適用をapplyメソッド呼び出しに置き換える。
ListやMapに引数を適用できるのは、それらがapplyメソッドを定義しているからだ。
19. 値を返す制御構造
if, for, whileのような制御構造はすべて式であり、値を返す。
val result = if ("string" == new String("string")) “reasonable"
else "unreasonable"
result: String = reasonable
for式はコンプリヘンション形式でフィルターをかけられる。
yieldキーワードでコレクションを作ることができる。
scala> for (i <- 1 until 9 if i % 2 == 0) yield i
res0: scala.collection.immutable.IndexedSeq[Int]
= Vector(2, 4, 6, 8)
20. match式
switchに相当。パターンマッチにより値を抽出し、それを返す。
abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends
Expr
!
val twoPlusZero = BinOp("+", Number(2), Number(0))
!
def simplify(expr: Expr) = {
expr match {
case UnOp("-", UnOp("-", e)) => e
case BinOp("+", e, Number(0)) => e
case BinOp("*", e, Number(1)) => e
case _ => expr
}
}
!
val number = simplify(twoPlusZero)
number: Expr = Number(2.0)
※コップ本に載っている例
24. 暗黙のパラメーター
パラメーターリストが足りないまま関数が呼び出されたとき、足りな
いパラメーターリストがimplicit宣言されていた場合、スコープ中の
implicit宣言された値で関数を完成させる
def maxList[T](elements: List[T])(implicit orderer: T =>
Ordered[T]): T =
elements match {
case List() => throw new IllegalArgumentException("empty
list")
case List(x) => x
case x :: rest => val maxRest = maxList(rest)(orderer)
if (orderer(x) > maxRest) x
else maxRest
}
!
scala> maxList(List(1,2,3,4,5))
res0: Int = 5
※コップ本に載っている例
ScalaライブラリはT=>Ordered[T]の暗黙の定義を提供しているので、
Orderedトレイトを継承していないIntでもmaxListは使える
26. ダックタイピング
ダックタイピングとは?
アヒルのように鳴けばそれはアヒルであるという考えのもと、メンバー
のシグニチャが同じであれば型が違っても互換性のある型とみなすこと
def using[T <: {def close(): Unit}, S](obj: T)(operation: T => S) = {
val result = operation(obj)
obj.close()
result
}
!
import java.io.PrintWriter
!
using(new PrintWriter("date.txt")) { writer =>
writer.println(new java.util.Date)
}
※コップ本に載っている例
ここではPrintWriterを渡しているが、セッションやソケットなど、close
メソッドを持っているオブジェクトなら何でも渡せる
27. 変位指定のチェック
ジェネリックな型には変位という概念がある
変位の種類は3つある。 Sのサブ型がTであるとき
非変:Class[T]とClass[S]にサブ型関係はない
共変:Class[T]はClass[S]のサブ型である
反変:Class[T]はClass[S]のスーパー型である
ここで共変な型Cell[+T]を定義する。しかしコンパイルを通らない。なぜだろうか?
class Cell[+T](init: T) {
private[this] var current = init
def get = current
def set(x: T) { current = x }
}
error: covariant type T occurs in contravariant position in type T of
value x
def set(x: T) { current = x }
^
※コップ本に載っている例
28. 変位指定のチェック
さきほどのコードがコンパイルを通らないのは、以下のよ
うな反例が書けるからである
val c1 = new Cell[String]("abc")
val c2: Cell[Any] = c1
c2.set(1)
val s: String = c1.get
このコードは型に矛盾はない。しかし結果としてString型
の変数にInt型の値を代入することを許している。もしこれ
がコンパイルを通ると、実行時エラーの危険を生む。
Scalaコンパイラは型パラメーターが現れるすべての場所を
チェックし、危険があれば開発者に教えてくれる