Scalaを使ってBacklog APIのクライアントライブラリ「Backlog for Scala」を作ってみました。本記事では、ライブラリを作成する際に、基本的なinterfaceやData Streaming APIをどのように実装したのかをお伝えします。
最近、Backlog API用のScalaライブラリを作っているのですが、それが関数型プログラミングでライブラリをデザインする力を身につける良いトレーニングになっています。
BacklogではすでにOOPスタイルで書かれたBacklog4jを提供していますが、個人的な感想として、複雑なケースで良い感じで処理を記述する事ができないのと、十分に強い型付ではないところが、もう少し向上されるといいなって思っていました。
私は関数型プログラミングや強い型付の言語が好きなので、それらを使って簡単にAPIにアクセスするライブラリを提供してみたいと思っていました。
以下は、自分がそのライブラリを実装する時にトライしてみたかった事のリストです。
- TypeSafeなBacklog APIライブラリ
- Data streaming
- 簡単にBacklog webhookに設定できる
- OAuth2 authentication
- ユーザーの好きなHTTPライブラリが使える
下記のような事も、併せて実験したいと思っていました。
- GraphQL for Backlog
- scala.js上でも動く
関数型プログラミングでは、副作用をうまくコントロールし、集約させることで副作用がもたらす色々な問題から解放されます。個人的にはそこがとても気に入っています。そしてコードを綺麗にデザインする事ができ、テストも書きやすくなります。
副作用をうまく集約すると、コードのモジュール化がやりやすくなり、どのように副作用を取り扱うかをユーザーの好きなように決める事ができます。
私はFree Monadを使用しています。もしFree Monadをご存知なくても、すごく良い解説コンテンツがあるので、下記を是非チェックしてみてください。
1. 基本的なinterfaceを定義する
APIからデータを取得する全ての型の定義が終わっていれば、こんな風に書けます。
//なんのデータを取りたいのかを概要的にかける。ここでリクエストは発生しない。 //.orFailはこの処理が失敗して処理が続けられない時に実行される。 val prg = for { projects <- projectApi.all().orFail issues <- issueApi.search(IssueSearch(projectIds = projects.map(_.id))).orFail } yield issues //ここでinterpreterを使ってプログラムを実行し、全てのリクエストを作成する。 //interpreterはプログラムの実行に責任を持つものであり、interpreterを切り替える事で、実際にリクエストせずにモックを取得するということもできる。 //ここでは、Akka HTTPを使ったAPIのリクエストのinterpreterの例を示している。 //これはデータ取得に必要な全てのリクエストを実行する。 //このように、"何をしたいか"と"それをどうやってやりたいか"を分けて記述する事ができる。 interpreter.run(prg).onComplete { case Success(issues) => println(issues.map(_.summary)) case Failure(ex) => ex.printStacktrace() }
上記の最初のブロックの記述のように、まずはAPI呼び出しを組み合わせる事ができます。この時に実際のリクエストは行われない。これらのすべての呼び出しはBacklog APIに対してTypesafeです。
2. Data Streaming APIを定義する
Data streamingをシンプルかつパワフルに表現したかったので、 FS2 stream ライブラリを選びました。
FS2はstreamをeffectとして表現するためのツールを提供しているので、処理はさらに簡単になります。
こんな感じに書けます。
//これは 100個の課題を取得するAPIリクエストを、並列で4個同時に実行し、合計で10000個に達するまで実行し続けるstreamを作る。 //ここでは課題はただprintされるだけである。 val stream = ApiStream.parallel(10000, 4)( (index, count) => issueApi.search(IssueSearch(offset = index, count = count)) ).map { issues => println(issues.map(_.summary).mkString("\n")) println() issues } //こちらは100この課題を取得するAPIを10000個に達するまで直列に実行する例である。 val stream2 = ApiStream.sequential(10000)( (index, count) => issueApi.search(IssueSearch(offset = index, count = count)) ).map { issues => println(issues.map(_.summary).mkString("\n")) println() issues } val prg = for { projects <- projectApi.all().orFail issuesStream <- ApiStream.sequential(10000)( (index, count) => issueApi.search(IssueSearch(offset = index, count = count, projectIds = projects.map(_.id)) ).compile.drain } yield () //再度interpreterを使ってstreamを構築する。 //ここで実際に処理が実行される interpreter.runStream(stream).onComplete { case Success(()) => println("Stream processed") case Failure(ex) => ex.printStacktrace() } interpreter.runStream(stream2).onComplete { case Success(()) => println("Stream processed") case Failure(ex) => ex.printStacktrace() } //stream以外の処理をstreamとして実行する。 //ここで渡されている処理は、複数のAPIリクエスト処理が組み合わせれたものである。 interpreter.run(prg).onComplete { case Success(()) => println("Stream processed") case Failure(ex) => ex.printStacktrace() }
API呼び出しを組み合わせた処理からstreamを作ったりできるので、プログラムの構築をより簡単に行えます。
開発者はプログラムの概要を読むだけで、何をするのかが簡単に理解でき、これがどのように処理を行っているかを気に留める必要がありません。
Backlog APIでGraphQLを試す
先日チャンスがあったので、実験的にGraphQL server for Backlog APIしてみました、まだ満足できるところまで行かなかったのですが、Backlog APIをGraphQLにするとどういう感じになるのかが分かって非常に興味深かったです。同僚と二日間に渡ってプロジェクト/課題/コメントの部分を実装してみました。
今回実装にあたり Sangriaを使用したのですが、ScalaでGraphQLを実装しやすくなりました。欲しいデータをどう取得するのかをうまく抽象化する事ができるので、非常に面白い結果となりました。
GraphQLと同じようようなアイデアを考えた人は過去にも存在しましたが、FacebookがGraphQLを公開した事でそのアイデアは一躍脚光を浴びました。GraphQLを使う事でフロントエンドはサーバーへのリクエスト数を可能な限り減らす事ができます。
我々が作成したライブラリはこちらからお試ししいただけます。
これから試してみたいこと
このライブラリはまだ開発段階で、足りてない機能もまだあるのですが、お気軽にフィードバックいただると嬉しいです。
次に開発しようと思ってるのは下記です。
- webhookのセットアップを容易にする
- OAuth2のサポート
- パラレル処理をFree Applicativeを使って実装する
また、こちらも実験してみたいと思っています。
- scala.jsのサポート
今回作ったGraphQLserverは私が作っているBacklog API For Scalaの一部です。是非Backlog API For Scala もチェックして見てください。
Scalaを使った開発に興味がある方はぜひ一度お話してみませんか?