Backlog Redmine Importer を作るときに気づいた Scala で再利用可能なモジュールを作る方法

Backlog チームの内田です。2年前に Scala を使って Redmine のデータを Backlog に移行できるツール「 Backlog Redmine Importer 」を作成しました。以下の図のようにRedmineからjson形式でデータをエクスポートし、Backlogにインポートするツールです。なぜ一旦ファイルに出力しているのかというとBacklogにインポートする部分を独立して動かすことができれば、Redmineからの移行以外にも様々な用途に利用できるためです。

今回のブログでは、Backlogにインポートする部分をモジュール化する時に2点ハマったところがあったので、問題点と解決方法を紹介したいと思います。

Redmine Importerの構成

*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のインポートモジュールを同時に修正できるようになり非常に開発効率は上がりました。

backlog redmine

プロジェクト構成

恩恵は開発効率だけではなく、Redmineモジュールとエクスポートモジュール、Backlogモジュールとインポートモジュールを分割することでモジュールの結合度を弱めることができ保守性や読みやすさ再利用性を改善することができました。

build.sbt

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プロジェクトの起動プログラムのみになりますので、どうにかその参照だけにしたいところです。

backlog redmine

(解決方法)限定子を使う

「必要のないモジュールが参照できてしまう問題」については限定子を使うことで解決できました。

Scalaではアクセス修飾子の他に限定子というものを使用することでより詳細なアクセス権限を設定することができます。

例えばclassやobject、関数の前に次のように指定します。Xにはパッケージ、クラス、シングルトンオブジェクトを指定できこの範囲がアクセス可能な範囲と設定できます。

private[X] class {
}

private[X] object {
}

private[X] def hoge() = {
}

パッケージにより指定範囲まで公開できるという方法を使用し次のように実装しました。

Boot.scala

package importer

object Boot {
  def apply() = {
    val issue = new IssueService()
    issue.add()
  }
}

IssueService.scala

package importer

private[importer] class IssueService {
  def add() = {}
}

Main.scala

package root

import importer.IssueService

object Main {
  def main(args: Array[String]): Unit = {
    importer.Boot()

    val issue = new IssueService()//ここがエラーになります
    issue.add()
  }
}

backlog redmine

これで上記の図を次の図のように変えることができました。importerというパッケージでスコープを作り起動プログラムのみを公開することでrootプロジェクトからは起動プログラムのみが参照できるようになっています。

backlog redmine

まとめ

次の2点の問題について解決方法を紹介しました。

  • 仕様が確定しないモジュールを別プロジェクトとして開発すると大変 -> マルチプロジェクト・ビルドを使う
  • 必要のないプログラムが参照できてしまう -> 限定子を使う

モジューラブルに作ることで、機能追加がしやすかったり、バグが入りにくかったり、元々の開発者の意図を理解しやすかったりしますので是非お試しください。

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる