Backlog チームの内田です。2年前に Scala を使って Redmine のデータを Backlog に移行できるツール「 Backlog Redmine Importer 」を作成しました。以下の図のようにRedmineからjson形式でデータをエクスポートし、Backlogにインポートするツールです。なぜ一旦ファイルに出力しているのかというとBacklogにインポートする部分を独立して動かすことができれば、Redmineからの移行以外にも様々な用途に利用できるためです。
今回のブログでは、Backlogにインポートする部分をモジュール化する時に2点ハマったところがあったので、問題点と解決方法を紹介したいと思います。
*RedmineからBacklogへの移行ツールは「 Backlog Redmine Importer」からご利用いただけます。通常なら大変な移行も簡単に移行できますので是非ご利用ください。
*Backlogのインポートモジュールを使用してBacklogのプロジェクトを別のスペースに移動するツールも開発されています。こちらも是非ご利用ください。Backlogのプロジェクト移行ツールについてはこちらのサイト( https://www.backlog.jp/faq/service/post-45.html )を参照ください。
問題点としては次の2点がありました。
- 仕様が確定しないモジュールを別プロジェクトとして開発すると大変
- 必要のないプログラムが参照できてしまう
仕様が確定しないモジュールを別プロジェクトとして開発すると大変
私はBacklogにインポートする部分をモジュール化するために、全く別のプロジェクトとして開発することにしました。この際、移行ツール側では publishLocal
というsbtの機能で自作ライブラリ化して利用するように考えていました。
しかし、この方法はRedmineからの移行ツールの開発という意味では非常に開発効率が悪かったのです。というのもRedmine APIの仕様を調べたあとでもAPI Clientで使用する時には思っていたものと違ったり、バージョンによって仕様が違ったりで、Backlogに取り込める形にするために細かい調整が必要で何度も publishLocal
する必要があり、開発をスムーズに進めることができませんでした。(publishLocal
はプロジェクトのビルドが走るため遅い)
プロジェクト構成(改善前)
(解決方法)マルチプロジェクト・ビルドを使う
この問題についてはマルチプロジェクト・ビルドを使うことで解決することができました。
マルチプロジェクト・ビルドとは通常のプロジェクトの中に複数のサブプロジェクトを入れておきビルドするというものです。共通の設定(例えばバージョンやライブラリ)を複数のサブプロジェクトに同時に適用できたり、依存関係を整理したりするのに役に立ちます。
私は、マルチプロジェクト・ビルドを使うことでモジュールをさらに細かく次の図のように4つに分割しました。この構成にすることによって開発時は1つのプロジェクトの中でRedmineのエクスポートモジュールとBacklogのインポートモジュールを同時に修正できるようになり非常に開発効率は上がりました。
プロジェクト構成
恩恵は開発効率だけではなく、Redmineモジュールとエクスポートモジュール、Backlogモジュールとインポートモジュールを分割することでモジュールの結合度を弱めることができ保守性や読みやすさ再利用性を改善することができました。
build.sbt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
lazy val backlog = (project in file("backlog")) lazy val importer = (project in file("importer")) .dependsOn(backlog) lazy val redmine = (project in file("redmine")) lazy val exporter = (project in file("exporter")) .dependsOn(redmine) lazy val root = (project in file(".")) .dependsOn(importer, exporter) |
上記のようにimporter
プロジェクトのdependsOn
メソッドにbacklog
プロジェクトを渡すことで、importer
プロジェクトがbacklog
プロジェクトに依存するという依存関係を作ることができます。これにより上記の図のプロジェクト構成通りの依存関係を作ることができます。
マルチプロジェクト・ビルドについてさらに知りたい方はこちらが公式ドキュメントになりますので参照ください。 http://www.scala-sbt.org/0.13/docs/ja/Multi-Project.html
必要のないプログラムが参照できてしまう
さて、ここまでくればだいぶ綺麗になったような気がしますが、まだ問題に感じているところがありました。
次の図のようにroot
プロジェクトはimporter
プロジェクトに依存するため、root
プロジェクトは全く参照する必要のないプログラムも参照できてしまいます。これでは保守する人が違う場合に、誤ってプログラミングする可能性があります。root
プロジェクトが本来必要なのはimporter
プロジェクトの起動プログラムのみになりますので、どうにかその参照だけにしたいところです。
(解決方法)限定子を使う
「必要のないモジュールが参照できてしまう問題」については限定子を使うことで解決できました。
Scalaではアクセス修飾子の他に限定子というものを使用することでより詳細なアクセス権限を設定することができます。
例えばclassやobject、関数の前に次のように指定します。Xにはパッケージ、クラス、シングルトンオブジェクトを指定できこの範囲がアクセス可能な範囲と設定できます。
1 2 3 4 5 6 7 8 9 |
private[X] class { } private[X] object { } private[X] def hoge() = { } |
パッケージにより指定範囲まで公開できるという方法を使用し次のように実装しました。
Boot.scala
1 2 3 4 5 6 7 8 9 |
package importer object Boot { def apply() = { val issue = new IssueService() issue.add() } } |
IssueService.scala
1 2 3 4 5 6 |
package importer private[importer] class IssueService { def add() = {} } |
Main.scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package root import importer.IssueService object Main { def main(args: Array[String]): Unit = { importer.Boot() val issue = new IssueService()//ここがエラーになります issue.add() } } |
これで上記の図を次の図のように変えることができました。importer
というパッケージでスコープを作り起動プログラムのみを公開することでroot
プロジェクトからは起動プログラムのみが参照できるようになっています。
まとめ
次の2点の問題について解決方法を紹介しました。
- 仕様が確定しないモジュールを別プロジェクトとして開発すると大変 -> マルチプロジェクト・ビルドを使う
- 必要のないプログラムが参照できてしまう -> 限定子を使う
モジューラブルに作ることで、機能追加がしやすかったり、バグが入りにくかったり、元々の開発者の意図を理解しやすかったりしますので是非お試しください。