Septeni Engineer's Blog

セプテーニ・オリジナルのエンジニアが綴る技術ブログ

お気に入りのScalaライブラリ・ツールをだらだら紹介する

この記事は Scala Advent Calendar 2019 の6日目です。

こんにちは。最近はPYXISのデータ基盤チームでSREっぽいことしている 門脇(@blac_k_ey)です。
TETRIS99の次はポケモンに進捗を奪われる日々を送っています。


締め切りギリギリまで ポケモンで忙しかった 「これ書きたい!」というテーマが思いつかなかったので、個人的に最近のScala開発でお世話になっているライブラリやツールなどを紹介していこうと思います。

詳細はあまり書かず、「ここが好き!」ぐらいのお気持ち表明ぐらいの文章でしかないので、ゆるく暖かい気持ちで読んでくれたら幸いです。

Ammonite

リッチなScalaREPL、Scalaスクリプト

自分のScala生活が豊かになっている大きな要因のひとつ。

Magic Importsを使って、ライブラリをちょっと試したいときにササッと使えるし、

$ amm
Loading...
Welcome to the Ammonite Repl 1.7.1
(Scala 2.12.10 Java 1.8.0_181)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ import $ivy.`com.typesafe.play::play-json:2.7.4`
https://repo1.maven.org/maven2/com/typesafe/play/play-json_2.12/2.7.4/play-json_2.12-2.7.4.pom
  100.0% [##########] 4.2 KiB (1.0 KiB / s)
https://repo1.maven.org/maven2/com/typesafe/play/play-functional_2.12/2.7.4/play-functional_2.12-2.7.4.pom
  100.0% [##########] 1.5 KiB (12.0 KiB / s)
https://repo1.maven.org/maven2/com/typesafe/play/play-functional_2.12/2.7.4/play-functional_2.12-2.7.4-sources.jar
  100.0% [##########] 9.7 KiB (79.4 KiB / s)
https://repo1.maven.org/maven2/com/typesafe/play/play-functional_2.12/2.7.4/play-functional_2.12-2.7.4.jar
  100.0% [##########] 175.5 KiB (389.2 KiB / s)
https://repo1.maven.org/maven2/com/typesafe/play/play-json_2.12/2.7.4/play-json_2.12-2.7.4-sources.jar
  100.0% [##########] 52.5 KiB (94.5 KiB / s)
https://repo1.maven.org/maven2/com/typesafe/play/play-json_2.12/2.7.4/play-json_2.12-2.7.4.jar
  100.0% [##########] 706.6 KiB (705.2 KiB / s)
import $ivy.$

@ import play.api.libs.json._
import play.api.libs.json._

@ val json = Json.parse("""{"language": "Scala", "repl": "Ammonite"}""")
json: JsValue = JsObject(Map("language" -> JsString("Scala"), "repl" -> JsString("Ammonite")))

スケジュール実行に対応したCIツールを使えば、手元で書いたちょっとしたスクリプトでささっと自動化できるし、

(esa.ioのメトリクスをDatadogに送信する例)

# .gitlab-ci.yml

image: openjdk:8

scheduled_post_stats:
  only:
    refs:
      - schedules
    variables:
      - $SCHEDULED_JOB_POST_STATS
  script:
    - ./amm esa-stats-to-datadog.sc --team septeni-original

f:id:Nomad_Blacky:20191205234130p:plain

f:id:Nomad_Blacky:20191205234756p:plain

何度閉じてもダイアログが開くという犯罪臭のするスクリプトだって書けちゃう!!(?)

import java.awt._, event._
import javax.swing._

import scala.util.Random

val (screenCenterW, screenCenterH) = {
  val ss = Toolkit.getDefaultToolkit().getScreenSize
  (ss.getWidth.toInt / 2, ss.getHeight.toInt / 2)
}

val label = new JLabel("何回閉じても無駄ですよ~ww")

def randomLocation(): (Int, Int) =
  (screenCenterW + Random.nextInt(200) - 200, screenCenterH + Random.nextInt(200) - 200)

def newDialog(): Unit = {
  val dialog = new JDialog()
  dialog.setSize(300, 200)
  val (x, y) = randomLocation()
  dialog.setLocation(x, y)
  dialog.add(label)
  dialog.setVisible(true)
  dialog.addWindowListener(new WindowAdapter {
    override def windowClosing(e: WindowEvent): Unit = {
      newDialog()
    }
  })
}

newDialog()

f:id:Nomad_Blacky:20191206000910g:plain

こうしてAmmoniteは自分のScala生活に欠かせないものになっていきました。

気になった人は以下のコマンドを実行して早速インストール! (執筆時点の最新版)

$ sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/lihaoyi/Ammonite/releases/download/1.8.2/2.13-1.8.2) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm' && amm

Ammoniteに関してはいくつか資料を公開していますので、そちらもご覧いただけると幸いです。

upickle

JSON, MessagePack に対応したシリアライゼーションライブラリ。
Ammoniteの依存に含まれるため、上記のコマンドでインストールした方はすぐお試しできます。

@ import upickle.default._
import upickle.default._

@ case class Post(id: Int, name: String, tags: Seq[String])
defined class Post

@ implicit val postRW: ReadWriter[Post] = macroRW
postRW: ReadWriter[Post] = upickle.core.Types$ReadWriter$$anon$3@73fb1d7f

@ val json = """
    [
      {"id":1, "name":"post1", "tags":["Java"]},
      {"id":2, "name":"post2", "tags":["Scala", "Ammonite"]}
    ]
    """
json: String = """
  [
    {"id":1, "name":"post1", "tags":["Java"]},
    {"id":2, "name":"post2", "tags":["Scala", "Ammonite"]}
  ]
  """

@ val posts = read[Seq[Post]](json)
posts: Seq[Post] = Vector(Post(1, "post1", Vector("Java")), Post(2, "post2", Vector("Scala", "Ammonite")))

@ write(posts)
res5: String = "[{\"id\":1,\"name\":\"post1\",\"tags\":[\"Java\"]},{\"id\":2,\"name\":\"post2\",\"tags\":[\"Scala\",\"Ammonite\"]}]"

macroによって、最低限のボイラープレートでJSONの読み書きを実現できていますね。

他のJSONライブラリと比べて好きなところとしては、自身以外のライブラリに依存していないところでしょうか。

$ coursier resolve --tree com.lihaoyi::upickle:0.8.0
  Result:
└─ com.lihaoyi:upickle_2.13:0.8.0
   ├─ com.lihaoyi:ujson_2.13:0.8.0
   │  └─ com.lihaoyi:upickle-core_2.13:0.8.0
   │     └─ org.scala-lang.modules:scala-collection-compat_2.13:2.0.0
   │        └─ org.scala-lang:scala-library:2.13.0
   ├─ com.lihaoyi:upack_2.13:0.8.0
   │  └─ com.lihaoyi:upickle-core_2.13:0.8.0
   │     └─ org.scala-lang.modules:scala-collection-compat_2.13:2.0.0
   │        └─ org.scala-lang:scala-library:2.13.0
   └─ com.lihaoyi:upickle-implicits_2.13:0.8.0
      └─ com.lihaoyi:upickle-core_2.13:0.8.0
         └─ org.scala-lang.modules:scala-collection-compat_2.13:2.0.0
            └─ org.scala-lang:scala-library:2.13.0

$ coursier resolve --tree com.typesafe.play::play-json:2.7.4
  Result:
└─ com.typesafe.play:play-json_2.13:2.7.4
   ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.8
   ├─ com.fasterxml.jackson.core:jackson-core:2.9.8
   ├─ com.fasterxml.jackson.core:jackson-databind:2.9.8
   │  ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8
   │  └─ com.fasterxml.jackson.core:jackson-core:2.9.8
   ├─ com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.8
   │  ├─ com.fasterxml.jackson.core:jackson-core:2.9.8
   │  └─ com.fasterxml.jackson.core:jackson-databind:2.9.8
   │     ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8
   │     └─ com.fasterxml.jackson.core:jackson-core:2.9.8
   ├─ com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8
   │  ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8
   │  ├─ com.fasterxml.jackson.core:jackson-core:2.9.8
   │  └─ com.fasterxml.jackson.core:jackson-databind:2.9.8
   │     ├─ com.fasterxml.jackson.core:jackson-annotations:2.9.0 -> 2.9.8
   │     └─ com.fasterxml.jackson.core:jackson-core:2.9.8
   ├─ com.typesafe.play:play-functional_2.13:2.7.4
   │  └─ org.scala-lang:scala-library:2.13.0
   ├─ joda-time:joda-time:2.10.1
   ├─ org.scala-lang:scala-library:2.13.0
   └─ org.scala-lang:scala-reflect:2.13.0
      └─ org.scala-lang:scala-library:2.13.0

$ coursier resolve --tree org.json4s::json4s-native:3.6.7
  Result:
└─ org.json4s:json4s-native_2.13:3.6.7
   ├─ org.json4s:json4s-core_2.13:3.6.7
   │  ├─ com.thoughtworks.paranamer:paranamer:2.8
   │  ├─ org.json4s:json4s-ast_2.13:3.6.7
   │  │  └─ org.scala-lang:scala-library:2.13.0
   │  ├─ org.json4s:json4s-scalap_2.13:3.6.7
   │  │  └─ org.scala-lang:scala-library:2.13.0
   │  └─ org.scala-lang:scala-library:2.13.0
   └─ org.scala-lang:scala-library:2.13.0

$ coursier resolve --tree io.circe::circe-core:0.11.1
  Result:
└─ io.circe:circe-core_2.12:0.11.1
   ├─ io.circe:circe-numbers_2.12:0.11.1
   │  └─ org.scala-lang:scala-library:2.12.8
   ├─ org.scala-lang:scala-library:2.12.8
   └─ org.typelevel:cats-core_2.12:1.5.0
      ├─ org.scala-lang:scala-library:2.12.7 -> 2.12.8
      ├─ org.typelevel:cats-kernel_2.12:1.5.0
      │  └─ org.scala-lang:scala-library:2.12.7 -> 2.12.8
      ├─ org.typelevel:cats-macros_2.12:1.5.0
      │  ├─ org.scala-lang:scala-library:2.12.7 -> 2.12.8
      │  └─ org.typelevel:machinist_2.12:0.6.6
      │     ├─ org.scala-lang:scala-library:2.12.6 -> 2.12.8
      │     └─ org.scala-lang:scala-reflect:2.12.6
      │        └─ org.scala-lang:scala-library:2.12.6 -> 2.12.8
      └─ org.typelevel:machinist_2.12:0.6.6
         ├─ org.scala-lang:scala-library:2.12.6 -> 2.12.8
         └─ org.scala-lang:scala-reflect:2.12.6
            └─ org.scala-lang:scala-library:2.12.6 -> 2.12.8

Scala.js対応している点も人によっては嬉しいかもしれません。

あと、他のJSONライブラリと比較してパフォーマンスが良いそうですが、自身で試してないので深くは言及しません。

requests

とてもシンプルなHTTPクライアント。
PythonのRequestsから影響を受けているみたいです。

これもAmmoniteの依存に含まれます。

@ val response = requests.get("https://google.com")
response: requests.Response = Response(
  "https://www.google.com/",
  200,
  "OK",
  Map(
    "expires" -> Buffer("-1"),
    "server" -> Buffer("gws"),
    "p3p" -> Buffer("CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\""),
    "x-xss-protection" -> Buffer("0"),
    "cache-control" -> Buffer("private, max-age=0"),
    "date" -> Buffer("Thu, 05 Dec 2019 12:54:14 GMT"),
    "content-type" -> Buffer("text/html; charset=ISO-8859-1"),
    "transfer-encoding" -> Buffer("chunked"),
    "x-frame-options" -> Buffer("SAMEORIGIN"),
    "alt-svc" -> Buffer(
      "quic=\":443\"; ma=2592000; v=\"46,43\",h3-Q050=\":443\"; ma=2592000,h3-Q049=\":443\"; ma=2592000,h3-Q048=\":443\...

@ response.text.take(100)
res1: String = "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/WebPage\" lang=\"ja\"><head><meta content"

import文なしにそのまま書ける点が地味ながらすき。

前述のupickleと合わせればWebAPIを叩くスクリプトも組めますね。

sbt-explicit-dependencies

コンパイル時に不要なライブラリや、暗黙的に依存しているライブラリを見つけてくれるsbtプラグインです。

ここでの「暗黙的に依存している」とは、ライブラリAが依存するライブラリBをアプリケーションコードが直接利用していることを指します。 (例: Circeの依存を追加して、ソースコード上でcatsを利用している)

↓インストール方法↓

// 執筆時点の最新版
addSbtPlugin("com.github.cb372" % "sbt-explicit-dependencies" % "0.2.11")

たとえば、以下のようなプロジェクトがあったとします。

// build.sbt
lazy val root = (project in file("."))
  .settings(
    name := "explicitdeps",
    libraryDependencies ++= Seq(
      "com.lihaoyi" % "ammonite" % "1.8.2" cross CrossVersion.full
    )
  )
// Hello.scala
object Hello extends App {
  println(ujson.Obj("key" -> "value", "foo" -> "bar").render(indent = 2))
}

sbtを起動して、 unusedCompileDependencies, undeclaredCompileDependencies を実行します。

sbt:explicitdeps> unusedCompileDependencies
[warn] explicitdeps >>> The following libraries are declared in libraryDependencies but are not needed for compilation:
[warn]  - "com.lihaoyi" % "ammonite" % "1.8.2"
[success] Total time: 0 s, completed Dec 5, 2019 11:28:04 PM

sbt:explicitdeps> undeclaredCompileDependencies
[warn] explicitdeps >>> The project depends on the following libraries for compilation but they are not declared in libraryDependencies:
[warn]  - "com.lihaoyi" %% "ujson" % "0.8.0"
[success] Total time: 0 s, completed Dec 5, 2019 11:28:17 PM

前者でライブラリ依存に加えているのに使われていない ammonite の依存が検出され、
後者でアプリケーションコードで使われているのに明示的に依存していない ujson が検出されました。

これで、闇のライブラリ依存に対する防衛術が手に入りましたね。べんり!

不正な依存があった場合にエラーとなる unusedCompileDependenciesTest, undeclaredCompileDependencies コマンドもあるので、CIに組み込むといい感じになります。

例えば、GitHub Actionsで使う場合はこんな感じ

check_unused_compile_dependencies:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v1
    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: '1.8'
    - name: Check unused compile dependencies
      run: sbt test/unusedCompileDependenciesTest

check_undeclared_compile_dependencies:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v1
    - name: Set up JDK 1.8
      uses: actions/setup-java@v1
      with:
        java-version: '1.8'
    - name: Check undeclared compile dependencies
      run: sbt test/undeclaredCompileDependenciesTest

プロジェクト導入初期からぜひ入れたいプラグインですね!

scala-steward

依存ライブラリのアップデートを探してリポジトリにプルリクエストを投げてくれるとてもかしこいbot
似たものとしてはdependabotなどがあります。
(こちらはsbtに非対応)

自分で動かすことも可能ですが、今回はコミュニティで定期的に動かしてくれているものを使いましょう。

このファイルに自身が公開しているGitHubリポジトリを加えて、プルリクエストを出します。

f:id:Nomad_Blacky:20191205235415p:plain

そのうち筆者のfthomasさんがマージしてくれます。(感謝)

すると、1日1回ぐらい(?)の頻度でライブラリのアップデートを確認して、自身のリポジトリプルリクエストを出してくれます。

f:id:Nomad_Blacky:20191205235445p:plain

f:id:Nomad_Blacky:20191205235548p:plain

自分でライブラリのアップデートを確認する作業は骨が折れるので、とても便利ですね!
便利と感じたら、ぜひ感謝の気持ちとしてBadgeを付けましょう。

f:id:Nomad_Blacky:20191205235626p:plain

まとめ

Scalaすき…

関数型ニキ達にインスパイアされた最近の Scala 開発の取り組み

こんにちは、中途三年目の堀越です。

近頃、Scalaのコミュニティにおいて Functional Programming による実装テクニックを紹介する記事や発表を見たり聞いたりすることは珍しいことではなくなってきました。弊社にもたくさんの関数型ニキ*1が在籍しており、わたしも日々影響を受けています。

ということで、本日はわたしが所属するチームでの日々の Scala 開発における取組みや戦略をサンプルコード*2と合わせて紹介していきます。

高カインド型によるEffect型の抽象化

私達はドメイン駆動設計を実践しています。なのでドメインロジックはドメインの関心事に集中できるのが理想です。ドメイン層を抽象化し、特定の実行環境や技術的関心事に依存しない戦略として 高カインド型 を用いてEffect型を抽象化します。

*1:関数型つよつよお兄さん

*2:import 文とか一部端折ってる部分あり。

続きを読む

Scala秋祭り開催しました!

こんにちは。セプテーニ・オリジナルの池田です。

先日サイバーエージェントさん、ビズリーチさん、チャットワークさん、そしてセプテーニ・オリジナルの4社で、「Scala秋祭り」を開催しました!!

f:id:taketor:20190917175728p:plain

【増席】Scala秋祭り - connpass

今年6月に開催されたScalaMatsuri2019にて、各社ブースが近く一緒にイベントしましょうとのことで話しをしまして、今回開催する運びになりました。

続きを読む

業務十倍効率化計画 Sentry API ft. Shell Script

みなさんこんにちは、2 年目を迎えました清水です。Scala 全然書かずに TypeScript やら Shell Script ばっか書いてます。今回はエラートラッキングツール Sentry の API を使って業務を効率化したよという内容で書かせていただきました。

概要をざっくりと説明すると、Sentry API と Shell Script でこれまで 100 分くらいかかっていたミーティングが 10 分にまで短縮できたよというような内容です(Sentry について全然知らなくても読めます)。

ちなみに、GitLab API で業務改善した同じ毛色のブログも書いてますので、この記事を読んでみて面白いと思ったらぜひ読んでみてください(http://labs.septeni.co.jp/entry/2019/03/05/120000)。

続きを読む

PYXISインフラチームの軌跡

中途三年目、堀越です。

わたしはPYXISという広告代理事業のエンジニアです。アプリケーション開発をする傍らインフラチームという集団の一員でもあります。今日はそのインフラチームついて書いていきたいと思います。

メンバー構成

わたしを含め4人のメンバーから構成されています。

Name Description
K.I氏 課題解決マン、貧乏くさいメロディックデスメタルが好き
H.H氏 シェル芸人、ビールが好き、1児の父
T.K氏 Scalaテトリスが好き、千葉県在住(最近引っ越したらしい)
あたい ブロガー、弁当男子、筋トレと走ることが好き
続きを読む