Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

InclusiveなOSS開発体制を作る意味で、GFWの影響をざっくり知ってもらえるとありがたい

TL;DR

2021年5月時点では raw.githubusercontent.comgcr.ioはGFW内からアクセスできません。 開発しているOSSを広いユーザに利用してもらいたい場合は、こうしたドメインへの強い依存は避けることを検討しても良いかもしれません。

中国インターネットから見たOSS開発体制

中国本土でOSS開発をしていると、以下のようにバッジが壊れているGitHubリポジトリによく遭遇します:

f:id:eller:20210520191050p:plain
バッジが表示されないGitHubリポジトリの図

これはバッジが使っているドメイン raw.githubusercontent.com にアクセスできないことから生じます。バッジなら大して影響ないのですが、開発体制構築がこれに依存していると厄介です。 例えばYarn v2のインストール手順は、JSONファイルを raw.githubusercontent.com からダウンロードするためにうまく実行できません。このためGitHub Issuesでユーザ側による回避策の適用が推奨されています:

github.com

GitHubが中国本土からアクセス可能なのは有名な話だと思いますが、それでもこうした一部ドメインへの制限があったり、Web画面にもたまにアクセスできなかったりもします。

同様に gcr.io もアクセスできません。Jibがかつて標準で利用していたベースイメージであるDistrolessのダウンロードが行えません。ただJibが利用するベースイメージはv3からDocker Hubに置いてあるAdoptopenjdkに変わったので、だいぶ使いやすくなりました。

試してませんがStackoverflowの投稿によるとfirestoreも一癖あるっぽいですね。

どのような回避・緩和策があるのか

GFW内からもアクセス可能なドメインを使う

バッジやJSONファイルを配布する場合、2021年5月時点では shields.ioGitHub Pages、Netlifyなどで代用が可能です。 おそらく最も運用が軽い対策になると思います。

2021年5月26日追記:GitHub Releasesに添付されたファイルもダウンロード可能でした。

ミラーサイトを用意する

Mavenやnpmなどのパッケージダウンロードが遅い場合は、中国国内に大手が提供するミラーサイトが利用できます。通信自体は可能なので必須ではありませんが、開発体験を向上できる場合があります。

maven.aliyun.com github.com

このように、ミラーを用意してそちらを使ってもらう手は一応あります。ただ個人的にはXcodeGhostの1件があったので、ミラーの利用をコミュニティとして推奨するのは避けたいなぁとは思っています。少なくとも公式サイトからチェックサムを確認できるようにする必要があるかと。なおGradleはv6から依存のチェックサムを自動的に検証できるようになりました。

hostsを書き換えてもらう

以下のように、中国語ブログでよく紹介されているのがこれです。詳細は割愛。

www.cnblogs.com

まとめ:そんなに気を使わなくても良いかも

こういうことを気にできると確かにInclusiveOSS開発体制の構築には貢献すると思いますが、今見たらant-designも普通に raw.githubusercontent.com を使っていたので、気にしなくても問題ないレベルかもしれません。

JavaのRecordでは配列を使わないほうが良いという話

配列使うとmutableになるから使うべきではない、というのに加えて。生成される hashCode()equals(Object), toString() が配列を考慮しない実装になっているため、JavaのRecordでは配列を使わないほうが良いようです。

検証コード

生成される hashCode()equals(Object) の挙動を検証するコードです。

gist.github.com

なぜこうなるのか

ObjectMethods クラスによって生成されるコードを見ると、 Arrays.hashCode(int[]) ではなくint[].hashCode()が、Arrays.equals(int[], int[]) ではなく int[].equals(Object) が使われているようです。これは考慮漏れというわけではなく、ObjectMethodsクラスのjavadocに明記されている仕様です。core-libs-devメーリスに質問を投げて確認しました

実際の実装はObjectMethodsクラスのコードを読むか、以下の記事を参照ください。

alidg.me

望ましい対策

Recordでは配列の代わりに不変コレクションを利用するのが望ましいと思われます。Java 9からimmutable listを作るfactory methodが導入されています。また不変であることを型として示したいならば、GuavaのImmutableListを使う手もあります。

Recordクラスがmutableになるリスクを受容した上で配列を使う場合は、hashCode()equals(Object), toString() を自動生成に頼るのではなくハードコードすることで、この問題を回避可能です。大抵のIDEにはこれらのコードを自動的に生成する機能が備わっているので、利用すると良いでしょう。

オンラインコミュニティに参加してるまとめ

自分のキャリアを考えたときに、FOSSエンジニアであることの比重が高いわりにプレゼンスないんだよね……ということに課題を感じたので改善策を考えてみた。LTやセッションを増やしたりcontributeするプロジェクトを増やしたりするのも必要だけど、他にも継続的にオンラインの知り合いを増やせる・議論の機会を得る手段がほしいと思い、オンラインコミュニティにいくつか参加し始めているところ。

参加コミュニティ

Codacy Community

OSSの静的解析ツールを複数組み合わせて統一されたUIで解析結果を伝えるSaaS、のコミュニティ。Twitterで見かけてDiscourseに参加。

SonarQubeやLGTMのような自社解析エンジンは持っていないが、CEOが静的解析で修士とった人だったし社員ぽい人もSpotBugsにPR送ってくる。のでマニアが集まるのではと期待してDiscourseに静的解析を学ぶ良い方法について話題を投げたけど反応薄いのが今。

SonarSource Community

前から参加してたDiscourseをより頻繁に見に行くようにした。私はコミュニティ担当の方とどうも反りが合わないようで、PRで話が噛み合わないとか返信を何週間も待たされたりとか、どうも見る気になれないのだけど。

どこにも買収されてない&自社で技術を持っている数少ない企業だと思うので、今後も注目していきたい。

Gradle Community

Slackがあるのに最近気づいて参加。Discourseでもいいけど、Configuration CacheとかWorker APIとかわりと新しめの話題について質問するにはSlackのほうが反応早いかもしれない。

GradleはGitHub Issuesでコミュニティ発のプラグインの機能について俯瞰状況をまとめてたり活発なユーザを称えるFellowship Programを初めていたりと周りを巻き込もうという意気を感じる。のはいいんだけどYoutubeチャンネルのノリがよくわからない。アイスブレイクのつもりなんだろうけどバラエティ番組みたいになっていたりする。

Write the Docs

コミュニティの蓄積がすごい。ツールの話はもちろんテクニカル面についてもまとまってそう。L10nについては特にまとまっていないので、Slackチャネルでおすすめのツールを聞いてみている。ドキュメンテーションツールもそうだが翻訳メモリなどについても話していきたい。

API the Docsというオンラインカンファレンスが近いようなので覗いてみるつもり。

SIGPX

DX: Developer Experience (開発体験)は重要だ - Islands in the byte stream で知り、PXは静的解析ツールとの親和性高いのではと思って第8回勉強会に以下のスライドで参加。静的コード解析そのものについて扱っている方はいらっしゃらなかったが、IDE周りというかMagpieBridgeLSPについて掘り下げて話すと良いかもと感じた。

speakerdeck.com

Java-jp Discord

ゆるゆる参加している。

twitter.com

コミュニティの更新を追う方法

モバイルアプリで追えるようにして、通勤中などのスキマ時間にぱぱっと済ませられるようにしている。

Discourse

公式モバイルアプリがあり、複数サイトを横断的に管理できる。今まではメール通知に頼っていたけどこちらのほうが早い。

Slack

Slackアプリが複数ワークスペース管理をサポートしている。各ワークスペースに同一のメールアドレスでサインインするようにすれば特別な配慮は不要。

Discord

こちらもSlackと同じ。

まとめ

はじめたばかりなのでまとまらない。情報発信は得意な方なはずなので継続していきたい。

静的解析ツールまとめ2021年春

静的解析ツールで飯を食えないか定期的に気になるタチなので有償サービスなど調べている。のをまとめる。主にJava/JavaScript周り。

Name Download Star 開発企業など
SpotBugs 482k/mo 2.2k なし
PMD - 3.3k なし
Stylelint 10m/mo 8.5k なし、Open Collectiveはある
SonarQube - 5.6k SonarSource
Codacy - - Codacy
CodeClimate - - CodeClimate
LGTM - - GitHub

有償サービス展開している企業は、どこも(国境を超えた)リモートでの就業はやってない。

選外

coveralls
カバレッジ出すSaaS。他のSaaS機能で代替できることが多そう。
snyk
依存に含まれる脆弱性を見つけるSaaS。DependabotやSonatypeと競合?

外国人永久居留証を取得しました

外国人永久居留証の申請条件を満たしたので、2020年9月25日付で外国人永久居留証を申請し、半年後の2021年2月25日に受け取りました。

どういうカードなのか

俗に言う中国版グリーンカードですが、だいたい以下の恩恵があるようです。

  1. 就労許可・居留許可を継続的に取り直す必要性がなくなる。有効期限は10年。毎年3ヶ月は中国に滞在する必要あり。
  2. 投資に制限がなくなる、らしい。
  3. 行政手続で段階的に使えるようになる、中国人レーンや住宅公共積立金を使えるようになるなどの計画があるらしい
  4. コロナ禍環境下においても入境が可能とされている(2020年11月27日現在)。無ければ入境できないというわけではない。

正直なところ、現状あまりメリットはありません。オンラインサービスで身份证代わりに使えるわけでもないですし、銀行や行政での手続にも引き続きパスポートが必要だという話も聞きます。2020年から鉄道もパスポートで乗れるようになっているので、会社に就労許可・居留許可の更新を代行してもらえる駐在員としてはさほど利点はない感じです。

ただコロナ禍環境下でも入境がしやすいはずという期待と、ICチップが入っているのでいずれ第二代身份证のシステムの環に入れるのではないかという期待、また会社に頼らず家庭環境を維持できるという生活の安定性向上の観点から申請に臨みました。

これから申請する人へのアドバイス

上海での行政手続というと、情報が錯綜しているとか担当者によって言うことが違うというイメージでしたが、今は既に上海公安出入境管理のサイトで申請に必要な書類や手続が明確化されているため、大きな混乱はありませんでした。いくつか情報を補足すると:

  1. 上海総領事館で発行する婚姻証明書単体では証明として不適とのことで、婚姻証明書の発行に使った戸籍謄本のコピー2部とその翻訳文を合わせて提出する必要がありました。窓口で翻訳すると手数料100元。
  2. 上海総領事館経由で発行する無犯罪証明は自分では開封できないのですが、翻訳文もまた必要なため、申請当日に窓口で開封してもらいその場で業者に翻訳を依頼する必要がありました。手数料150元。
  3. 手続きはサイトに記載のとおり、学林路の窓口で行う必要がありました。以前は民生路だったようですが移転しています。
  4. 手数料1,800元は、申請時ではなく居留証受け取り当日に現金で支払う必要がありました。
  5. 写真は白背景の印刷したものを2枚のみ、データでの提出はありませんでした。当日窓口でも別に撮影があります。
  6. 「有效签证(或居留证件)」の原本とコピー提出をもとめられますが、居留許可カードのことではなく、パスポートに貼られたビザをコピーする必要があります。
  7. カードに中文姓名を併記するか英文姓名のみにするか選択できました。
  8. 申請時にバーコード付きの書類を渡されます。受取など後続の手続きにはこの書類の持参が必要です。

申請のクリティカル・パスは無犯罪証明です。申請から届くまできっちり2ヶ月かかりました。また婚姻証明のために日本から戸籍謄本を取り寄せる必要もあります。この2つを早めに開始するべきでしょう。

健康診断は2週間前から申込可能、1週間強で受取可能。公証は申請してから1週間で受取可能。銀行での預金凍結は即日でできます。户口本の名称変更(英文に訂正する必要あり)も、配偶者の戸籍がある場所の公安で即日でできました。配偶者の協力が必須なのは住居に関する公証と、户口本の名称変更、そして申請当日に身分証やパスポートを預かるのみです。配偶者の勤め先の名前も控えておくと良いと思います。

参考

Gradle Plugin実装の基本

Gradleは動きが早いのでTIPSを文書化する意味があまりないのですが、全体像をざっくりつかめるだけでも変化を追うには有用のはず。 そのうち体裁まとめてZennに置いたほうがいい気がしてますが、一旦走り書きということで。

Gradleの考え方

設定と実行の分離

キャッシュを効かせるためにも、Configuration Cacheとかの文脈で、設定フェーズと実行フェーズは分けて考えることを推奨する動きがあるっぽい。 実行フェーズに設定を変えるなとか、afterEvaluate { ... } に過度に依存した設定を書くなとかを気にすればいい。

設定の遅延

設定より規約

能力と慣習の分離

登場人物

プラグイン実装に必要な登場人物は、だいたい3つほど頭に入れれば良いと思われる。

  1. Plugin
  2. Extension
  3. Task

他には依存を扱うConfigurationとかProjectにメソッドやプロパティを追加するためのConventionなどもある。ConventionはExtensionで代替すべきという情報もCommunity Forumにはあったので置いといて良いかも。

Plugin

プラグインを適用した際、真っ先にインスタンスが作成されるクラス。このPluginによってまず必要な初期化処理を行う。Gradleのバージョン確認もここ。 基本的にはファイル作成やタスク作成といった重い処理は行うべきではない。適用しただけで重くなるようなプラグインになってしまうので。

また"能力と慣習の分離"の観点から、機能を提供する部分をbase pluginとして慣習・規約を扱う部分から独立して定義することが望ましい。 BasePlugin, ReportingBasePluginなど標準で用意されたbase pluginもあるので、適宜 project.pluginManager.apply(BasePlugin) などとして利用できる。

Extension

Property-like fieldsを持つPOJO。 ExtensionもPlugin同様、インスタンス生成コストは抑える方が良い。これはプラグインの提供する機能(タスク)を使わない場合でもExtensionは作成されることが多いため。

Extensionではプロジェクト内で実行されるTaskすべてに共通の設定を行うことになるはず。

Task

主にDefaultTaskを拡張Property-like fieldsを持つ。入力と出力を宣言することで、キャッシュ可能にできる。

実行のコストは重くして良い。不要なTaskは生成されない・実行されないようにビルドスクリプトを書くことがユーザには推奨されている。 同じプロジェクトに属するTaskは同時実行されない=後続Taskをブロックするので、重い処理はWorker APIに逃がすことも検討する。

lazy config をどう実現するか

PropertyFileCollection に代表されるProperty-likeなクラスを活用する。Extensionのプロパティのconventionをデフォルト値に、TaskのプロパティのconventionをExtensionのプロパティにする。ちょっと古いプラグインだとconvention mappingというのを使っているがinternal APIなので無視で良い。

// ExtensionはPOJOにする。生成コストを低く保つ。
// 抽象クラスにすることでProperty-likeフィールドの生成処理を省略できる(ドキュメント未確認)
abstract class MyExtension {
  abstract Property<String> getFoo()
}

// TaskもProperty-likeフィールドを持つ。@Inputや@Output,@Internalなどの修飾子を使ってキャッシュ可能にする
// やはり抽象クラスにすることでProperty-likeフィールドの生成処理を省略できる
@CacheableTask
abstract class MyTask extends DefaultTask {
  @Input
  abstract Property<String> getFoo()

  @TaskAction
  void run() {
    print this.foo.map { "The value of foo property is ${it}" } .get()
  }
}

// base pluginはextensionの登録やGradleのバージョン確認など、機能実行の前準備のみ実行。規約(慣習)は扱わない。
class MyBasePlugin implements Plugin<Project> {
  @Override
  public void apply(Project project) {
    def extension = project.extensions.create("myExtension", MyExtension)
    project.tasks.withType(MyTask).configureEach {
      // 規約に関係ないデフォルト値はここで設定して良いはず
      it.foo.convention(extension.foo)
    }
  }
}

// こちらのpluginでは規約(慣習)を扱う。これはsourceSetごとにTaskを作る例
class MyPlugin implements Plugin<Project> {
  @Override
  public void apply(Project project) {
    project.pluginManager.apply(MyBasePlugin)

    project.plugins.withId('java-base') {
      def convention = project.convention.getPlugin(JavaPluginConvention)
      convention.sourceSets.all {
        def name = it.getTaskName("foo", null)
        project.tasks.register(name, MyTask) {
          // 必要な設定を行う
        }
      }
    }
  }
}

VerificationTask のようなインタフェースもあるので適宜実装すると、他プラグインとの整合性を取りやすい。

ドキュメントに目を通しても迷ったら

「ドメイン駆動設計入門」付録のGradle向け解釈

ITエンジニア本大賞2021で紹介されていた「ドメイン駆動設計入門」(以下、本書と呼ぶ)が、DDDを学ぶ上でわかりやすかったです。一応他のDDD本も数冊読んではいたのですが、どうしてもユビキタス言語や境界づけられたコンテキストなど”場合による”ものが頻出してしまい、結局どうすればいいんだ……となって手が動きにくかったのです。よくわからない概念の上にさらにわからない概念を積み重ねるので、どんどん混乱する感覚がありました。

反面、本書では副題の通りボトムアップ形式を採っているため、実際にどう手を動かせば良いのか細かく確認できました。また不明点を最小化しながら進むだけでなく、その概念を導入することでどんな問題が解決されるのかが実例をもって明示されており、私のような独学派にはとてもありがたかったです。

さて本書ではC#を使っています。Javaとたいして変わらないとはいえ細かいところは違ってきますので、ここでは付録で紹介されたソリューション(プロジェクト)構築方法をGradleユーザ向けに解釈したものを紹介します。なおウェブアプリケーションフレームワークとしてspring-bootを利用するものとしますが、他のフレームワークでも大差ないはずです。

サブプロジェクトによる分割

Mavenではサブモジュール、Gradleではサブプロジェクトを使ってプロジェクトを分割します。本書ではプロジェクト分割の方針を2つ紹介していますが、ここでは簡単のためすべてのレイヤが固有サブプロジェクトを持つ構成を説明します。

domainサブプロジェクトには値オブジェクトやエンティティ、ドメインサービス、仕様やリポジトリインタフェースを配置します。ほぼPOJOで済むため、依存はspring-contextのようなアノテーションのみとなるでしょう。

ドメインオブジェクトの振る舞いの多くがここに配置されるため、Javadocやunit testを優先的に充実させる必要があるでしょう。また equals()hashCode()を手書きするとコードやテストカバレッジの見通しが悪くなることから、Immutablesのようなコードジェネレータによって対策することも考えられます。

infrastrutureサブプロジェクトはdomainサブプロジェクトに含まれるリポジトリインタフェースの実装を配置します。このサブプロジェクトはJDBCドライバのような依存先ミドルウェア固有の依存を持ちます。productionで利用する実装に加え、オンメモリで動作する実装も別途用意することで、unit testを書きやすくできます。

このサブプロジェクトではクラスをpublicにする必要はありません。Springが自動的に@Repositoryで修飾されたクラスを見つけてインスタンスを作るためです。他サブプロジェクトのunit testから呼び出す必要がある場合のみ、publicにすると良いでしょう。

applicationサブプロジェクトはdomainサブプロジェクトとinfrastructureサブプロジェクトの双方に依存します。アプリケーションサービスによるユースケースの記述をメインとするサブプロジェクトです。

本書の方針に従うならば、このサブプロジェクトに含まれるクラスやメソッドのAPIにはドメインオブジェクトを露出させないことが望ましいと言えます。このため他サブプロジェクトには api configuration ではなく implementation configuration を使って依存します。これによりドメインオブジェクトの露出をコンパイルエラーとして発見できる可能性が上がります。詳細は後述します。

presentationサブプロジェクトはapplicationサブプロジェクトにのみ依存します。後述するGradleの機能により、presentationサブプロジェクト内ではドメインオブジェクトやRepositoryの実装に触れない状態を担保できます。MockMvcなどを使うことでunit testが重くなり、またこのサブプロジェクトをビルドしている際はGradleのworkerが空きがちなので、maxParallelForksオプションでテストを並列実行することの恩恵が大きいでしょう。

apiとimplementationの使い分け

ここまでで2つ、詳細を後述すると述べたことがあります:

  1. presentationサブプロジェクトに対するドメインオブジェクトの露出をコンパイルエラーとして発見する方法
  2. presentationサブプロジェクト内ではドメインオブジェクトやRepositoryの実装に触れない状態を担保する方法

これらは表現こそ異なりますがひとつの課題を示しています。すなわち「ドメインオブジェクトをpresentationサブプロジェクトに露出させたくない」です。 もちろん依存先のpublicフィールドや戻り値がドメインオブジェクトでないことをArchUnitなどで確認しても良いですが、Gradleならこれらをコンパイルエラーとして発見する方法があります。

API and implementation separationがこの課題に対する解決になります。これはAPIとして外部に露出している依存と、そうではない内部利用している依存とを明確に分けて定義するものです。つまりサブプロジェクトが内部利用している依存は、そのサブプロジェクトの利用者に対して開示する必要はないという考えです。

これにより、applicationサブプロジェクトが依存しているdomainサブプロジェクトやinfrastructureサブプロジェクトを、presentationサブプロジェクトのコンパイル時クラスパスに含めずプロジェクトをビルドすることができます。 依存に色を付ける必要があるという意味で手間は増えますが、そもそもAPIとして露出している依存は減らすべき(JLBP-2)という話もありますので”依存がAPIとして露出しているのか?”を意識することは有用です。Gradle公式ドキュメントによるとコンパイルのパフォーマンス改善も期待できるケースがあるそうです。

例えば以上のサブプロジェクトの依存関係を図示すると、以下のようになります。

f:id:eller:20210131174527p:plain
com.savvasdalkitsis.module-dependency-graph によって生成

api configurationを使うのは1箇所だけです。infrastructureサブプロジェクトに含まれるクラスはdomainサブプロジェクトに含まれるリポジトリインタフェースを実装しており、またドメインオブジェクトをその引数や戻り値に使っているため、api configurationを使って依存します。その他はすべて implementation configurationが使えます。

補足: アセット生成を独立したサブプロジェクトの責務とする理由

前出の図に含まれているfrontendサブプロジェクトは、HTMLやJSなどのアセットを生成するためのものです。ドメイン駆動からは外れますが解説します。

このサブプロジェクトだけ若干特殊で、npmやyarnのプロジェクトをfrontend-gradle-pluginを通じてビルドする作りになります。ReactやVueの開発をGradleで無理やり実現するのではなくnpmあるいはyarnを呼び出す形を取ることで、JavaScript開発の知見を無理なく導入するとともにフロントエンド開発者にGradleやJavaの学習を強要する必要性を減らせます。

ちなみにビルド性能の向上も期待できます。これはfrontend-gradle-pluginのタスク、特にinstallFrontendタスクが重いことと、Gradleは異なるプロジェクトに属するタスクだけparallel buildできることから、説明できます。なおプロジェクトと同数以上のworkerがいないと性能が出ないので、--max-workersオプションを使うかorg.gradle.workers.maxプロパティを使って明示的にワーカー数を引き上げておくことが望ましいです。以下に手元の環境でhyperfineした結果を貼っておきます:

Benchmark #分割前: ./gradlew clean build
  Time (mean ± σ):     28.356 s ±  2.624 s    [User: 1.419 s, System: 0.145 s]
  Range (min … max):   25.914 s … 33.624 s    10 runs

Benchmark #分割後 (4 workers): ./gradlew clean build
  Time (mean ± σ):     27.161 s ±  3.987 s    [User: 1.388 s, System: 0.137 s]
  Range (min … max):   23.202 s … 33.721 s    10 runs

Benchmark #分割後 (5 workers): ./gradlew clean build --max-workers=5
  Time (mean ± σ):     24.442 s ±  2.916 s    [User: 1.321 s, System: 0.135 s]
  Range (min … max):   21.799 s … 31.388 s    10 runs

まとめ

ドメイン駆動設計入門」付録のGradle向け解釈を述べました。本書は私のような、ドメイン駆動を手を動かしつつ独学したい方におすすめします。

本投稿が本書の内容を実践する上でGradleユーザの参考になれば幸いです。またGitHubに私の書いたプロジェクトを置いてありますので、具体的にプロジェクト設定を見てみたい方は参照ください。