TypeScriptで配列をn個ずつに分割する

配列をn個ずつの二次元配列にするワンライナーです(Rubyeach_slice 的なやつ) 使い古されたネタなので、いろんな書き方が可能だと思いますが…。

const arraySplit = <T = object>(array: T[], n: number): T[][] =>
  array.reduce((acc: T[][], c, i: number) => (i % n ? acc : [...acc, ...[array.slice(i, i + n)]]), []);

使い方はこんな感じ。

const result = arraySplit<number>([1, 2, 3, 4, 5], 2); // <- [[1, 2], [3, 4], [5]]

JS版として、以下のQiita記事にあるコメントを参考にさせていただきました(実質、それをTypeScript化した形です)

qiita.com

AWS CDKをプロダクションで使うための私見プラクティス

AWS CDK (Cloud Development Kit)の登場によって、ようやく理想的なInfrastructure as Codeが実現できるようになってきたなと感じています。
素で書くCloudFormationテンプレートやAnsible等と比べて、特に以下のあたりが便利ですね。

  • 型があり、補完が効く
  • ロール、ポリシー周りの記述がgrantXxxだけで直感的に記述できる
  • 変更に伴う変更セット周りの面倒くささが解消される

CDKをプロダクションで使っていくために、「こうするといいんじゃないかな」と思う個人的なプラクティスをまとめてみました。

(注) CDKのバージョンは執筆時点での最新バージョンである1.19.0を対象としています。
また、言語は問わない内容にしたつもりですが、TypeScriptしか触っていませんので、TypeScriptが前提です。

書き方はサンプルリポジトリを参考にする

まず、プロジェクトの構成は cdk init app で作られたものを土台にし、下手に弄らないほうが無難だと思います。
この構成だと、lib 以下にスタックの定義を記述していくことになると思います。

公式のサンプルリポジトリに豊富なサンプルがあるので、近しい構成のものを参考にしながら、膨らませていくとよいと思います。

github.com

書けるものはdescriptionを書く

Stack, Lambda, API等、説明(description)が記載できるものについては横着せずに、書いておくのがよいと思います。

const sampleStack = new SampleStack(app, 'SampleStack', {
  description: 'はじめてのAWS CDKスタックです'
});

マネージメントコンソールで一覧で見た時に用途が明確になりますし、コードのコメントのかわりとしても使えます。

~/.aws/credentialsのデフォルトプロファイルに注意

cdk deploy とすると、あまりに簡単にデプロイができてしまいますが、オプション未指定時は ~/.aws/credentials のデフォルトプロファイルが使われることに注意しましょう(事故の元になります)。

常にプロファイルを指定してデプロイするよう心がけたほうが安全だと思います。

$ cdk deploy --profie staging

コードを短くすることにこだわりすぎない

CFnテンプレートを書くのに比べて短い記述量で済むのがCDKのメリットの一つですが、記述を短くすることを最優先にせずに、バランスを考えた方がよい気がしています。

例えば、3つのLambda関数を作成する場合、以下のようにループを用いて書くことができます。

for (const name of ['fn1', 'fn2', 'fn3']) {
  new lambda.Function(this, name, {
    runtime: lambda.Runtime.NODEJS_12_X,
    code: lambda.Code.asset('src'),
    handler: `${name}.handler`
  });
}

多少冗長でも以下のようにありのままに書いた方がよい気がしています。直感的に内容が分かりますし、例えば、後で一部の関数だけメモリやタイムアウトの値を変えたくなった時に素直に対応ができます。
このあたり、DRYの原則に反しますが、テストコードを書く時の感覚に近いかもしれません。

new lambda.Function(this, 'fn1', {
  runtime: lambda.Runtime.NODEJS_12_X,
  code: lambda.Code.asset('src'),
  handler: 'fn1.handler'
});

new lambda.Function(this, 'fn2', {
  runtime: lambda.Runtime.NODEJS_12_X,
  code: lambda.Code.asset('src'),
  handler: 'fn2.handler'
});

new lambda.Function(this, 'fn3', {
  runtime: lambda.Runtime.NODEJS_12_X,
  code: lambda.Code.asset('src'),
  handler: 'fn3.handler'
});

Stackのコンストラクタだけで記述を済ませず、シンプルにする

独自のStackクラスのコンストラクタ内ですべての処理を記載しようとすると、モノによっては、やはり長くなってしまいます。
1つ1つのリソース定義はメソッドに抽出するなどして、コンストラクタでは、全体像がひと目で把握できるシンプルな構造になっていると、後のメンテナンスがしやすいと思います。

import cdk = require('@aws-cdk/core');
import sns = require('@aws-cdk/aws-sns');
import subs = require('@aws-cdk/aws-sns-subscriptions');
import sqs = require('@aws-cdk/aws-sqs');

export class SqsSnsStack extends cdk.Stack {

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const queue = this.createQueue();

    const topic = this.createTopic();

    topic.addSubscription(new subs.SqsSubscription(queue));
  }

  private createQueue(): sqs.Queue {
    return new sqs.Queue(this, 'HelloCdkQueue', {
      visibilityTimeout: cdk.Duration.seconds(300)
    });
  }

  private createTopic(): sns.Topic {
    return new sns.Topic(this, 'HelloCdkTopic');
  }
}

STABLEなものだけで構築できそうならば、CDKを採用する

CDKは新しいツールであり、2018年8月にDeveloper Previewとして発表された頃からGitHubをウォッチしていますが、アップデートが頻繁にあります。
まぁ、GAになってからは落ち着いた感じもしますが、それでも、今後、破壊的な変更がある可能性はゼロではないと思います。

公式のAPIリファレンスを見ると、各サービスごとに、CDKでの安定性(stability)ステータスが確認できます。例えば、Lambdaの場合は下記URLです。

https://docs.aws.amazon.com/cdk/api/latest/docs/aws-lambda-readme.html

ちょっと乱暴ですが、ステータスが実験的(EXPERIMENTAL)になっているものの利用は避け、安定(STABLE)なものだけで実現可能な場合にのみ、CDKを採用する、というのも手です。
使いたいオプションがサポートされていなかったり、という罠にハマることが少ないと思うので…(まぁIssueなりPullRequestを送ればよい話ではありますが…)。

まぁ、サーバレスなアプリケーションを作る定番サービス(DynamoDB, Lambda, API Gateway, SNS, SQS, etc)はいずれもSTABLEなので、多くのケースでは実戦で使えると思ってます。

Runtime Contextをうまく活用する

CDKにはContextという形で、パラメータ値をスタックに流し込むことができます。

https://docs.aws.amazon.com/cdk/latest/guide/context.html

うまく活用することで、デプロイ時の不測の変更を防いだり、環境(ステージ)間の差をコードから分離して管理することができるようになります。

参考記事

CloudFormationのベストプラクティスに基づいて使う

CDKは新しいAWSのサービスというわけではなく、実態はCloudFormationであり、CFnをプログラム的に扱えるようにうまく抽象化したものです。
そのため、CDKを使っていく上でも、CFnの知識は不可欠であり、CFnには成熟したベストプラクティスが存在するので、まずはそれを身につけた上で使っていきましょう。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html

GradleからbootRunを実行する時にコマンドライン引数でパラメータを渡したい

GradleからbootRun (Spring Bootアプリケーションの起動タスク)を実行する時に、コマンドライン引数でパラメータを渡す方法についてです。

注:古めのバージョン(Spring Boot 1.4.x, Gradle 3.x)で確認しているため、最新バージョンでは動作しない可能性があります。

build.gradleで指定する場合

はじめに、 build.gradle で指定する場合は以下のように書きます。

bootRun {
   jvmArgs  ['-Dspring.profiles.active=local']
}

コマンドライン引数で渡す場合

そもそもSpring Bootでは、環境変数やプロパティファイルなど、多様で柔軟な方式で外部設定値を与えることができます。
コマンドライン引数もそれらの選択肢のうちの一つであり、プロパティファイルを書き換えたり環境変数を指定するまでもないようなケースで便利(お手軽)です。

いきなり結論ですが、コマンド実行の際は以下のようにします。

$ ./gradlew bootRun -PjvmArgs="-Dspring.profiles.active=local"

-P はプロジェクトプロパティを設定するGradleのオプションです。

合わせて、 build.gradle も以下のようにします。

bootRun {
    if (project.hasProperty('jvmArgs')) {
        jvmArgs project.jvmArgs.split('\\s+') as List
    }
}

ハイブリッド版

build.gradle でデフォルト値は決めつつ、コマンドライン引数で追加の設定を与えたいような、上記2つのハイブリッド版です。
コマンド実行方法は同じなので省略します。 build.gradle をちょっと書き換えるだけでOKです。このあたりの柔軟性はやりすぎ注意ですが、やはりGradleは便利ですね。

bootRun {
    def myJvmArgs = ['-Dfoo=aaa']
    if (project.hasProperty('jvmArgs')) {
        myJvmArgs.addAll(project.jvmArgs.split('\\s+') as List)
    }
    jvmArgs myJvmArgs
}

CodeBuildの標準イメージ(マネージド)上でdocker-composeを使ってRDBやRedisを利用する

CodeBuildの標準イメージで、docker-composeが使えるようになっていました。

docs.aws.amazon.com

でそれぞれ使えそうです(リンク先はGitHub上のDockerfile)。

これまでは、MySQLやRedisなどが絡んだテストを実行したい場合、ビルドのフェーズ内でパッケージをインストールする or 独自のイメージを用意する等の対応が必要でした(よね?)。
今では、日常的な開発環境構築に利用する docker-compose.yml をそのまま使って docker-compose up -d するだけで、使い捨てのビルド環境が構築できるということですね。

試してみて気づいた細かい注意点などをメモしておきます。

UbuntuAmazon Linuxの違い

次に挙げるように、パッケージマネージャやJavaのランタイムの指定の違いがあり、共通の buildspec.yml でどちらのOSにも対応するのはちょっと難しいかもしれません。
もっとも、どちらかのOSに対応していればほとんどのケースでは十分だと思いますが…。

パッケージインストール用のコマンド

CodeBuildの buildspec.yml を書く上では、パッケージマネージャが異なることは気をつける必要があります。

Ubuntu の場合は apt / apt-get, Amazon Linux の場合は yum を利用します。

runtime-versions で指定できるjavaの種類

aws/codebuild/standard:2.0 を使っている場合、 buildspec.yml 中で runtime-versions を指定する必要があります。

docs.aws.amazon.com

未指定の場合、以下のようなエラーが発生します。

YAML_FILE_ERROR Message: This build image requires selecting at least one runtime version.

例えば、ランタイムにJavaを使いたい場合は、以下のように指定します。

phases:
  install:
    runtime-versions:
      docker: 18
      java: openjdk8

ここで指定できるJavaの値ですが、OSによって次のような違いがあるみたいです(correttoはAWSの OpenJDKディストリビューションですね)

OS 指定可能な値
Ubuntu openjdk8, openjdk11
Amazon Linux corretto8, corretto11

coretto の指定について、(この記事を書いている時点で)日本語のドキュメントには書かれていませんが、英語のドキュメントには書かれています。

Runtime Versions in Buildspec File Sample for CodeBuild - AWS CodeBuild

高速化のためのローカルキャッシュの利用

ビルド時間の短縮は常に重要な関心事です。素直に作ったビルドジョブにおいては、ビルドの時間を大きく占めるのが、Docker イメージや依存関係のあるライブラリのダウンロードでしょう。
2017年の末頃からS3上へのキャッシュはサポートされていましたが、今ではローカルキャッシュも利用できます。ネットワーク越しでないため、2回目以降のビルドがさらに短縮できることが期待できます。

docs.aws.amazon.com

JavaでGradleを使っている場合は、

cache:
  paths:
    - '/root/.gradle/**/*'

と指定しておけばOKだと思います。
ちなみに、ローカルキャッシュに関するサイズの上限や有効期間については、AWSドキュメント上では言及がありません。キャッシュがあってもなくても、ビルドが安定するような作りにしておくことが大切ですね。

あとは、Docker18.09.0からサポートされたBuildKitなどを有効化してみるのも効果があると思います。

GitHubで2FAを有効にした時にgit pushでAuthentication failedや403エラーになった時の対応

GitHubで途中でアカウントの2FAを有効にした場合、今まで使っていたリポジトリgit push origin xxx を実行すると、

remote: Invalid username or password.
fatal: Authentication failed for 'https://github.com/hoge/some-repo.git/'

とか、

fatal: unable to access 'https://github.com/hoge/some-repo.git/': The requested URL returned error: 403

といったエラーに遭遇します。
その時の対応法です。

解決法

  1. 新しくPersonal access tokensを発行する
  2. .git/configに記載されている urlhttps://{account name}@github.com/hoge/some-repo.git の形式にする
  3. 初回push時にパスワードを聞かれるので、1.で発行したPersonal access tokenを入力(ペースト)する

参考

Spring × SpockのテストでsetupSpecにAutowiredされたコンポーネントを使いたい

今のプロジェクトではJUnit5よりも好んでSpock (Groovy)を使ってます。

Spring Boot製アプリケーションのテストを行う際に、 setupSpec (テストクラス内で一度きりの初期化)メソッド内で、 Autowired されたコンポーネントを使いたかったのですが、使えなかったので、その対応方法のメモです。

まずは前提ですが、

  • spring-spockはなるべく最新のバージョンを使いましょう
  • Spockのテストクラスには @RunWith(SpringRunner)つけない ようにしましょう

失敗ケース

  @Shared
  @Autowired
  HogeService hogeService

  def setupSpec() {
      hogeService.doSomething()
  }

成功ケース

  @Autowired
  HogeService hogeService

  @Shared
  boolean isInitialized = false

  def setup() {
      if (isInitialized) {
          return
      }

      hogeService.doSomething()
      isInitialized = true
  }

というわけで、 setupSpec を使うのをやめて、setup(テストメソッド側で毎回呼ばれる処理)に移しました。 ちなみに、 isInitialized を使った一連のコードは、なくても(テストの成否に)影響はないのですが、 hogeService.doSomething が1回だけ行えばよいような初期化処理であり、テストケースの数が多い場合、実行時間の面で有利です。

参考

MacでGitのバージョンを上げる

  1. brew upgrade git → これだけだと反映されず…
  2. sudo vim /etc/paths で、/usr/local/bin を先頭行に移動
  3. ターミナル再起動
  4. git --version で最新バージョンに上がっていることを確認