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回だけ行えばよいような初期化処理であり、テストケースの数が多い場合、実行時間の面で有利です。

参考

インストール済みパッケージ情報をApex or SOQLで取得したい #salesforce

[設定] - [インストール済みパッケージ] から確認できる、インストール済みのパッケージとそのバージョンを、プログラム的に取得したい、というメモです。

結論から言うと、インストール済みパッケージの取得はTooling APIで提供されているみたいなので、以下のクエリでOKです。

SELECT Id, SubscriberPackageId,
  SubscriberPackage.NamespacePrefix,
  SubscriberPackage.Name, SubscriberPackageVersion.Id,
  SubscriberPackageVersion.Name, SubscriberPackageVersion.MajorVersion,
  SubscriberPackageVersion.MinorVersion,
  SubscriberPackageVersion.PatchVersion,
  SubscriberPackageVersion.BuildNumber
FROM InstalledSubscriberPackage
ORDER BY SubscriberPackageId

開発者コンソール上のQuery Editorで行う場合、「Execute」の右にある「Use Tooling API」にチェックを入れておく必要があります。

ところが、これをApexでやろうとした場合、上記のクエリ結果を List<InstalledSubscriberPackage> に代入してあげればOKかと思いきや、 InstalledSubscriberPackage がInvalid Typeとしてエラーになってしまいます。
そのため、ちょっと回りくどいですが、ApexからTooling APIREST API呼び出しを行ってあげればOKです。

String query = 'SELECT Id FROM InstalledSubscriberPackage';

Http http = new Http();
HttpRequest req = new HttpRequest();
req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
req.setHeader('Content-Type', 'application/json');
req.setEndpoint(URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v44.0/tooling/query/?q=' + EncodingUtil.urlEncode(query, 'UTF-8'));
req.setMethod('GET');

HttpResponse res = http.send(req);
System.debug(res);

String result = res.getBody();
System.debug(result);

Tooling API、直接触れる機会はなかなかないですね。特に最近はSalesforce CLIなどでラップされてしまっていますし。

Lightning Web Components(LWC)をVisualforceで表示してみる#salesforce

巷で話題の(?)Lightning Web Components(LWC)をVisualforceで表示できないか試してみました。

LWCをAura Componentでラップして、Lightning Outを使う

最初に思いついたのは、LWCをAura Componentでラップして、Lightning Out for Visualforceを使う方法です。
この方法で問題なく表示できました。

ソースを順に見ていきます。

LWC

Trailheadのサンプルのまんまですが…。

■ helloworld.html

<template>
  <lightning-card title="HelloWorld" icon-name="custom:custom14">
      <div class="slds-m-around_medium">
          <p>Hello, {greeting}!</p>
          <lightning-input label="Name" value={greeting} onchange={changeHandler}></lightning-input>
      </div>
  </lightning-card>
</template>

■ helloworld.js

import { LightningElement, track } from 'lwc';
export default class HelloWorld extends LightningElement {
  @track greeting = 'World';

  changeHandler(event) {
    this.greeting = event.target.value;
  }
}

Aura Component (auraHelloWorld.cmp)

LWCを呼び出すだけ。

<aura:component implements="flexipage:availableForAllPageTypes">
  <c:helloworld />
</aura:component>

参考にしたのは以下のページです:

Compose Aura Components from Lightning Web Components

Lightning Outアプリケーション(helloWorldOut.app)

上記のAura Componentを使えるように宣言します。

<aura:application extends="ltng:outApp" >
    <aura:dependency resource="c:auraHelloWorld" />
</aura:application>

Visualforce (lwcHelloWorld.vfp)

ページ名(ファイル名)は何でもよいです。

<apex:page>
    <apex:includeLightning />
    <div id="hello"></div>
   
    <script>
        $Lightning.use("c:helloWorldOut", function() {
            $Lightning.createComponent("c:auraHelloWorld", {}, "hello", function(cmp, err) {}); 
        });
    </script>
</apex:page>

プレビュー

f:id:jappy:20190121220208p:plain

できました!

Aura Componentでラップする必要ないのでは?

ところで、よくよく考えると、Aura Componentでラップする必要がないのでは?という疑問が湧きました。

というわけで、Lightning Outアプリケーションで直接LWCを宣言します。

<aura:application extends="ltng:outApp" >
    <aura:dependency resource="c:helloworld" />
</aura:application>

その後、Visualforce側 createComponent の引数も "c:helloworld" に変更します。こちらの方法でも、同じように表示できました。

まとめ

LWCをVisualforceで表示したければ、今までのAura Componentと同じ要領でLightning Outが利用できます。
注:今回は簡単なサンプルだけなので、実際にやると色んな落とし穴があるかもしれません。

pyenv利用時にパッケージコマンド実行時に出るcommand not found...command exists in these Python versionsエラーを治す

pyenvとpipを使っている環境で、下記のようなエラーが出た時の解消法についてのメモです。

$ hoge
pyenv: hoge: command not found

The `hoge' command exists in these Python versions:
  3.5.2

解消法

まずはコマンドが存在するというPythonバージョンに切り替えます。

$ pyenv global 3.5.2
$ pyenv rehash

該当パッケージをアンインストールします。

$ pip3.6 uninstal hoge

利用したいPythonバージョンにまた切り替えます。

$ pyenv global 3.7.0
$ pyenv rehash

あとは該当パッケージをインストール。

CodePipeline経由と直接実行の場合でCodeBuildのファイルパーミッションが異なるという話

CodeBuildでソースにGitHubを指定した場合のビルドで、

  • 直接CodeBuildで「ビルドの開始」を押した時
  • CodePipelineのBuildから実行された時

のそれぞれで、ファイルパーミッションが変わってしまっている?という話です。

Java(Gradle)でビルドするジョブで、後者の場合 gradlew の実行権限が付与されていないために、ビルドエラーになることで気づきました。

buildspec.yml

以下のように、buildフェーズの際にカレントディレクトリのファイル一覧を出力させてデバッグします。

version: 0.2

phases:
  build:
    commands:
      - ls -la
      - ./gradlew bootJar

artifacts:
  files:
    - build/libs/*.jar

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

直接実行

f:id:jappy:20190114145654p:plain

gradlew に実行可能権限が付いています。

CodePipeline経由で実行

f:id:jappy:20190114145609p:plain

gradlew に実行可能権限が付いていません。
実行前に付与してあげれば済むはずですが、一応覚えておきたいポイントだなと思いました。

API Gateway WebSocketのコストメリットやLambdaの同時実行数について

Amazon API GatewayでWebSocketが利用可能になりました。

使用感などは、下記のAWSブログでのチャットアプリのサンプルを構築してみると、わかってきます。それにしても、チャットアプリはWebSocketのサンプルとして定番中の定番ですねぇ…。

[発表]Amazon API GatewayでWebsocketが利用可能 | Amazon Web Services ブログ

使ってみた印象としては、「ルート」に対して、Lambda関数(あるいはその他のインテグレーション)を割り当てていく、というところが面白いところですが、それ以外の設定については、ほとんどRESTと同じだった印象です。
むしろ導入する場合のハードルは、クライアント側の実装の方かと思いました(昔socket.ioでとあるシステムを開発していたことがありますが、今もデファクトなんですかね…?)

それはさておき、本記事では、API GatewayのWebSocketを実運用する場合に、いくつか浮かんだ疑問について考えてみました。

  • ポーリング方式と比べた場合のコストメリット
  • Lambdaの同時実行数は大丈夫?
  • ALBとの比較

想定するユースケース

ユースケースとしては、チャットアプリではなく、AWS側で発生した何らかのイベントを、接続中のクライアントにプッシュする、というものです。下図の赤い矢印(サーバプッシュ)の部分の送信ですね。

f:id:jappy:20190114064014p:plain

ちなみに図中はイベントの発生元がRDSになってますが、まぁ何かしらのイベント発生だと読み替えてください。

これまでは、このようなことをやろうとした場合、

  • 同じくWebSocketをサポートしているALBを使い、EC2やLambdaなどでWebSocketサーバを立てる
  • (諦めて)クライアントからポーリングをする
  • GraphQL Subscription(使ったことない…)

などの方法がありました(参考記事 [1])

[1] リアルタイム革命/The Revolution of Real-time WebApps - Speaker Deck

ポーリング方式と比較してのコストメリット

東京リージョンの場合の料金は、2018年1月14日現在、下表のようになっています(一部のみ。無料利用枠を除く)

HTTP/REST API WebSocket API
リクエスト数に対する料金(100万回あたり) 4.25USD(最初の3億3,300万コールまで) 1.26USD(最初の10億メッセージまで)
接続時間に対する料金(100万分≒16,667時間≒694日あたり) N/A 0.315USD

ポーリングの頻度、WebSocketで送信が必要なイベントの発生頻度、接続時間、など色々と前提条件が異なるので一概に比較はできませんが、多くの場合、WebSocketの方がコストを低く抑えられそうです。

リクエスト数に対する料金は、単純計算で1/3以下になっていますし、ポーリング方式の場合、イベントの発生有無に関わらずAPIコールが発生していました。WebSocketの場合、イベントが発生していた場合だけメッセージ送信すればよいので、送受信するメッセージ数そのものも抑えることができます。

Lambdaの実行単位

当初、「クライアントとLambdaの接続が確立しっぱなしになると、クライアントの数が増えた時にLambdaの同時実行数に達してしまうのでは?」という疑問が浮かびました。1接続につき、Lambda関数が1個占有されてしまうようなイメージですね。

こちらは勘違いだったみたいです。
クライアントの接続の管理は、API Gateway側で行われます。

Lambda関数は、メッセージの送信(あるいは接続、切断)が発生した場合に限り、イベント駆動で起動され、処理が実行されるようでした。

ALBとの比較

同じくre:Inventの発表で、ALBのターゲットにLambdaが利用できるようになりました。
ALBもWebSocketをサポートしているので、ALB + Lambdaでも、イベントドリブンなWebSocketシステムの実現ができそうです。

こちらのパターンは未確認なのですが、API GatewayのWebSocketを利用するメリットとしては、スロットル制御、利用量プラン、認証、Lambda以外のインテグレーション(HTTP, Mock, その他AWSサービス)もできる、といった点だと思います。