そごうソフトウェア研究所

SOA、開発プロセス、ITアーキテクチャなどについて書いています。Twitterやってます@rsogo

AWS SNSを使ったEメールの送信

最終的にはAWS SNSを使ってスマホにPush通知を送信する方法を確認したいですが、その前に簡単にEメールを送信すうる手順を確認したいと思います。

Topicの作成

f:id:begirama:20190416113146p:plain

以下の設定ができます。

  • 暗号化
  • アクセスポリシー
  • 配信再試行ポリシー
  • 配信ステータスのログ記録

サブスクリプションの作成

f:id:begirama:20190416113453p:plain

今回はテスト的にEメールを指定して、自分のメールアドレスをエンドポイントに指定してみます。

オプションとして、下記を設定できます。

f:id:begirama:20190416113751p:plain

サブスクリプションの確認

サブスクリプションの欄には保留中のエンドポイントとして先程のメールアドレスがリストされています。

f:id:begirama:20190416114500p:plain

下記のようなメールが対象に届いているのでConfirmします。

f:id:begirama:20190416114308p:plain

トピックへのメッセージの発行

f:id:begirama:20190416114048p:plain f:id:begirama:20190416114116p:plain

DynamoDBの勉強をしています

AWS Lambda + DynamoDBの組み合わせで サーバレスなアプリケーションを作ろうとしています。 DynamoDB をはじめとするNoSQLデータベースをちゃんと使ったことがなかったので今回改めて勉強しようと思います。

こちらのAWSで資料をお勧めしてもらいました。

各種設計に関してはマニュアルのベストプラクティスの章が参考になりそうです。 docs.aws.amazon.com

また、こちらのサイトではモデリングについて、パターン分けして詳しい説明を和訳されていて、参考になりました。

NoSQLデータモデリング技法 · GitHub

AWS Lambdaで必要な外部Jarも含めて一つのJarにする

AWS Lambdaでアプリを作っている時に必要となる外部のクラスが見つからずにjava.lang.NoClassDefFoundErrorが発生するケースがあります。

ビルド時は依存関係は解決できていてコンパイルエラーはでていんですが、これは外部のライブラリがパッケージングの際に含まれていないことが原因でした。

これはビルドツールにMavenを使っている場合はmaven-shade-pluginで解決できます。

マニュアルのIDE なしで Maven を使用した .jar デプロイパッケージの作成 (Java)にも記述があります。

プラグインセクションでは、Apache maven-shade-plugin は、Maven がビルドプロセス中にダウンロードして使用するプラグインです。このプラグインは、デプロイパッケージであるスタンドアロン .jar (.zip ファイル) を作成するために、jar のパッケージ化に使用されます。

stackoverflowでも同様の質問に対して、同じ解決策が出てました。

stackoverflow.com

homebrewでmysqlを8から5.7にバージョンを落としたときの対応

趣旨

Macにhomebrewで入れたMySQLを8から5.7にダウングレードした時の話をします。 事の発端は新しいMacBook Proに開発環境を移行しようとしたんですが、 homebrewでMySQLを入れるとバージョン8が入りました。

で、いろいろ変わっているようなので、今やりたいのはMySQL8対応じゃないし、問題を先送りすることにして、5.7にバージョンを落とすことにしました。 しかし、これはこれで小一時間はまりました。

発生した問題

homebrewは提供されていればバージョンを指定して過去のバージョンをインストールできます。brew uninstallして、brew installしましたが、起動が失敗します。

結論としては、アンインストールしても/usr/local/var/mysql/に各種ファイルが残っていて、それが原因で起動に失敗していました。/usr/local/var/mysql/以下のファイルをすべて削除することで解消しました。

やった内容

ここから先は、実際に試した内容です。

MySQL 8のuninstall

$ brew uninstall  mysql

MySQL 5.7のinstall

  • brew search {キーワード}で、どのパッケージが利用可能か確認
    ラッキーなことに5.5, 5.6, 5.7が使えそうです。
$ brew search mysql
==> Formulae
automysqlbackup              mysql++                      mysql-cluster                mysql-connector-c++          mysql-search-replace         mysql@5.5                    mysql@5.7 ✔
mysql                        mysql-client                 mysql-connector-c            mysql-sandbox                mysql-utilities              mysql@5.6                    mysqltuner

==> Casks
homebrew/cask/mysql-connector-python     homebrew/cask/mysql-shell                homebrew/cask/mysql-utilities            homebrew/cask/navicat-for-mysql          homebrew/cask/sqlpro-for-mysql
  • バージョンを指定してインストール
$ brew install  mysql@5.7
==> Downloading https://homebrew.bintray.com/bottles/mysql@5.7-5.7.23.high_sierra.bottle.tar.gz
Already downloaded: /Users/rsogo/Library/Caches/Homebrew/mysql@5.7-5.7.23.high_sierra.bottle.tar.gz
==> Pouring mysql@5.7-5.7.23.high_sierra.bottle.tar.gz
==> /usr/local/Cellar/mysql@5.7/5.7.23/bin/mysqld --initialize-insecure --user=rsogo --basedir=/usr/local/Cellar/mysql@5.7/5.7.23 --datadir=/usr/local/var/mysql --tmpdir=/tmp
==> Caveats
We've installed your MySQL database without a root password. To secure it run:
mysql_secure_installation

MySQL is configured to only allow connections from localhost by default

To connect run:
    mysql -uroot

This formula is keg-only, which means it was not symlinked into /usr/local,
because this is an alternate version of another formula.

If you need to have this software first in your PATH run:
  echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.bash_profile

For compilers to find this software you may need to set:
    LDFLAGS:  -L/usr/local/opt/mysql@5.7/lib
    CPPFLAGS: -I/usr/local/opt/mysql@5.7/include
For pkg-config to find this software you may need to set:
    PKG_CONFIG_PATH: /usr/local/opt/mysql@5.7/lib/pkgconfig


To have launchd start mysql@5.7 now and restart at login:
  brew services start mysql@5.7
Or, if you don't want/need a background service you can just run:
  /usr/local/opt/mysql@5.7/bin/mysql.server start
==> Summary
🍺  /usr/local/Cellar/mysql@5.7/5.7.23: 317 files, 234.4MB

これでうまく行ってくれれば良かったのですが、起動させると、次のようなエラーがでました。

$ mysql.server start
Starting MySQL
. ERROR! The server quit without updating PID file (/usr/local/var/mysql/rsogo-Mac-Book-2.local.pid).

PID fileは存在しないし、困ってしまったのですが、/usr/local/var/mysql/にある.errファイル(私の環境ではrsogo-Mac-Book-2.local.errというファイル名でした)を見ると、既にibdata1があるというFatalレベルのエラーが発生していました。

今回は初期構築なので、MySQLを一度アンインストール後、/usr/local/var/mysql以下をバッサリ削除して、再度インストールすることで、下記のように正常に起動するようになりました。

$ mysql.server start
Starting MySQL
. SUCCESS! 

実際にはこんなにすんなり行かず、特に下記のようにサービスとして起動するようにすると、これ自体は成功して、実際アクセスすると接続でエラーになるので、設定がミスっているのではないかとか、いろいろ試行錯誤しました。勘でいろいろやるんじゃなく、エラーログを調べるのは大事ですね。

brew services start mysql@5.7

あと今回いろいろ調べるなかでhomebrewで入れると/usr/local/Cellar/mysql@5.7/5.7.23みたいなパスにインストールされるということも理解できました。

Mule ESBのフロー定義中にでGroovyスクリプトでカスタムのロジックを埋め込む

そういえば、Salesforce.comによるMulesoftの買収が発表されましたね。今後、どのようになっていくのか注目したいと思います。

www.mulesoft.com

さて、今回はMuleのフロー定義にGroovyのスクリプトを埋め込んでみたいと思います。 これができるとMuleで予め用意されたメッセージの変換ルール関数以外の独自処理をスクリプトで行うことができます。 Groovy以外にもJavaScriptなどのスクリプトを呼び出せます。

フロー定義

全体像

HTTPのGETリクエストを受け取り、URLのPATHのlxに置き換えるという処理をしています。 処理は<scripting:transformer>の中でやっています。名前空間http://www.mulesoft.org/schema/mule/scriptingを使うためにhttp://www.mulesoft.org/schema/mule/scripting http://www.mulesoft.org/schema/mule/scripting/current/mule-scripting.xsd"を宣言に追加しています。

<mule
  xmlns="http://www.mulesoft.org/schema/mule/core"
  xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
  xmlns:http="http://www.mulesoft.org/schema/mule/http"
  xmlns:scripting="http://www.mulesoft.org/schema/mule/scripting"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/scripting http://www.mulesoft.org/schema/mule/scripting/current/mule-scripting.xsd">

  <http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8086" basePath="groovy" doc:name="HTTP Listener Configuration"/>

  <!-- GET -->
  <flow name="GetFlow">
    <http:listener config-ref="HTTP_Listener_Configuration" path="/*" doc:name="Recieve HTTP request" allowedMethods="GET">
      <http:response-builder reasonPhrase="#[flowVars['reason']]" statusCode="#[flowVars['statusCode']]"/>
    </http:listener>
    <logger doc:name="Log the payload" level="INFO" message="GET is called."/>

    <set-variable variableName="VarTableName"
                  value="#[message.inboundProperties['http.request.path']]"
                  doc:name="Variable"/>
                  
    <scripting:transformer name="stringReplaceWithParams">
      <scripting:script engine="groovy">
        <property key="oldStr" value="l" />
        <property key="newStr" value="x" />
        <scripting:text>
          return VarTableName.toString().replaceAll("$oldStr", "$newStr")
        </scripting:text>
      </scripting:script>
    </scripting:transformer>
  </flow>
</mule>

ステップバイステップで見ていきます。

HTTPリクエストのリッスン

8086ポートで、URLのベースのパスとして/groovyに来たリクエストを受け取る設定になっています。

<http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="8086" basePath="groovy" doc:name="HTTP Listener Configuration"/>

GETメソッドで呼び出されたときに、下記のフローが動作します。

  <flow name="GetFlow">
    <http:listener config-ref="HTTP_Listener_Configuration" path="/*" doc:name="Recieve HTTP request" allowedMethods="GET">

URLパスの取り出し

HTTPリクエストのパス(ホスト名、ポート名は含まず、それ以降)を取り出し、VarTableNameという変数に入れています。

    <set-variable variableName="VarTableName"
                  value="#[message.inboundProperties['http.request.path']]"
                  doc:name="Variable"/>

Groovyのスクリプトで文字列操作

    <scripting:transformer name="stringReplaceWithParams">
      <!-- scriptのエンジンとして、groovyを指定 -->
      <scripting:script engine="groovy">
        <!-- 変数に値を設定 -->
        <property key="oldStr" value="l" />
        <property key="newStr" value="x" />
        <!-- 実際のGroovyのコード。replaceAllでURLパスの文字列を変換して、応答として返す -->
        <scripting:text>
          return VarTableName.toString().replaceAll("$oldStr", "$newStr")
        </scripting:text>
      </scripting:script>
    </scripting:transformer>

テストしてみましょう

リクエス

次のURLに対して、GETメソッドのリクエストを出します

http://localhost:8086/groovy/helloworld

レスポンス

URLのパス(/groovy/helloworld)のうち、lxに置換した結果が返ってきています。

/groovy/hexxoworxd

curlコマンドで確認したら、こんな感じ。

$ curl http://localhost:8086/groovy/helloworld
/groovy/hexxoworxd

うまくいきましたね。

AWS Lambdaの何が嬉しかったか。Excel生成WebサービスのLambda関数化

AWS Lambdaを使った開発をやってみて何が嬉しかったのかを書いておこうと思います。

Lambda関数化したのは以下のようなWebサービスでした

  • SPAなWebアプリからJSONでリクエストを受け取る
  • JavaExcelを扱うライブラリであるPOIでExcelのテンプレートファイルにJSONで受け取ったデータを埋め込む
  • Excelをクライアントに返す(ブラウザでダウンロードが始まる)

Excelを触る時点で、Web系の技術だけで完結するのは難しいので、 この部分だけJavaでやるというのは今後もあるんじゃないかなと思います。

で、次のような課題がありました

  • 普段は大丈夫だけど、たまにでかいデータが来て、Java側でOutOfMemoryが発生する
  • そもそもExcel作成リクエストはたまにしか実行されない(業務のひとまとまりの作業が終わって帳票を出したいときだけ)
  • なのに、でかいデータのためにそれなりのサイズのEC2を用意しておかないといけない

Lambda関数化したことで

  • 使うときのみリソースを割り当てるので、思い切ったリソースを割り当ててもコスト的には下がる。これは処理頻度が低いサービスでは大きいメリットだと思います
  • 副次的な効果として、これまで同じJavaアプリケーションサーバー上で実行していた他のアプリケーションがExcel作成WebサービスのOutOfMemoryの巻き添えを食っていたのですが、単独機能のサービスとして切り出されたことで、お互いに影響を受けることがなくなりました。マイクロサービス化ができたわけです。

他の用途ですと、Javaの立ち上がり時の遅さが気になったかもしれませんが、 今回のケースだと、そもそもExcelを生成するのに10秒くらいかかるような Webサービスだったので性能は気になりませんでした。

まとめ

AWS Batchも使ってみたいなーと思っているサービスで、 バッチという処理頻度が低い処理に対して、 実行されるときだけ大きいリソースを割り当てて(必要だったら並行でいっぱい立ち上げて)、 終わったら終了というのは非常にクラウドらしく、魅力的です。

もちろんサーバレスになることで、運用が楽になるか、スケールするとかいい事はあると思うのですが、 処理面での直接的な効果として頻度が低い処理に、思い切りリソースを割り当てられるというメリットがありました。

Lambda関数をAPI Gatewayで公開する(シンプル版)

先日、JavaでLambdaを作るエントリを書きましたが、今回、API Gatewayを使って、Lambdaで実装した処理をWebサービスとして公開したいと思います。

API GatewayはRESTfulなAPIを簡単に作れます。 RESTfulなので、まずはリソースを定義して、そのリソースに対するGET、POST、PUT、DELETEなどのメソッドを提供します。 後はどのバックエンドに接続するのかが、基本的な手順です。

手順はこちらをベースに進めます。

docs.aws.amazon.com

Lambda関数の作成

バックエンドはLambda関数を使います。 こちらで公開されているNode.jsのサンプルを、そのまま使って作成しておきました。

docs.aws.amazon.com

APIの作成

f:id:begirama:20180513002008p:plain

リソースの定義

最初にリソースを定義します。

f:id:begirama:20180513002455p:plain

  • リソース名: 任意の名前です。ここではproxyとしています
  • リソースパス: /proxy/{itemId}など個別のパターンを設定できますが、ここではワイルドカードになる{proxy+}を使います。これで、ルートのURL以下はなんでも受け取ります。

メソッドの定義

f:id:begirama:20180513002542p:plain アクションで、メソッドの作成を選びます。

f:id:begirama:20180513002702p:plain Lambda関数プロキシを選択します。 リージョンを選択して、Lambda関数名に何文字か入力すると、候補が表示されます。

f:id:begirama:20180515072400p:plain ここまでの状態はこんな感じ。

APIのデプロイ

f:id:begirama:20180513003023p:plain このままでは呼び出せないので、APIのデプロイを行います。

f:id:begirama:20180513003052p:plain ステージというのが設定できて、ステージごとに認証やスロットリングの設定を変更することができます。 例えば、社内用のAPIと、取引先用のAPIなんかで分けることもできそうです。

f:id:begirama:20180513003200p:plain

APIの呼び出し

デプロイが完了すると、こんな感じのURLが割り当てられます。

f:id:begirama:20180515070649p:plain

このまま呼び出すと、

{"message":"Missing Authentication Token"}

エラーが返ってきます。 エラーメッセージの内容がわかりにくいのですが、上のURLはルートのもので、子リソース(ここではproxy)が定義されている場合には、そのまま呼び出すとこのエラーが返ってきます。

そこで、https://dummy.execute-api.ap-northeast-1.amazonaws.com/dev/proxyのような子リソースを含むURLで呼び出すと、 欲しかった結果を得られます。