Upgrade to Pro — share decks privately, control downloads, hide ads and more …

All in Scala

Avatar for cupper cupper
December 27, 2020

All in Scala

Avatar for cupper

cupper

December 27, 2020
Tweet

More Decks by cupper

Other Decks in Programming

Transcript

  1. ⾃⼰紹介 名前: 川嶋 ⼀寿 職業: プログラマー 拠点: 静岡県静岡市 趣味: 酒、ジョギング、⽔泳(最近再開)

    好きな⾔語: Scala その他活動: 専⾨学校講師、Scalapediaの記事執筆 Twitter: @cupperservice https://github.com/cupperservice/LT-Document/tree/main/20201225
  2. ディレクトリ構成 dev-res // アプリケーション +-- client // フロントエンドのコード +-- server

    // サーバサイドのコード +-- shared // フロントエンドとサーバサイドの共通コード services // Docker +-- appsvr // Application サーバ +-- db // Database サーバ +-- websvr // Web サーバ https://github.com/cupperservice/LT-Document/tree/main/20201225
  3. JSライブラリの呼び出し JSのライブラリも呼び出すことができる。 @js.native trait SpeechSynthesisVoice extends js.Object { val voiceURI:

    String = js.native } @js.native @JSGlobal class SpeechSynthesisUtterance(var text: String) extends js.Object { var voice: SpeechSynthesisVoice = js.native } @js.native @JSGlobal("speechSynthesis") object SpeechSynthesis extends js.Object { def speak(msg: SpeechSynthesisUtterance): js.Any = js.native def getVoices(): js.Array[SpeechSynthesisVoice] = js.native } https://github.com/cupperservice/LT-Document/tree/main/20201225
  4. Airframeとは Scalaで開発するための軽量ライブラリの集まり Designed for Scala and Scala.js RPC Framework MessagePack-based

    Object Serialization REST Services ... https://github.com/cupperservice/LT-Document/tree/main/20201225
  5. ディレクトリ構成 前回と同じような構成 app // アプリケーション +-- api // サーバサイドのAPI インターフェース

    & Value オブジェクト +-- server // サーバサイドのコード +-- ui // フロントエンドのコード services // Docker +-- appsvr // Application サーバ +-- db // Database サーバ +-- websvr // Web サーバ https://github.com/cupperservice/LT-Document/tree/main/20201225
  6. 前回との違い1 クライアント・サーバ間の通信 Airframe RPCを使⽤して実現する 前回: REST API JSONの処理が必要 今回: Airframe

    RPC API IFを定義するだけ どのように通信するかはAirframeまかせ https://github.com/cupperservice/LT-Document/tree/main/20201225
  7. APIで使⽤するValueオブジェクトを定義 APIで使⽤するValueオブジェクトを定義する case class NumOfAllWords(num: Int) case class DictionaryInformation(numOfWords: Int,

    history: List[Word]) case class Word(id: Int, text: String, refCount: Int, lastRefTime: DateTime) case class Example(id: Int, text: String) case class DateTime(time: Long) https://github.com/cupperservice/LT-Document/tree/main/20201225
  8. APIのインターフェースを定義 @RPCアノテーションを付けてAPIのインターフェースを定義する @RPC trait MyDicAPI { def getInformation(): DictionaryInformation def

    createWordIfNotExists(text: String): Word def getExamples(wordId: Int): List[Example] def createExample(wordId: Int, example: Example) def getHistories(max: Int): Seq[Word] } https://github.com/cupperservice/LT-Document/tree/main/20201225
  9. APIの実装 trait MyDicApiImpl extends MyDicAPI { private val service: WordService

    = bind[WordService] override def createWordIfNotExists(text: String): Word = { service.createIfNotExist(text) match { case Right(word) => word case Left(e) => throw e } } ... https://github.com/cupperservice/LT-Document/tree/main/20201225
  10. クライアントからAPIを呼び出す 呼び出し側の実装(ServiceJSClient.MyDicAPI)は、API IFから⾃動⽣成される object MyDictionary { lazy val client =

    new ServiceJSClient() def findWord(text: String): Future[Word] = client.MyDicAPI.createWordIfNotExists(text) def getHistories(): Future[Seq[Word]] = client.MyDicAPI.getHistories(10) } https://github.com/cupperservice/LT-Document/tree/main/20201225
  11. Playframework GET /word cupper.mydic2.controllers.WordController.getInformation POST /word cupper.mydic2.controllers.WordController.createIfNotExist PUT /word/:id cupper.mydic2.controllers.WordController.update(id:Int)

    Airframe val router = Router .add[MyDicApiImpl] val design = newDesign .add(Finagle.server .withRouter(router) .withPort(port) .withName("My Dictionary server") .design) https://github.com/cupperservice/LT-Document/tree/main/20201225
  12. 前回との違い3 サービスの注⼊⽅法 前回: Guice class InfrastructureModule extends AbstractModule { override

    def configure(): Unit = { bind(classOf[cupper.mydic2.models.WordRepo]).to(classOf[cupper.mydic2.dao.WordRepo]) } } @Singleton class WordController @Inject()( cc: ControllerComponents, usecase: cupper.mydic2.models.WordModel) extends AbstractController(cc) { https://github.com/cupperservice/LT-Document/tree/main/20201225
  13. 前回との違い3 サービスの注⼊⽅法 今回: Airframe DI val design = newDesign .bind[WordService].toInstance(new

    WordService(new DicRepositoryImpl())) .add(Finagle.server .withRouter(router) .withPort(port) .withName("My Dictionary server") .design) trait MyDicApiImpl extends MyDicAPI { private val service: WordService = bind[WordService] https://github.com/cupperservice/LT-Document/tree/main/20201225
  14. 前回との違い4 HTMLレンダリング 前回: DOMを直接操作 class EditExampleView(_top: Element) extends Screen(_top) {

    document.getElementById("edit-example-create").asInstanceOf[Button].onclick = (event) => Event.dispatch(Event.ApplyEditExample(Example(exampleId.value.toInt, exampleText.value))) val exampleText = document.getElementById("edit-example-content").asInstanceOf[TextArea] override def show(data: Data): Unit = { val editExample = data.asInstanceOf[EditExampleData] exampleText.value = editExample.example.text } } https://github.com/cupperservice/LT-Document/tree/main/20201225
  15. 前回との違い4 HTMLレンダリング 今回: Airframe RX class EditExampleTab extends Page {

    override def render: RxElement = { div(cls -> "tab_content", examples.map (list.map (e => // textarea の定義 )), button("Apply", onclick -> {e: MouseEvent => { // イベント処理 }}), ) } } https://github.com/cupperservice/LT-Document/tree/main/20201225
  16. 前回との違い5 DBアクセス 前回: JDBC APIを直接使⽤ def _find(word: String, connection: Connection):

    Option[Word] = { val stmt = connection.prepareStatement("select * from word where word=?") stmt.setString(1, word) val rs = stmt.executeQuery() if(rs.next()) { Some(Word( rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getString(4)) ) } else { None } } https://github.com/cupperservice/LT-Document/tree/main/20201225
  17. 前回との違い5 DBアクセス 今回: Slick(FRM)を使⽤ 以下の Word は、Slickによって⾃動⽣成されたコード override def findWords(text:

    String): Future[Seq[v1.Word]] = { withDb(db => { val query = Word.filter(w => w.text === text) for(list <- db.run(query.result)) yield { for(w <- list) yield v1.Word(w.id.toInt, w.text, w.refCount, w.lastRefTime) } }) } https://github.com/cupperservice/LT-Document/tree/main/20201225