Lagom with Scalaやってく!【その2:最小サービスの作成】


Gakuです。
Lagom with Scalaやってく!【その1:環境構築】
前回までで、環境構築は終わったので、今回は新しくサービスを作成します。
提供されているHello Worldはいろいろなことが記載されており、理解が難しかったので、さらにコードを削ぎ落とし、最低限動くサービス(GetメソッドでStringを取得するだけのもの)を作ります。

今回作成したコード

こちらにあります。
https://github.com/gaku3601/study-lagom/tree/step2

apiとimplのフォルダを作成する

PJルートにmy-study-lagom-apiとmy-study-lagom-implフォルダを作成します。この状態だと、まだmoduleとして認識されていない状態ですね。

モジュールの追加

study-lagom/build.sbtを以下のように修正します。

study-lagom/build.sbt
organization in ThisBuild := "com.example"
version in ThisBuild := "1.0-SNAPSHOT"

// the Scala version that will be used for cross-compiled libraries
scalaVersion in ThisBuild := "2.13.0"

val macwire = "com.softwaremill.macwire" %% "macros" % "2.3.3" % "provided"
val scalaTest = "org.scalatest" %% "scalatest" % "3.1.1" % Test

// 追記
lazy val `study-lagom` = (project in file("."))
  .aggregate(`my-study-lagom-api`, `my-study-lagom-impl`, `study-lagom-api`, `study-lagom-impl`, `study-lagom-stream-api`, `study-lagom-stream-impl`)

// 追加
lazy val `my-study-lagom-api` = (project in file("my-study-lagom-api"))
  .settings(
    libraryDependencies ++= Seq(
      lagomScaladslApi
    )
  )

// 追加
lazy val `my-study-lagom-impl` = (project in file("my-study-lagom-impl"))
  .enablePlugins(LagomScala)
  .settings(
    libraryDependencies ++= Seq(
      macwire,
    )
  )
  .dependsOn(`my-study-lagom-api`)

lazy val `study-lagom-api` = (project in file("study-lagom-api"))
  .settings(
    libraryDependencies ++= Seq(
      lagomScaladslApi
    )
  )

lazy val `study-lagom-impl` = (project in file("study-lagom-impl"))
  .enablePlugins(LagomScala)
  .settings(
    libraryDependencies ++= Seq(
      lagomScaladslPersistenceCassandra,
      lagomScaladslKafkaBroker,
      lagomScaladslTestKit,
      macwire,
      scalaTest
    )
  )
  .settings(lagomForkedTestSettings)
  .dependsOn(`study-lagom-api`)

lazy val `study-lagom-stream-api` = (project in file("study-lagom-stream-api"))
  .settings(
    libraryDependencies ++= Seq(
      lagomScaladslApi
    )
  )

lazy val `study-lagom-stream-impl` = (project in file("study-lagom-stream-impl"))
  .enablePlugins(LagomScala)
  .settings(
    libraryDependencies ++= Seq(
      lagomScaladslTestKit,
      macwire,
      scalaTest
    )
  )
  .dependsOn(`study-lagom-stream-api`, `study-lagom-api`)

これでモジュールとして認識されるはずです。

実装

とりあえず、理解している部分にはコメントを記載しました。理解できていないところはふわっとしたコメントを記載していますが、スルーいただければ幸いです。(自分の覚書用で。理解できたら更新します。

apiの実装

以下のように階層を作りサービスファイルを作成します。

で、こんな感じで実装。

MyStudyLagomService.scala
package com.example.mystudylagom.api

import akka.NotUsed
import com.lightbend.lagom.scaladsl.api.{Descriptor, Service, ServiceCall}

trait MyStudyLagomService extends Service {

  /**
    * Example: curl http://localhost:9000/api/hello2/Alice
    * id: stringには/api/hello2/:idのidが入ってくる
    * ServiceCall[NotUsed, String]のNotUsed部分にはrequest bodyが、Stringはresponseの肩
    *  今回、request bodyはNotUsedで使わないことを宣言している→つまり取得処理
    *  lagomはServiceCall[NotUsed, String]自動的にGetメソッドと判断する
    */
  def hello(id: String): ServiceCall[NotUsed, String]

  override final def descriptor: Descriptor = {
    import Service._
    named("my-study-lagom")
      .withCalls(
        pathCall("/api/hello2/:id", hello _), // APIルーティングを定義
      )
      .withAutoAcl(true) // これはつけとかないと動かない。
  }
}

ServiceImplの作成

階層はこんな感じ。

で、MyStudyLagomServiceImpl.scala

MyStudyLagomServiceImpl.scala
package com.example.mystudylagom.impl

import akka.NotUsed
import com.example.mystudylagom.api.MyStudyLagomService
import com.lightbend.lagom.scaladsl.api.ServiceCall

import scala.concurrent.Future

class MyStudyLagomServiceImpl() extends MyStudyLagomService {
  // apiの実装の中身。「_ =>」の_にはpostの場合、request bodyが入ってくるみたい。
  override def hello(id: String): ServiceCall[NotUsed, String] = ServiceCall { _ =>
    // なんでもかんでもFutureで返せばOKっぽい?
    Future.successful("test")
  }
}

これで起動すれば動きそうですが、エラーが出ます。ちらっと調べた感じDIをするためのLoaderが必要とのこと。
こんな感じで実装

MyStudyLagomLoader.scala
package com.example.mystudylagom.impl

import com.example.mystudylagom.api.MyStudyLagomService
import com.lightbend.lagom.scaladsl.api.ServiceLocator
import com.lightbend.lagom.scaladsl.api.ServiceLocator.NoServiceLocator
import com.lightbend.lagom.scaladsl.server._
import com.lightbend.lagom.scaladsl.devmode.LagomDevModeComponents
import play.api.libs.ws.ahc.AhcWSComponents
import com.softwaremill.macwire._

/*
 * これはDIの仕組みを行うために必要?
 * このfileを作らないと動かない
 * とりあえず、いらないものを削ぎ落として、最低限動くようにしたもの
 * akkaとかcassandraの読み込みもここで行うようなので、また別の機会でほそぼそ試す。
 */
class MyStudyLagomLoader extends LagomApplicationLoader {

  // loadとloadDevModeで、おそらく、本番・開発環境で使う依存を管理する感じかな?
  override def load(context: LagomApplicationContext): LagomApplication =
    new MyStudyLagomApplication(context) {
      override def serviceLocator: ServiceLocator = NoServiceLocator
    }

  // このメソッド、消しても動く?と思って消してみたらエラー出た。
  override def loadDevMode(context: LagomApplicationContext): LagomApplication =
    new MyStudyLagomApplication(context) with LagomDevModeComponents
}

abstract class MyStudyLagomApplication(context: LagomApplicationContext)
  extends LagomApplication(context)
    with AhcWSComponents {

  // こんな感じでバインドするみたい
  override lazy val lagomServer: LagomServer = serverFor[MyStudyLagomService](wire[MyStudyLagomServiceImpl])
}

で、このLoaderをconfで指定してあげる。

application.conf
# Loaderの読み込みでこの一行は必要
play.application.loader = com.example.mystudylagom.impl.MyStudyLagomLoader

起動

ここまでできたら起動してみる。で作成したAPIにcurl投げると

gakumbp:study-lagom gaku$ curl http://localhost:9000/api/hello2/Alice
test

なんか返ってくる!
そして、別サービスのstudy-lagomのAPIにcurl投げると

gakumbp:study-lagom gaku$ curl http://localhost:9000/api/hello/Alice
Hello, Alice!

こちらもなんか返ってくる🥳

おわり

なんとな〜く。わかっては来ているけど、ここからどうサービス間で連携するのか、とか、どう永続化をするのか。
CQRSはどうすんの?集約ルートどう書くの?
みたいなところが山積みなんで、暇な時にこそこそやっていこうと思う。