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

All in Scala

cupper
December 27, 2020

All in Scala

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