Save 37% off PRO during our Black Friday Sale! »

Pre ScalaMatsuri 2020

Pre ScalaMatsuri 2020

8a820699dff43e6b707a488b592e7a21?s=128

Akihiro Okuno

August 30, 2020
Tweet

Transcript

  1. Client-Server architecture in Scala.js Pre-ScalaMatsuri 2020/08/30 @choplin / Akihiro Okuno

  2. About Me • Akihiro Okuno • Twitter, Github: @choplin •

    Database Enthusiast • VLDB2020 Tokyo will start tomorrow! • CTO at Splink Inc. • Medical AI startup company • We’re hiring!
  3. Scala.js Introduction • Scala to JS Compiler + Ecosystem •

    Brief History • First release in Nov 2013 (0.1.0 • Production-ready in Feb 2015 (0.6.0) • Reached 1.0.0! in Feb 2020 • Growing Ecosystem • Build tools • UI • Client-server communication • Functional programming • etc. addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.1") plugins.sbt lazy val root = (project in file(".")) .settings( scalaVersion := "2.13.3", name := "scalajs-tutorial", libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "1.0.0", scalaJSUseMainModuleInitializer := true ) .enablePlugins(ScalaJSPlugin) build.sbt import org.scalajs.dom.document object Hello { def main(args: Array[String]) = { val e = document.getElementById("app") e.textContent = "Hello Scalajs!" } } Main.scala $ sbt fastOptJS build => Yay!
  4. Architecture component • Scala JS architecture component • User Interface

    • Logic • Communication • Choices available to each component Client User Interface Logic Communication Client Communication Endpoint Whatever you need Server
  5. User Interface • User Interface • Manages intereactions with users,

    i.e. DOM rendering UI states, event flows etc. • Properties • Safe and clean UI logics • Requires sbt as a main build system • Main component on the client side! • Choices • Dom Builder: scalajs-dom, scalatags • RX/FRP: Binding.scala, OutWatch, airframe-rx- html • React Wrapper: scalajs-react, slinky • Flux: Diode Client User Interface Server
  6. Logic • Logic • All other stuffs to build an

    application • Properties • Less intrusive • Self-contained in many cases • Best starting point for Scala.js Client Logic Server
  7. Communication Client • Communication Client • Manages sending and receiving

    information between servers via HTTP • Properties • Interface matters! • Explicitly defined interface helps us a lot • Strictly-typed interface with Scala.js • Isomorphic architecture • Choices • RPC approach • IDL approach • Endpoint approach • Vanila REST Client Communication Client Server
  8. Communication Client – RPC approach • RPC approach • Define

    interfaces in internal DSL (e.g. Traits and annotations), and generate client/server codes automatically • Pros • Straightforward and intuitive for Scalaist • Less efforts • Cons • Implicit HTTP request/response • Not Human-friendly • Hard to incorporate HTTP ecosystem (monitoring, load-balancing, authz/authn, etc.) • Example • autowire, OutWatch, airframe-rpc, etc. case class Person(id: Int, name: String) @RPC trait MyService { def hello(person: Person): String } class MyServiceImpl extends MyService { override def hello(person: Person): String = s"Hello ${person.name} (id=${person.id})!" } val client = new ServiceSyncClient(Http.client.newSyncClient( "localhost:8080")) client.myService.hello(Person(id=1, name="leo")) Interface Server Client
  9. Communication Client – IDL approach • IDL approach • Define

    interfaces in external IDL, and compile them with the provided tools • Pros • High interoperability • Existing community and ecosystem • Cons • Weaker type safety • No types, or at least, conversions are required • Sometimes Scala support is not matured • Need to learn IDL • Examples • OpenAPI, GraphQL, gRPC, ScalaPB, mu-scala, etc syntax = "proto3"; package com.example.protos; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } .proto val channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).buil d val blockingStub = GreeterGrpc.blockingStub(channel) val request = HelloRequest(name = "World") val reply: HelloReply = blockingStub.sayHello(request) private class GreeterImpl extends GreeterGrpc.Greeter { override def sayHello(req: HelloRequest) = { Future.successful(HelloReply(message = "Hello " + req.name)) } } Server Client
  10. Communication Client – Endpoint approach • Endpoint approach • Define

    REST endpoints as values in Scala and retrieve client/server automatically. • Pros • No need to learn other language • Strictly typed in Scala • Definition Programmable • Interoperable with other languages • You can exploit standard HTTP REST • Cons • REST design must be considered • Some boilerplates is required (boring, but not complex) • Understanding of the library design is necessary • Examples • tapir, endpoints4s • My recommendation case class Person(id: Int, name: String) trait GreeterEndpoints extends algebra.Endpoints with algebra.JsonEntitiesFromSchemas with generic.JsonSchemas { val sayHello: Endpoint[Person, Counter] = endpoint(get(path / "hello"), ok(jsonResponse[String])) implicit lazy val personSchema: JsonSchema[Person] = genericJsonSchema } Endpoint object GreeterClient extends GreeterEndpoints with endpoints4s.xhr.thenable.Endpoints with endpoints4s.xhr.JsonEntitiesFromSchemas val helloResult: js.Thenable[String] = GreeterClient.sayHello(Person(1, "foo")) class GreetRoutes[F[_]: Sync] extends endpoints4s.http4s.server.Endpoints[F] with endpoints4s.http4s.server.JsonEntitiesFromSchemas with GreetEndpoints { val routes: HttpRoutes[F] = HttpRoutes.of( sayHello.implementedBy(person => s"Hello ${person.name} (id=${person.id})!") ) } Server Client
  11. Example 1: Scala.js in Logic • Mainly JS, Scala.js only

    as Logic • Pros • Less intrusive • Self-contained in many cases • Best starting point for Scala.js • Cons • Applicability is limited • Logic component tends to be a small part of whole client codes. • Most of client codes are still in JS/AltJS • Understanding of Scala.js’s JS interoperability is necessary Client Server
  12. Example 2: All Scala • Scala.js for all components •

    + a bit of JS optionally • Pros • Complete Scala experience! • ADT, pattern match, FP, etc. • Simple build system • Complete within sbt • sbt-scalajs, sbt-crosscompile, sbt-scalajs-bundler etc. • Cons • Hard to adopt the existing frontend ecosystem • There is no DefinitelyTypes for Scala.js! • Writing facades is always required (complex and boring) • (IMO) Psychological barrier to adopt JS • Requires determination! Client Server
  13. Example 3: Scala.js in Communication • Mainly JS, Scala.js only

    as Communication Client • Pros • Explicit and strictly-typed client-server interface • Easily exploitable the existing frontend ecosystem • High Interoperability (not the case for RPC) • Cons • Most of client codes are still in JS/AltJS • Build definition tends to be complex • Both Sbt and JS bundler must be combined • Wrappers of Scala.js is sometimes required • boring, but not complex • Wrappers may be auto-generated • My recommendation Client Server
  14. Summary • Scala.js is production-ready • You should consider how

    to incorporate Scala.js in your product • User Interface • Logic • Communication • Example architecture • Scala.js in Logic • All Scala • Scala.js in Communication • Enjoy Scala.js! Client User Interface Logic Communication Client Communication Endpoint Whatever you need Server