petitviolet_blog

@petitviolet blog

ScalaMatsuri2019で発表してきました

「ピュアなドメインを支える技術」というタイトルで発表してきました。

2019.scalamatsuri.org

ScalaMatsuriは2017で発表して以来だったので楽しみにしてました。

petitviolet.hatenablog.com

資料

内容としては関数型プログラミング + CQRSという感じでDDDをどうやってピュアに保って推進するか、というもの。 MonadとかKleisliとかCQRSみたいなウッとなりやすい言葉を出しつつどうやってわかりやすく伝えるかに腐心したので、何かしら伝わっていると嬉しい。

感想

時間はたっぷりあったはずなのに、なぜか直前まで原稿書いたりスライドを微修正したりして他の人の発表を十分に楽しめなかったのを大いに後悔している。。

運営の皆さまも本当にありがとうございました! WiFiも快適でカフェスペースもあり、何も困ることがなかったです。

ScalaMatsuriは今後も参加したいところです。

いい感じにオブジェクトのdiffを取るライブラリ作った

Scalaのオブジェクト同士を比較する際に、==を使って比較することは出来るが、同値かそうでないかしかわからない。 そこで、具体的に何がどう変わったのかをジェネリックに取得するライブラリを作ってみた。

こんな雰囲気で使える。

$ amm
Loading...
Welcome to the Ammonite Repl 1.6.7
(Scala 2.12.8 Java 1.8.0_192)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ import $ivy.`net.petitviolet::generic-diff:0.6.0-RC2`; import net.petitviolet.generic.diff._
import $ivy.$                                        ;
import net.petitviolet.generic.diff._

@ case class Hoge(i: Int, s: String)
defined class Hoge

@ Hoge(100, "foo") diff Hoge(100, "bar")
res: DiffResult[Hoge] = DiffResult(List(FieldSame("i", 100), FieldDiff("s", "foo", "bar")))

関係ないけどAmmonite便利。

case classのフィールドで異なる値があればそれがわかるようになっている

ソースコードpetitviolet/scala-generic-diffに公開してあってScala2.13.0-RC2まで無駄に対応してみた。

ユースケース

例えば、オブジェクトのフィールドを更新する処理の中で、実際に値が書き換わっていたらDBに保存するが、値が書き換わっていなければDBへの保存処理をスキップする、などが出来る。 あるいはバリデーションで何も更新されていないよと突き返すことも。

classのequalsをoverrideしてidによる同値チェックで実装しているような場合とか。 DDDのEntityでそういう処理をしていることは多そう。

@ case class User(id: String, name: String) {
    override def equals(obj: scala.Any): Boolean = obj match {
      case other: User => other.id == this.id
      case _ => false
    }
    def updateName(newName: String) = new User(this.id, newName)
  }
defined class User

@ val user = User("user-id", "alice")
user: User = User("user-id", "alice")

こういうuserがいた時に、==での比較するとこうなる。

@ user == user
res: Boolean = true

@ user == user.updateName("bob")
res: Boolean = true

これに対してdiffを取ると、

@ user diff user
res: DiffResult[User] = DiffResult(List(FieldSame("id", "user-id"), FieldSame("name", "alice")))

@ user diff user.updateName("bob")
res: DiffResult[User] = DiffResult(List(FieldSame("id", "user-id"), FieldDiff("name", "alice", "bob")))

このようにフィールドごとに同値判定をして、同じ値かどうかを結果として返却してくれる。

異なるフィールドが存在したかどうかはhasDiffを参照すれば可能。

@ (user diff user).hasDiff
res: Boolean = false

@ (user diff user.updateName("bob")).hasDiff
res: Boolean = true

技術的な話

ここからはライブラリの実装的な話でトピックは以下。

ジェネリックプログラミング

case classのコンストラクタ全体を参照するのにshapelessを使っている。 ジェネリックにフィールド名と値を取得している。

実装はdiff.scalaのこのあたり
case classをHListで表現しつつ、LabelledGenericでフィールド名と値の組を扱っている。

再帰的にフィールドを参照しつつdiffをとっていくような実装となっていて、アイデアcirceから学ぶ GenericProgramming入門パクった 参考にした。

macro

こんな感じにdiffした結果をフィールド名で参照できるようにするためにmacroを使っている。

@ val diffResult = user diff user.updateName("bob")
diffResult: DiffResult[User] = DiffResult(List(FieldSame("id", "user-id"), FieldDiff("name", "alice", "bob")))

@ diffResult.id
res: Field = FieldSame("id", "user-id") 

@ diffResult.name
res: Field = FieldDiff("name", "alice", "bob")

ScalaDynamicsを使ってフィールドを自由に指定することが出来るようにしつつ、内部的にはmacroを使って型安全性を担保している。

ここのアイデアscalikejdbc/SQLSyntaxSupportFeature.scalaを参考に実装した。

ここでフィールドとして存在しない名前を指定するとコンパイルで死ぬ。

@ diffResult.hoge
java.util.NoSuchElementException: field #hoge not found.
  net.petitviolet.generic.diff.DiffResult.$anonfun$field$2(GenericDiff.scala:29)
  scala.Option.getOrElse(Option.scala:138)
  net.petitviolet.generic.diff.DiffResult.field(GenericDiff.scala:29)
  ammonite.$sess.cmd15$.<init>(cmd15.sc:1)
  ammonite.$sess.cmd15$.<clinit>(cmd15.sc)

Dynamicだけどマクロで型安全に実装していてScala便利。

今後の展望

使うのかどうかよく分からないので展望とか今のところ特にない。

2018年度の振り返り

社会人4年目の振り返り。
昨年度のはこれ。

petitviolet.hatenablog.com

エンジニアとして

相変わらずサーバサイドでScalaを書くことがほとんど。
GraphQLを実戦投入出来たし、FPとかDDDらへんも深めることが出来た感覚。

GraphQLについてはGraphQLナイトとScala関西で発表も出来た。

petitviolet.hatenablog.com

FPとかDDDっぽい話で行くとこのへん。

fringeneer.hatenablog.com

Kubernetesは実戦投入したこともあってクラウドネイティブという流れにのってトークしてみたり。

petitviolet.hatenablog.com

スキルとしては新しい何かを身につけたというよりは今までのを総合的にアウトプット出来た一年間だったような感じでもある。
新規事業を作るぞ!みたいなところだとそれなりのバリューを発揮できるくらいの力はついてきたと思う多分。

仕事

今年度は1つのプロダクト開発に従事していた。
プロダクトとしては1つだったが、チーム内では広告配信サーバと管理画面のバックエンドAPIとバッチらへんをがっつり設計から実装までやった。 会社としてMicrosoft Azureをまともに使ったのは初めてだったのもあって共同開催の勉強会をするなどしていた。(自分は発表してないけど)

fringe81.connpass.com

Kubernetesも実戦投入出来たし多少の知見は溜まったものの、もっとでかい規模感でやりたいという気持ち。 他には少しAndroidな仕事を久しぶりにするなどもしていた。アプリ作るわけじゃなくてピュアJavaSDK改修してたというくらい。

あとインターンとかの採用関連っぽいところの仕事もそこそこ占めていた感覚。
サマーインターンのお題作ったりとかそういうの。

そして年始いきなり病欠してしまったというのが辛い出来事だった。

twitter.com

プライベート

子どもが少し大きくなって歩き始めたり、家を買うなど。まだリフォーム中だけど。
人生順調である。

来年度に向けて

なんと30歳になるので体調管理頑張りつつもっとスキルの幅を広げていきたいという気持ち。 ちなみに今も風邪をひいていて、年々免疫とか体力とかが落ちてきているというのを実感している。。

そして、自分で書いた小さいライブラリを公開して自分で使ったりはしているものの、OSSにコントリビュートは全くできていないので頑張りたい所存。