Visual Studio Codeを使いはじめた

今までテキストエディタSublime Textを使っていたのですが、Visual Studio Codeを使い始めました。まだ使い始めたばかりですが、好感触なので、近いうちに完全に乗り換えようと思います。

感想

デフォルトでの使いやすさ

インストールした段階で、ほぼほぼエディタとして快適に書ける環境がそろっている印象です。
まぁ元々、テキストエディタについては、快適に書ける・読めるための最低限のチューニングしかしてなかったというのもありますが…。
Sublime Textでいう「Package Control」的なものも、個別インストールをすることなく最初から使えます。あと、デフォルトのカラーテーマ「Dark+」もいい感じです。

あと、Sublime Textとの比較でいうと、日本語入力も普通に使えますし、Shift_JISファイルを開くためのエンコードの設定も楽ですね。

起動も快適

気にならないレベルで起動します。
よく比較されるAtomは起動が遅い、というのを目にするので…(実際に使ったことはないので何とも言えないですが)

安定性についても今のところは全然問題ないです。

使い方・セットアップについての備忘録

編集

ショートカット

いっぱいあっても覚えられない人間なので、よく使うものだけ覚えていきます。

キー 用途
Ctrl + Shift + p コマンドパレットを開く (=F1)
Ctrl + o ファイルを開く
Ctrl + k + o フォルダを開く
Alt + Shift + f コードフォーマット
Ctrl + クリック クリックした定義に移動(=F12?)
Ctrl + \ 画面分割
Ctrl + 数字 ペイン切り替え

一括編集

  • Shift + Alt
    • 複数行
  • Alt
    • カーソルを追加

JSONフォーマット

  • Alt + Shift + f (コードフォーマットと同じ)

設定

[ファイル] > [基本設定]から。

  • 空白文字を表示
{
    "editor.renderWhitespace": "all"
}

拡張

Salesforceの開発でおなじみのMavensMateもAtom用のものがあるみたいですね。

アーカイブ済みの活動をクエリする時はFROM ActivityHistoriesではなくFROM Event ... ALL ROWSを使った方がよさそう #salesforce

あるオブジェクトに関連付いている活動履歴レコードをクエリする場合、SOQLで以下のようにクエリします(関連先のオブジェクトのIDが hogeId の場合)

SELECT
  Id, Subject, Description, StartDateTime, EndDateTime
FROM
  Event
WHERE
  WhatId = :hogeId
ORDER By
  StartDateTime DESC

ただし上記のクエリでは、アーカイブ済み(や削除済み)のレコードは取得できません。

アーカイブ済みのレコードを取得する方法は、調べた限りでは、少なくとも2つの方法がありそうです。

  • FROM ActivityHistories
  • FROM Event … ALL ROWS

パターン① FROM ActivityHistories

親オブジェクトから子(活動履歴)をクエリする方法(リレーションクエリ)です。

SELECT
  Id,
  (
    SELECT
      Id, Subject, Description, StartDateTime, EndDateTime
    FROM
      ActivityHistories
  )
FROM
  Parent__c
WHERE
  ID = :whatId

ちなみに、リレーションクエリを使わずに直接に ActivityHistory をクエリすると以下のエラーになります。

entity type ActivityHistory does not support query.

パターン② FROM Event ALL ROWS

ALL ROWS キーワードを付ける方法です。このキーワードを付けることによって、アーカイブ済み、削除済みのレコードも含めて取得対象になります。
ただし今回は削除済みのレコードは取得したくないので IsDeleted = false を条件に追加しています。

SELECT
  Id, Subject, Description, StartDateTime, EndDateTime
FROM
  Event
WHERE
  WhatId = :hogeId
  AND IsDeleted = false
ORDER By
  StartDateTime DESC
ALL ROWS

2つの書き方の比較

FROM ActivityHistories

  • メリット
    • クエリが1回で済む
  • デメリット
    • 「すべてのデータの参照」権限のないユーザがアクセスする場合、クエリ内部の句では以下の条件をすべて見なしていなければ、エラーになる。
      • WHEREを指定していないこと。
      • LIMIT 500(あるいはそれ以下の値)を指定し、取得レコード数を制限していること。
      • ORDER BYでレコードの取得順を指定する場合、ActivityDate の降順および LastModifiedDate の昇順の順番であること。

FROM Event … ALL ROWS

  • メリット
    • WHERE, LIMIT, ORDER BY に関する検索条件の指定に制限がない(とは言え、WhatIdで関連先オブジェクトを絞ったり、一般的なガバナ制限には気をつける必要はあります)
  • デメリット(運用上はデメリットにはならないと思いますが…)
    • ALL ROWS キーワードは開発者コンソール(Query Editor)では利用できない(Apexからのみ利用可)

ActivityHistoriesを使ったリレーションクエリの場合、デメリットが大きいんですよね。
現実問題、「すべてのデータの参照」権限のないユーザが利用することは普通のケースだと思います。というわけで、上記のルールの上でがんばるよりは、おとなしく FROM Event … ALL ROWS を使った方がよさそう、という結論に至りました。

ちなみに、制限に違反していると、以下のようなエラーに遭遇します。

  • LIMIT指定がない場合

There is an implementation restriction on ActivityHistories. When you query this relationship, security evaluation is implemented for users who don’t have administrator permissions, and you must specify a LIMIT with a maximum of 500

  • ORDER BY の指定が想定外場合

There is an implementation restriction on ActivityHistories. When you query this relationship, security evaluation is implemented for users who don’t have administrator permissions, and you must use a specific sort order: ActivityDate DESC, LastModifiedDate DESC

このあたり、以下のURLに書かれていることなんですが、「次の制約は、パフォーマンスの問題を回避するのに役立ちます。」みたいな言い回しよりも、はっきりとエラーになる旨を書いておいてほしい…。

developer.salesforce.com

AWS CodeBuildで依存ライブラリのキャッシュを利用する

2017.12.02 追記

buildspec.yml の cache セクションに記載することで、キャッシュを効かせられるようになりました!
詳細は→How to Enable Caching for AWS CodeBuild | AWS DevOps Blog


会社ブログに書きましたが、その続き、というかAWS CodeBuildのビルド時間を短縮するための小ネタです。

AWS CodeBuildはビルド時に新しく実行環境を起動するため、クリーンな環境でのビルドが行える一方、jarなりgemなりの依存ライブラリをビルドのたびにダウンロードしてしまうので、ビルド時間がかさんでしまいます。

今のところサービス標準の機能として、特定のディレクトリをキャッシュして再利用する、みたいなことはできないみたいなので、 buildspec.yml 側で対応する仕組みを考えてみました。

Gradle の場合、こんな感じです(ポイントだけ抜粋)

version: 0.2

phases:
  install:
    commands:
      - aws configure set s3.signature_version s3v4
      - aws s3 cp --region ap-northeast-1 $GRADLE_CACHE_S3_URL /tmp/gradle-caches.tar.gz
      - mkdir -p $GRADLE_USER_HOME/caches
      - tar -zxf /tmp/gradle-caches.tar.gz -C $GRADLE_USER_HOME/caches
  build:
    commands:
      - ./gradlew build
  post_build:
    commands:
      - tar -zcf gradle-caches.tar.gz -C $GRADLE_USER_HOME/caches .
artifacts:
  files:
    - gradle-caches.tar.gz
  discard-paths: no

ビルド後の post_build フェーズ時 *1$GRADLE_USER_HOME/caches の中身を tarball にし、成果物(アーティファクト)としてS3にアップロードします。毎回のビルド開始時にはそのファイルを展開する、という方法です。 tarball の中身が足りない場合は ./gradlew build 実行時に不足分のライブラリがダウンロードされるはずです。

ちなみに初回はS3にファイルがないので、その部分の対応は必要になります(今回は省略)。

*1: この時点ではすべての依存ライブラリのダウンロードが終わっているはずです。

AWS SDK for Java (DynamoDBMapper)を使った条件付き書き込み

DynamoDBMapperの概要については以下に書かれています。

docs.aws.amazon.com

DynamoDBMapper はタイプセーフに扱えて便利な反面、素のAWS API(オペレーション)が色々とラップされてしまっているので、以下によく使う利用法を逆引きリファレンスとして整理しました。

(注)確認は AWS SDK for Java のバージョン 1.10.62 で行っています。古いバージョンなので、最新のバージョンでは API が変更されている可能性もあるのでご注意ください。

すべての属性を置き換える

項目を更新する際に、既存の項目を丸ごと置き換える(不要な属性は削除)か、特定の属性だけ書き換えたいかを使い分けたいケースがあります。
通常、前者はputItem、後者はupdateItemといった形でオペレーションを使い分けます。

引数1つだけの save メソッドは putItem 相当の動作になります。

mapper.save(item);

指定した属性だけを更新する(他の属性はそのまま)

保存時の振る舞いについては DynamoDBSaveExpression を使って変更が可能です。

DynamoDBMapperConfig config = new DynamoDBMapperConfig(DynamoDBMapperConfig.SaveBehavior.UPDATE_SKIP_NULL_ATTRIBUTES);
mapper.save(item, config);

item中で非NULLの属性だけが更新されます。

ちなみに、 SaveBehavior には以下の4種類のパターンがあります。詳細はJavaDocを参照してください。

  • UPDATE(デフォルト)
  • UPDATE_SKIP_NULL_ATTRIBUTES
  • CLOBBER
  • APPEND_SET

項目が存在しない場合はエラーにする

putItem, updateItemのどちらも、基本的にはupsert相当の動作になります(キーがあれば更新、なければ追加)。

中途半端な属性だけをもつ項目が追加されてしまっても困るので、項目が存在する場合に限り、更新処理を行いたい場合があります。

その場合、条件式 ( DynamoDBSaveExpression )を利用します。

AttributeValue hashValue = new AttributeValue().withS("Key1");
AttributeValue rangeValue = new AttributeValue().withN(Long.toString(123L));

DynamoDBSaveExpression expression= new DynamoDBSaveExpression()
        .withConditionalOperator(ConditionalOperator.AND)
        .withExpectedEntry("partitionKeyName", new ExpectedAttributeValue()
                .withValue(hashValue)
                .withComparisonOperator(ComparisonOperator.EQ))
        .withExpectedEntry("sortKeyName", new ExpectedAttributeValue()
                .withValue(rangeValue)
                .withComparisonOperator(ComparisonOperator.EQ));

mapper.save(item, expression);

// DynamoDBMapperConfigと組み合わせる場合は以下。
// mapper.save(item, expression, config);

プライマリキー “Key1”, パーティションキー “123” の項目が存在しない場合、 ConditionalCheckFailedException がスローされます。

項目が存在する場合はエラーにする

上の例と逆に、項目が存在しない場合に限り登録を行いたい場合の例です。

DynamoDBSaveExpression expression = new DynamoDBSaveExpression()
        .withExpectedEntry("partitionKeyName", new ExpectedAttributeValue().withExists(false))
        .withConditionalOperator(ConditionalOperator.AND)
        .withExpectedEntry("sortKeyName", new ExpectedAttributeValue().withExists(false));

mapper.save(item, expression);

こちらも違反時は ConditionalCheckFailedException がスローされます。

その他の条件式

上で書いた以外にも、色々な条件式が利用できます。

docs.aws.amazon.com

DynamoDBMapperでも、上記の条件式がすべて利用できるはずですので、必要に応じて DynamoDBSaveExpression を作成していくことになると思います。

(続)活動を意図的にアーカイブしてテストしたい #salesforce

この前(というか日付的には昨日)、こんな記事を書きました。

jappy.hatenablog.com

「期日が1年以上前の活動を作ったら、そのうちアーカイブされると思ったが、1日待ってもアーカイブされなかった」というのが要旨ですが、改めて確認するとアーカイブされていました

というわけで、改めてわかったことをまとめると、

活動がアーカイブされるには、1日以上かかる。

今回のレコードは、期日が「2016/04/20 15:00」、作成日時が「2017/06/15 10:04」でしたが、アーカイブされたのは 6/17 12:00~6/18 22:20のどこか、なようです。

アーカイブ時刻ってArchivedDateみたいな項目でわかるんでしたっけ…?)

活動がアーカイブされるのに、レコード数は(おそらく)関係ない

今回、DE環境で確認したので、ディスク容量は5.0MBです。

確認時点で使用量は約79%(4.0MB)、そのうち、活動は441件(=882KB)でした。

そんなにキリがよい数字でもないので、レコード数や空き容量はおそらく関係ないのでしょう。

FROM Eventだけだとアーカイブ済みの活動はクエリされない

開発者コンソール上のQuery Editorで FROM Event として活動をクエリしていましたが、アーカイブ済みの活動はクエリ結果として返らないようです。

SOQLで提供されている ALL ROWS というキーワードを使うと、アーカイブ(や削除)済みのレコードもクエリできますが、これはQuery Editor上では使えないようです(なぜでしょうね…?)

Apexからは利用できるみたいなので、Anonymous Windowで実行しました。

List<Event> events = [
  SELECT Id, ActivityDate, IsArchived, Subject FROM Event ORDER BY ActivityDate ASC ALL ROWS    
];

System.debug(events[0]);

活動を意図的にアーカイブしてテストしたい #salesforce

Salesforceにおける活動のアーカイブとしては、

  • 期日が365日以上前の活動はアーカイブされる
  • この365日という期限に関しては、サポートデスクに依頼することで(必要であれば)延長できる

ことまでは書かれています。

さて、1件も活動が登録されていないクリーンなSalesforce環境で、テストのためにアーカイブ済みの活動を作りたい、というのは、稀によくあるケースだと思います。
そこで、調査してみました。

が、結論から言うと、残念ながら作れないようです。もし方法知っている方がいたら教えてください…。

やったこと

期日が1年以上前の活動を手動で登録してみました。

予想

活動のアーカイブ処理は何時かは分からないが、毎日どこかのタイミングで行われているはず。 なので、24時間以上待てば、アーカイブされているだろう。

結果

優に24時間は待ってみましたが、特にアーカイブされた痕跡はありません。
ただ、もしかするとアーカイブ処理は実は日次ではなく、週次とか月次とか不定期とか、そんなタイミングで実施されているのかもしれないので、そのうちしれっとアーカイブされているかもしれません。
もしくは、一定以上の数のレコードがないとアーカイブ対象にならないとか。

ちなみに、アーカイブされたかどうかの確認は、開発者コンソール上のQuery Editorで、以下のクエリを実行して行っています。

SELECT Id, ActivityDate, IsArchived, Subject FROM Event ORDER BY ActivityDate ASC

また、API、Apex、設定などで、明示的にアーカイブする手段があるだろうと思って調べてみたのですが、見つかりませんでした。

salesforce.stackexchange.com

このページの回答を見るに、やっぱりできなそうな感じ…。

追記

続きを書きました。

jappy.hatenablog.com

MyBatisのMapperをSpockで全自動テスト

MyBatisのSQLは2Way SQLではない上、動的SQLスニペットなどによって可読性が損なわれがちということもあり、構文エラーは早めに検知する仕組みを用意した方がよいという印象です。

そこで、Mapperを全スキャンして、SpockでData Driven Testingを行う方法がよいんじゃなかろうか?と考えました。
Spockを使うと、各SQLを一つのテストパターンとして実行できるので、SQLにバグがあった場合にそのMapper・メソッドが特定しやすくなるんじゃないかという狙いです。

というわけで、Springを使っていることを前提として、以下のような根拠の下で、実装してみました。

  • 通常Mapperインタフェースは特定のパッケージ以下にそろっているはずで、Springであれば ClassPathScanningCandidateComponentProvider を使うと、パッケージ配下のMapperインターフェース一覧を簡単に取得できる。
  • インタフェースが分かれば、対応するMapperの実体(コンポーネント)は ApplicationContext から getBean で取得できる。
  • 引数はとりあえずnullでない適当な値をセットしてしまえば、 NullPointerException にはならず、SQLクエリの実行自体は行えるはず。

で、作ってみたSpecificationは以下のような感じです。

MyBatis mappers automated test with Spock/Spring

ひとまず実行して例外が起きなければOK、という最低限のテストです。

手元のMapperで必要な最低限の型しか対応しておらず、また、デフォルトコンストラクタがない場合を考慮していなかったり、と不十分な点は多々ありますが、SQLを一通り実行してテストすることはできるようになりました。

(実行環境)

  • Spring Boot 1.4.2.RELEASE
  • mybatis-spring-boot-starter 1.2.0
  • spock-core 1.1-groovy-2.4-rc-3
  • spock-spring 1.1-groovy-2.4-rc-3