GeekFactory

int128.hatenablog.com

Doma 2をGroovyで使用する時に気を付けること

気付いた範囲でまとめてみます。

フォルダ配置

Doma 2はAnnotation ProcessorでDAOの実装クラスを自動生成します。Doma 2のAnnotation ProcessorJavaのコードにのみ対応しているため、DAOやエンティティはGroovyではなくJavaで書く必要があります。また、GroovyとJavaのコードが互いに参照する場合は両者を /src/main/groovy に配置する必要があります。

具体的には以下のような配置になります。

  • src/main/
    • groovy/
      • example/entity/
        • Hello.java エンティティクラスはJavaで書く必要がある Groovyで書いてもよい
      • example/dao/
        • HelloDao.java DAOインタフェースはJavaで書く必要がある
    • resources/
      • META-INF/example/dao/HelloDao/
        • selectById.sql

(4/18追記)エンティティクラスもAnnotation Processorで処理されるため、Javaで書く必要があるようです。

ビルドの設定

GroovyとJava/src/main/groovy に配置した場合、GroovyコンパイラがAnnotation Processorを実行することになるため、以下のように compileGroovy タスクに対して設定を適用します。

// build.gradle
plugins {
    id 'groovy'
}

dependencies {
    compile 'org.seasar.doma:doma:2.16.0'
}

processResources.destinationDir = compileGroovy.destinationDir
compileGroovy.dependsOn processResources

外部ドメインを利用する場合などAnnotation Processorの設定が必要な場合は、Groovyコンパイラに設定を渡します。

compileGroovy.options.compilerArgs = ['-Pdoma.domain.converters=example.framework.data.HelloConverter']

2.16.0からはAnnotation Processorの設定を /src/main/resources/doma.compile.config に書けます。これにより、ビルドツールやIDEでAnnotation Processorを設定する必要がなくなります。

その他

ドメインクラスは @Immutable@Canonical を使うと扱いやすいです。

Create React AppでChrome Extensionを開発する

Reactのアプリを開発するにはFacebook謹製のcreate-react-appが便利です。しかし、create-react-appはWebアプリの開発に特化しているため、Chrome Extensionの開発には使えない問題があります。スクリプトを少し変えてwatch buildを行う方法を紹介します。

Chrome Extensionの開発に必要なフロー

Chrome Extensionを開発するには以下のようなフローが必要になります。

  1. アプリをビルドし、 build フォルダに出力する。
  2. Chrome拡張機能を開き、デベロッパーモードで build フォルダを読み込む。
  3. Chrome Extensionを実行する。
  4. ソースコードを修正する。
  5. アプリをビルドし、 build フォルダに出力する。
  6. 3に戻る。

create-react-appでnpm startを実行するとlocalhost:3000で開発サーバが実行されます。localhost:3000をそのまま開いても通常のWebアプリとして扱われるため、Bookmarks APIなどのChrome APIを実行できません。Chrome APIを実行するには上記の2が必要になります。

スクリプトのカスタマイズ

create-react-appは内部でWebpackの開発サーバを実行しています。これをwatchに変更します。

今回は以下の方針で修正します。

  • ejectして修正するとバージョンアップに追随するのが辛いのでやりたくない。
  • create-react-appが持っているWebpackの設定(webpack.config.dev.js)をなるべくそのまま利用したい。

まず、package.jsonを以下のように修正します。

  "scripts": {
    "start": "node scripts/start.js",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "zip": "zip -j - build/* > build/extension.zip",
    "clean": "rm -fr build"
  }

scripts/start.jsを作成します。

process.env.NODE_ENV = 'development';

var fs = require('fs-extra');
var paths = require('react-scripts/config/paths');
var webpack = require('webpack');
var config = require('react-scripts/config/webpack.config.dev.js');

// removes react-dev-utils/webpackHotDevClient.js at first in the array
config.entry.shift();

var compiler = webpack(config);
compiler.watch({}, function(err, stats) {
  if (err) {
    console.error(err);
  } else {
    copyPublicFolder();
  }
  console.error(stats.toString({
    chunks: false,
    colors: true
  }));
});

// as react-scripts/scripts/build.js
function copyPublicFolder() {
  fs.copySync(paths.appPublic, paths.appBuild, {
    dereference: true,
    filter: file => file !== paths.appHtml
  });
}

ちょっと雑ですが、create-react-appが持っているWebpackの設定を一部改変してwatchに渡すことで継続的にビルドを実行できます。

これでChrome Extensionの開発に必要な下表のタスクが実行できるようになりました。

コマンド やること
npm start ファイルが変更される度にdevelopmentでビルド
npm build productionでビルド
npm zip ビルドをZIPファイルに固める

ご参考まで。

Jenkinsfileによるジョブ管理のメリットと実例

ジョブの設定をJenkinsfileで管理し始めてから3か月ぐらい経ったので、知見をまとめてみます。

Jenkinsfileを使うメリット

Jenkinsの画面でジョブを管理していると以下のような問題が起きることが多いと思います。

  • 誰かが勝手にJenkinsの設定を変更して動かなくなった
  • ジョブ設定を別リポジトリに横展開したいけど、ポチポチ設定するのが面倒

JenkinsfileをGitで管理することで、以下のメリットがあります。

  • いつ、誰が、なぜジョブ設定を変更したのか後から調べられる
  • Pull Requestでジョブ設定の変更をレビューできる
  • ブランチを使ってジョブ設定を試行錯誤しやすい

Jenkinsの運用ポリシー

前項のメリットを実現するには、Jenkinsを以下のポリシーで運用することが望ましいでしょう。

  • Jenkinsの設定は最小限に抑える
  • なるべく画面からジョブ設定を変更せずに済むようにする(GitHub OrganizationやMultibranch Pipelineを利用)
  • なるべくJenkinsにログインしなくてもオペレーションが回るようにする(ビルド結果をチャットに通知)
  • Jenkinsfileをポータブルにする(認証情報を書かない、Jenkins Agentをイミュータブルにする等)

Jenkinsfileのテンプレート

どの言語でも共通のテンプレートを用意しておくと便利です。以下のようなテンプレートを利用しています。

node {
  try {
    checkout scm

    stage('build') {
      try {
        // TODO: ビルドを実行する
        // TODO: チャットにビルド成功を通知する
      } finally {
        // TODO: テスト結果を収集する (junit, publishHTMLなど)
      }
    }
  } catch (e) {
    // TODO: チャットにビルドエラーを通知する
    throw e
  }
}

ビルド結果はチャットの専用チャンネルに通知するようにしています。ただし、masterブランチのビルドエラーが起きた場合はすぐに直す必要があるので普段のチャンネルに流すようにしています。

  } catch (e) {
    // TODO: チャットにビルドエラーを通知する
    if (env.BRANCH_NAME == 'master') {
      // TODO: チャットにmasterが壊れたと通知する
    }
    throw e
  }

GradleでJVM言語をビルドする

GradleでJava、Groovy、Kotlinなどをビルドする場合は以下のようになります。

node {
  try {
    checkout scm
    sh 'chmod +x gradlew'

    stage('check') {
      try {
        sh './gradlew check'
      } finally {
        // TODO: テスト結果を収集する
        junit allowEmptyResults: true, testResults: 'build/test-results/test/*.xml'
        publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: false, reportDir: 'build/reports/jacoco/test/html', reportFiles: 'index.html', reportName: 'Coverage'])
        publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: false, reportDir: 'build/reports/tests/test/html', reportFiles: 'index.html', reportName: 'Test'])
      }
    }

    // TODO: チャットにビルド成功を通知する
  } catch (e) {
    // TODO: チャットにビルドエラーを通知する
    throw e
  }
}

ビルド実行時の情報を使う

ビルド実行時に取得できる情報は、JenkinsのGlobal Variable Referenceで確認できます。例えば、 env.BRANCH_NAME でブランチ名を取得できるので、masterブランチの場合にのみ特殊な処理を行うといったことも簡単にできます。

    if (env.BRANCH_NAME == 'master') {
      // masterブランチの場合はSonarQubeの静的解析を行う
      sh './gradlew sonarqube'
    }

また、パスワードやアクセストークンのような認証情報はJenkinsで管理し、ビルド実行時に注入させることができます。

withCredentials([usernamePassword(credentialsId: 'xxx', passwordVariable: 'AWS_SECRET_ACCESS_KEY', usernameVariable: 'AWS_ACCESS_KEY_ID')]) {
  // S3にリリースする
}

まとめ

本稿ではJenkinsfileでジョブ設定を管理するメリットを説明し、Jenkinsfileの実例を紹介しました。