【最高】AWS Loftに行ってきました!
2018年10月1日にOPENした、AWS Loft Tokyoに行ってきました!
前日の台風のせいで交通機関は乱れに乱れまくる中、だいぶ余裕も持って家を出たらOPEN時間より早く着いてしまいました。

OPEN初日ということで、会場では無料でカフェラテを頂くことができ、さらには写真で撮った画像が名言と共にラテアートに・・・!
写真撮ったらラテアートが生成された凄え#AWSLoft pic.twitter.com/WWWCoRfoAr
— 𝔈𝔩𝔞𝔰𝔱𝔦𝔠 𝔎𝔞𝔧𝔦 𝔖𝔢𝔯𝔳𝔦𝔠𝔢@魔人ママチャリライダー (@Anorlondo448) 2018年10月1日
凄すぎるでしょこれ・・・そっくり度まで出てるしwww
ちなみにカフェでの支払いはAmazon Payかクレジットカードのみとのこと。
11時にオープニングセレモニーが始まり、アマゾンウェブサービスジャパン代表取締役社長の長崎さんによるスタートアップ支援についての説明がありました。
- 「AWS Activate」1年間で最大10万ドルの有料サポートが無料
- Solution Architectによる技術支援
などなど・・・
手厚すぎるでしょうこれ・・・
さすがAWS!
そして斬新なテープカットイベント!
テープの「AWS」って文字が光ってるwwww

続いては、
- 株式会社トレタの増井さん
- 株式会社ユーザベース、株式会社UB Venturesの竹内さん
- オープニングセレモニーで話された長崎さん
によるスタートアップトークショー・・・だったのですが、ここらへんからリモートで障害対(ry)がありちゃんと聞くことができず・・・もったいない(;´Д`)
ただ、「好奇心を持ち続けること」という言葉だけは心に響きました。
ここで午前は終了となり、午後13時からソリューションアーキテクトの塚田さんによる「イノベーションを起こし続ける開発組織のカルチャー」のお話や今後のAWS Loftでのイベントについて。
今後のイベント
— 𝔈𝔩𝔞𝔰𝔱𝔦𝔠 𝔎𝔞𝔧𝔦 𝔖𝔢𝔯𝔳𝔦𝔠𝔢@魔人ママチャリライダー (@Anorlondo448) 2018年10月1日
Tech Meetup行きたいな#AWSLoft pic.twitter.com/ChqTw8SRMk
セッション良すぎた・・・
これはいろんな人に聞いてもらいたいやつでした。
(資料公開されないかな・・・)
(動画で配信して欲しいな・・・)
目次は大きく分けて4つでAWSの開発組織における、
の話で、どれもこれも素晴らしい内容でした!
全部書き留められなかったのでとりあえず、心に残ったものを列挙
メカニズム
- Amazonでは会議でのプレゼンテーションツールの利用は殆ど無い
- プレゼンテーション形式の会議は話し手の話術に依存する
- 聞き手に取っての捉え方が変わってしまう恐れがある
- 6 pagerと呼ばれる形式のレポートで行われる
アーキテクチャ
- システムと組織。これらは分けて考えられない
- DevOpsの実践
- 文化(Culture)+実践(Practice)+ツール(Tool)
- AWSの歴史
- 1995サービスローンチ
- 2001年、アプリケーションが肥大化し、モノリスが肥大化
- 2004年、最初のサービスはSQS
- モノリシックな開発サイクル
- デプロイが一大イベントとなってしまっていた
- microservice化した
- Two-Pizza Teams
- 少数精鋭のチームのほうがよりスピーディに進めることができる
- Owenership & Autonomy
- 作るものに対する全ての責任を負う
- プロダクト計画の策定
- ロードマップ
- 開発
- 運用/カスタマーサポート
- 説明責任
- 大きな組織の一部分
- 自律的なスタートアップのイメージ
- QAは誰がやる?
- チームがやる
- オンコールは誰が?
- チームがやる
- Opsは誰が?
- Not Exist
- チームがOpsもやる
- 全てはサービスチームに存在し、自身の役割に集中する
- チームには権限が与えられ、多くの自由が認められている
- ただしチームとして高い水準を維持する必要がある
- チームの水準を高く維持する
- オンボーディング/トレーニングを通じて
カルチャー
- CI/CD
- APOLLOというデプロイサービスを作った
- MVP(Minimum Viable Product)
- 必要最低限の能力を兼ね備えたプロダクト
- フィードバックを重視、開発の優先度付もこれに基づく
- PressRelase/FAQを先に作る
- 6 pager/1 pager
組織
- Our Leadership Principles
- 全てのアマゾニアンが心がける信条
- リーダーシッププリンシプル
- 判断基準にもなり、共通認識によるショートカットになる
塚田さんのセッションの後は「Night - Opening Party」の準備時間までコワーキングスペースでもくもく!
この間に「Ask An Expertカウンター」でSolution Architectの方とCloudFrontの使い方について相談させて頂きました。
すぐ近くにSolution Architectの方がいるなんて、何て素晴らしい環境なんだ・・・!
自分たちで考えた対応方法なども聞いてもらい、フィードバックをもらったり、他の方法の検討を一緒にしてくれたり、濃密な時間を過ごしました。
Solution Architectの方と濃厚な時間を過ごしてしまった・・・!#AWSLoft
— 𝔈𝔩𝔞𝔰𝔱𝔦𝔠 𝔎𝔞𝔧𝔦 𝔖𝔢𝔯𝔳𝔦𝔠𝔢@魔人ママチャリライダー (@Anorlondo448) 2018年10月1日
月イチとかで、AWS Loftで作業したいなと本気で思いました。
AWSリソースの構成とか使い方とかその場で聞きながら作業できるの最強なのでは・・・
17:30からは「Night - Opening Party」!!!
シャレオツな料理と、

Amazon Dashボタンで光り方が変わる提灯wwww

スゲェ!シャレオツ!!!
ネットワーキングタイムでは、「凍らない方のいぬ」の方や、コンテナに強いポジティブな方と名刺交換できました。
二人共雲の上の存在でしたが、お話できて嬉しかった・・・・!

素敵なお土産を頂き、素晴らしい体験をさせてもらった一日でした!
Solution Architectの方以外にも、AWSにつよい人がたくさん集まりそうな予感がするので、AWS Loftで今後どのようなイベントが発生していくのかとても楽しみです!
ああ〜〜はやく次行きてぇ
Javascriptがわからないのを克服していく⑤(API Gatewayとcognitoとの連携)
概要
- 以下のチュートリアルをやりながら、Node.jsのコードを読んで理解を深める
- サーバーレスウェブアプリケーションの構築
対象
- 実際に公開するページ(ride.html)で動く以下Javascript
js/ride.js
- インラインでコメントしながら理解する
js/ride.js
メソッド一覧
- function rideScopeWrapper($)
- function requestUnicorn(pickupLocation)
- function completeRequest(result)
- function handlePickupChanged()
- function handleRequestClick(event)
- function animateArrival(callback)
- function displayUpdate(text)
function rideScopeWrapper($)
- then()はPromiseを返す
- 認証トークンの確認
- request/signoutボタンの制御
- pickupChangeイベントの定義
/*global WildRydes _config*/
// グローバル変数「WildRydes」を定義。定義済みだったら定義済みの変数を使う
var WildRydes = window.WildRydes || {};
// 「WildRydes」のmapオブジェクトを定義。こちらも同じく定義済みだったら定義済みの変数を使う
WildRydes.map = WildRydes.map || {};
// ()でくくってあるので、読み込みと同時に実行される即時関数
(function rideScopeWrapper($) {
// 認証トークン
var authToken;
// 認証トークンを取得する。取得できたらsetAuthToken()を実行する
WildRydes.authToken.then(function setAuthToken(token) {
if (token) {
// トークンがあったら認証トークンにセット
authToken = token;
} else {
// なかったらサインインページに遷移
window.location.href = '/signin.html';
}
}).catch(function handleTokenError(error) {
// exception発生したら画面にエラー表示して、サインインページに遷移
alert(error);
window.location.href = '/signin.html';
});
...
// Register click handler for #request button
$(function onDocReady() {
// requestボタンをクリックしたらhandleRequestClick()(後述)を実行するように定義
$('#request').click(handleRequestClick);
// signOutボタンをクリックしたら無名関数を実行
$('#signOut').click(function() {
// サインアウト処理
WildRydes.signOut();
// サインアウトしたメッセージを表示
alert("You have been signed out.");
// サインインページに遷移
window.location = "signin.html";
});
// 地図上で「pickupChange」eventが発生したらhandlePickupChanged()を実行
$(WildRydes.map).on('pickupChange', handlePickupChanged);
// 認証トークンを取得する。取得できたらupdateAuthMessage()(後述)を実行する
WildRydes.authToken.then(function updateAuthMessage(token) {
if (token) {
// トークンがある場合、メッセージを画面表示
displayUpdate('You are authenticated. Click to see your <a href="#authTokenModal" data-toggle="modal">auth token</a>.');
// 認証トークンにトークンをセット
$('.authToken').text(token);
}
});
// Lambdaのendpointが無かったら「#noApiMessage」のメッセージを表示
if (!_config.api.invokeUrl) {
$('#noApiMessage').show();
}
});
...
}(jQuery));
function handlePickupChanged()
- 地図がクリックされた時にボタンの属性を変える
function handlePickupChanged() {
// requestボタンのjQueryオブジェクト
var requestButton = $('#request');
// requestButtonオブジェクトのテキストに文字列をセット
requestButton.text('Request Unicorn');
// requestButtonオブジェクトの「disabled」にfalseをセット。(requestボタンの非表示を解除)
requestButton.prop('disabled', false);
}
function requestUnicorn(pickupLocation)
function requestUnicorn(pickupLocation) {
// ajax通信を行う
$.ajax({
// POSTで通信
method: 'POST',
// 通信先はAPI Gateway
url: _config.api.invokeUrl + '/ride',
// ヘッダをセット
// API Gatewayのオーサライザをセットしたときの「トークンのソース」にセットしたキー
headers: {
Authorization: authToken
},
// 地図上でクリックした地点の緯度経度情報をJSONにしたものをBodyにセット
data: JSON.stringify({
PickupLocation: {
Latitude: pickupLocation.latitude,
Longitude: pickupLocation.longitude
}
}),
// コンテンツタイプセット
contentType: 'application/json',
// 成功時の処理。completeRequest()を実行
success: completeRequest,
// エラー時の処理
error: function ajaxError(jqXHR, textStatus, errorThrown) {
// エラーログを出力して、エラー画面を表示
console.error('Error requesting ride: ', textStatus, ', Details: ', errorThrown);
console.error('Response: ', jqXHR.responseText);
alert('An error occured when requesting your unicorn:\n' + jqXHR.responseText);
}
});
}
function completeRequest(result)
function completeRequest(result) {
var unicorn; // Unicornオブジェクト格納変数
var pronoun; // 代名詞?
// API Gatewayから受信した結果をログ出力
console.log('Response received from API: ', result);
// 結果からUnicornオブジェクトをセット
unicorn = result.Unicorn;
// Unicornの性別によって、メッセージに表示する代名詞をセット
pronoun = unicorn.Gender === 'Male' ? 'his' : 'her';
// Unicornの名前、色などをメッセージに表示
displayUpdate(unicorn.Name + ', your ' + unicorn.Color + ' unicorn, is on ' + pronoun + ' way.');
// animateArrival()(後述)を実行
animateArrival(function animateCallback() {
// Unicornが来たというメッセージを表示
displayUpdate(unicorn.Name + ' has arrived. Giddy up!');
// 地図上の緯度経度を外す
WildRydes.map.unsetLocation();
// requestボタンを非表示
$('#request').prop('disabled', 'disabled');
// requestボタンの表示を「Set Pickup」に変更
$('#request').text('Set Pickup');
});
}
function handleRequestClick(event)
- requestボタンがクリックされたときの挙動
function handleRequestClick(event) {
// 地図の選択された地点の緯度経度を取得
var pickupLocation = WildRydes.map.selectedPoint;
// eventの処理を停止
event.preventDefault();
// requestUnicorn()を実行
requestUnicorn(pickupLocation);
}
function animateArrival(callback)
- 選択した地点に現在地をセット
function animateArrival(callback) {
// 地図の選択された地点の緯度経度を取得
var dest = WildRydes.map.selectedPoint;
// 現在地をリセット
var origin = {};
// 選択された地点に現在地をセット
if (dest.latitude > WildRydes.map.center.latitude) {
//
origin.latitude = WildRydes.map.extent.minLat;
} else {
origin.latitude = WildRydes.map.extent.maxLat;
}
if (dest.longitude > WildRydes.map.center.longitude) {
origin.longitude = WildRydes.map.extent.minLng;
} else {
origin.longitude = WildRydes.map.extent.maxLng;
}
// 地図の描画
WildRydes.map.animate(origin, dest, callback);
}
function displayUpdate(text)
function displayUpdate(text) {
// メッセージフィールドにテキストを表示
$('#updates').append($('<li>' + text + '</li>'));
}
わかったこと
- API GatewayのオーサライザにAWS Cognitoを指定しておけば、Cognitoから取得したトークンを使ってAPI Gatewayの実行が簡単にできる
- Javascript側でやっていることはトークンの有無の確認だけっぽい
わからなかったこと
- 認証・認可って何?(裏で全部やってくれてるっぽい)
Javascriptがわからないのを克服していく④(地図上の位置情報を取得してオブジェクトを移動する)
概要
- 以下のチュートリアルをやりながら、Node.jsのコードを読んで理解を深める
- サーバーレスウェブアプリケーションの構築
対象
- 実際に公開するページ(ride.html)で動く以下Javascript
js/esri-map.js
- インラインでコメントしながら理解する
js/esri-map.js
- 地図上でピンやUnicorn画像を動かすための処理
- Tokenなどの処理はなかった
全体
/*global WildRydes _config*/
// グローバル変数「WildRydes」を定義。定義済みだったら定義済みの変数を使う
var WildRydes = window.WildRydes || {};
// 「WildRydes」のmapオブジェクトを定義。こちらも同じく定義済みだったら定義済みの変数を使う
WildRydes.map = WildRydes.map || {};
// ()でくくってあるので、読み込みと同時に実行される即時関数
(function esriMapScopeWrapper($) {
...
}(jQuery));
require.js
- require.jsというのを使って、実行するfunctionに依存モジュールを渡しているらしい
- @see https://qiita.com/opengl-8080/items/196213867b859daea719
- ESRIは地図系の会社の模様
// esriのモジュールをrequireして、requireCallback()を実行する
require([
'esri/Map',
'esri/views/MapView',
'esri/Graphic',
'esri/geometry/Point',
'esri/symbols/TextSymbol',
'esri/symbols/PictureMarkerSymbol',
'esri/geometry/support/webMercatorUtils',
'dojo/domReady!'
], function requireCallback(
...
) {
...
});
requireCallback()
- 地図上の位置情報を取得して、ピンやUnicorn画像を移動させる処理
function requireCallback(
// 引数はrequireしたオブジェクト達
Map, MapView,
Graphic, Point, TextSymbol,
PictureMarkerSymbol, webMercatorUtils
) {
// グローバル変数のmapオブジェクト
var wrMap = WildRydes.map;
// 「gray-vector」っていうフリーのマップを使ってmapオブジェクト生成
var map = new Map({ basemap: 'gray-vector' });
// 地図表示用オブジェクト生成
var view = new MapView({
center: [-122.31, 47.60],
container: 'map',
map: map,
zoom: 12
});
// 地図上に配置するピンのオブジェクト生成
var pinSymbol = new TextSymbol({
color: '#f50856',
text: '\ue61d',
font: {
size: 20,
family: 'CalciteWebCoreIcons'
}
});
// ピンに向かってくるUnicornのオブジェクト生成
var unicornSymbol = new PictureMarkerSymbol({
url: 'images/unicorn-icon.png',
width: '25px',
height: '25px'
});
var pinGraphic;
var unicornGraphic;
// 与えられた緯度経度を地図の中心に持ってくる
function updateCenter(newValue) {
wrMap.center = {
latitude: newValue.latitude,
longitude: newValue.longitude
};
}
// 与えられた数値を基に、表示領域の更新
function updateExtent(newValue) {
var min = webMercatorUtils.xyToLngLat(newValue.xmin, newValue.ymin);
var max = webMercatorUtils.xyToLngLat(newValue.xmax, newValue.ymax);
wrMap.extent = {
minLng: min[0],
minLat: min[1],
maxLng: max[0],
maxLat: max[1]
};
}
// 地図を表示
view.watch('extent', updateExtent);
view.watch('center', updateCenter);
view.then(function onViewLoad() {
updateExtent(view.extent);
updateCenter(view.center);
});
// 地図上でクリックされたら、eventを取得してhandleViewClick()を実行
view.on('click', function handleViewClick(event) {
// 地図上のクリックした地点を取得
wrMap.selectedPoint = event.mapPoint;
// ピンを削除
view.graphics.remove(pinGraphic);
// クリックした地点に新しいピンを生成
pinGraphic = new Graphic({
symbol: pinSymbol,
geometry: wrMap.selectedPoint
});
// 地図に新しいピンを追加
view.graphics.add(pinGraphic);
// 「pickupCahge」eventを発生させる
// event発生時の処理はride.jsのhandlePickupChanged()
$(wrMap).trigger('pickupChange');
});
// アニメーション関数オブジェクト生成
wrMap.animate = function animate(origin, dest, callback) {
// 開始時刻
var startTime;
// animateFrame()関数を変数に入れる
var step = function animateFrame(timestamp) {
var progress;
var progressPct;
var point;
var deltaLat;
var deltaLon;
// 開始時刻がなければ現在時刻を設定
if (!startTime) startTime = timestamp;
// 経過時間
progress = timestamp - startTime;
// 「経過時間/2000」と「1」の小さい方
progressPct = Math.min(progress / 2000, 1);
// 移動先と現在地の緯度経度の差分に上記計算したprogressPctを乗算
deltaLat = (dest.latitude - origin.latitude) * progressPct;
deltaLon = (dest.longitude - origin.longitude) * progressPct;
// 移動先の地点のオブジェクトを生成
point = new Point({
longitude: origin.longitude + deltaLon,
latitude: origin.latitude + deltaLat
});
// Unicorn画像情報を削除
view.graphics.remove(unicornGraphic);
// 新しい位置情報を持ったUnicorn画像を生成
unicornGraphic = new Graphic({
geometry: point,
symbol: unicornSymbol
});
// Unicorn画像を追加
view.graphics.add(unicornGraphic);
// progressPctが1以下の場合、ブラウザ上でアニメーションを行う
// @see https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame
if (progressPct < 1) {
requestAnimationFrame(step);
} else {
callback();
}
};
// ブラウザ上でアニメーションを行う
requestAnimationFrame(step);
};
// 位置情報が設定されていない場合、ピンを削除
wrMap.unsetLocation = function unsetLocation() {
view.graphics.remove(pinGraphic);
};
}
参考にさせてもらったページ
今回の収穫
- グローバル変数の定義方法
- require.jsでfunctionで使うmoduleを管理する
JavascriptとCognitoとAPI Gatewayがわからないのを克服していく③(API GatewayとcognitoとLambdaの連携)
概要
- 以下のチュートリアルをやりながら、Node.jsのコードを読んで理解を深める
- サーバーレスウェブアプリケーションの構築 モジュール 3 サーバーレスサービスバックエンド
- サーバーレスウェブアプリケーションの構築 モジュール 4 RESTful API
モジュール 3、4で使っている技術
(おまけ)サービス名がAWSで始まったりAmazonで始まったり
- 真偽は確かではないが、以下って聞いたことがある
モジュール 3:サーバーレスサービスバックエンドの概要
- Lambda/DynamoDBを使ってWebアプリケーションのrequestを処理するためのバックエンドプロセスを構築
- 手順通りにやれば問題なし
- Lambdaのコードは後で読む(後述))
モジュール 4:RESTful API
- モジュール3で作成したLambdaをAPI Gatewayで公開する
- インターネット越しにパブリックアクセスできるが、Cognitoユーザプールで保護される
- CognitoユーザプールAuthorizer作成でエラーが発生した
レスポンス Response Code(応答コード): 401 レイテンシー 153 Unauthorized request: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx
- 理由はわからないがAuthorizerを再作成して「Cognito ユーザープール」を入力しないでプルダウンで選択する形にしたら動いた。。。
API Gateway設定で気になったところ
- endpoint typeの種類
- リージョン: 現在のリージョンにデプロイされる
- エッジ最適化: CloudFrontネットワークにデプロイされる。地理的に分散したクライアントへの接続時間が改善される
- プライベート: VPCからのみアクセス可能
Authorizer
resource
method
- 「Lambda プロキシ統合の使用」
- requestはLambdaにプロキシされる
- requestの詳細がhandler関数の「event」で参照できるようになる
プリフライトリクエスト
Lambdaに付与するIAMロールについて
- いつもLambdaを作る時に何となく付与していた「AWSLambdaBasicExecutionRole」を見てみる
- CloudWatch Logs関連のロールしかついてなかった
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
インラインポリシー
- IAMポリシー一覧には出てこない模様
- 設定したIAMロールにのみ表示
メインディッシュ
Lambdaのコードを読む
- requestUnicorn.js
- またインラインにコメントを描いていく
全体の構成
- const(定数定義)
- exports.handler
- function findUnicorn()
- function recordRide()
- function toUrlString()
- function errorResponse()
const(定数定義)
- lambdaで使う定数の定義ね
// Node.jsでセキュアのランダムな文字列を生成するクラスのオブジェクトを生成
const randomBytes = require('crypto').randomBytes;
// 我らがaws-sdkの読み込み
const AWS = require('aws-sdk');
// DynamoDBのドキュメント管理をするクライアントオブジェクトを生成
// @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html
const ddb = new AWS.DynamoDB.DocumentClient();
// fleetって、艦隊?
// Map状を動くUnicornの名前とかのデータJSON
// 「Rocinante」は・・・俺の地元のingress agentの名前
const fleet = [
{
Name: 'Bucephalus',
Color: 'Golden',
Gender: 'Male',
},
{
Name: 'Shadowfax',
Color: 'White',
Gender: 'Male',
},
{
Name: 'Rocinante',
Color: 'Yellow',
Gender: 'Female',
},
];
callback関数について
- Node.js v6.10とv8.10はオプションでcallbackパラメータをサポートしている
- 第一引数はLambda関数に失敗した実行結果を提供する
- @see https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-handler.html
function errorResponse()
- エラーレスポンスをコールバックするための関数の模様
// 引数
// errorMessage: エラーメッセージ
// awsRequestId: AWSへのリクエストID
// callback: コールバック関数
function errorResponse(errorMessage, awsRequestId, callback) {
// コールバック関数に以下を渡す
// Lambda関数の失敗の実行結果: null
// 関数の実行結果
callback(null, {
// ステータスコード500
statusCode: 500,
// エラーメッセージとリクエストIDのJSON
body: JSON.stringify({
Error: errorMessage,
Reference: awsRequestId,
}),
// ヘッダー
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}
function toUrlString()
function toUrlString(buffer) {
return buffer.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
exports.handler
// @see https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-handler.html
//
// Lambda関数のハンドラーの引数
// event: Lambdaが受信したイベントオブジェクト
// context: Lambda実行中のランタイム情報オブジェクト(関数の残り実行時間とか) @see https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-prog-model-context.html
// callback: 明示的に呼び出し元にcallbackする場合のメソッド
exports.handler = (event, context, callback) => {
// API Gatewayから認証情報が入力eventに入っているっぽい
// @see https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
if (!event.requestContext.authorizer) {
// 認証情報が無かったらerrorResponse()をコールして終了
errorResponse('Authorization not configured', context.awsRequestId, callback);
return;
}
// ランダムな文字列からrideIdを生成
const rideId = toUrlString(randomBytes(16));
// rideIdをログ出力
console.log('Received event (', rideId, '): ', event);
// Because we're using a Cognito User Pools authorizer, all of the claims
// included in the authentication token are provided in the request context.
// 私達はCognitoユーザープール認証を使っているため、認証トークンに含まれるclaimsの全てはrequestコンテキストで提供される
// This includes the username as well as other attributes.
// これはユーザ名や他の属性も含んでいる
//
// →つまり、Lambdaに渡される認証トークンでCognitoユーザプールのユーザ情報が取れる
const username = event.requestContext.authorizer.claims['cognito:username'];
// The body field of the event in a proxy integration is a raw string.
// プロキシ統合(API Gateway?)のイベントのフィールドのBodyは、生の文字列です
//
// In order to extract meaningful values, we need to first parse this string
// into an object.
// 意味のある値を抽出するには、まず。この文字列を解析してオブジェクトにする必要がある。
//
// A more robust implementation might inspect the Content-Type
// header first and use a different parsing strategy based on that value.
// より堅牢な実装では最初にContent-Typeヘッダを解析して、その値に寄って異なる方法の解析ストラテジを使用します
//
// →API Gatwayから渡される値の解析方法について書かれているっぽい
// イベントの本文をパースする
const requestBody = JSON.parse(event.body);
// Mapをクリックした地点から緯度経度を取得
const pickupLocation = requestBody.PickupLocation;
// 移動してくるUnicornを取得
const unicorn = findUnicorn(pickupLocation);
// recordRide()を実行
recordRide(rideId, username, unicorn).then(() => {
// You can use the callback function to provide a return value from your Node.js Lambda functions.
// Node.jsのLambda関数からの値のreturnを提供するためのこのコールバック関数を使うことができる
// The first parameter is used for failed invocations.
// 最初のパラメータは失敗時の呼び出しに使われる
// The second parameter specifies the result data of the invocation.
// 2番めのパラメータは呼び出しの結果データを出力する
// Because this Lambda function is called by an API Gateway proxy integration
// the result object must use the following structure.
// Lambda関数はAPI Gatewayのプロキシ統合から呼び出されるため、結果オブジェクトは次のような構造を使う必要がある
callback(null, {
statusCode: 201,
body: JSON.stringify({
RideId: rideId,
Unicorn: unicorn,
UnicornName: unicorn.Name,
Eta: '30 seconds',
Rider: username,
}),
headers: {
'Access-Control-Allow-Origin': '*',
},
});
}).catch((err) => {
console.error(err);
// If there is an error during processing, catch it and return
// from the Lambda function successfully. Specify a 500 HTTP status
// code and provide an error message in the body. This will provide a
// more meaningful error response to the end client.
errorResponse(err.message, context.awsRequestId, callback)
});
};
function findUnicorn()
// This is where you would implement logic to find the optimal unicorn for
// this ride (possibly invoking another Lambda function as a microservice.)
// For simplicity, we'll just pick a unicorn at random.
function findUnicorn(pickupLocation) {
// ログに緯度経度を出力
console.log('Finding unicorn for ', pickupLocation.Latitude, ', ', pickupLocation.Longitude);
// ランダムに移動するUnicornを選出
return fleet[Math.floor(Math.random() * fleet.length)];
}
function recordRide()
function recordRide(rideId, username, unicorn) {
// DynamoDBにputした結果を返す
return ddb.put({
// Ridesテーブルに
TableName: 'Rides',
// 以下のItemを登録
Item: {
RideId: rideId, // ID
User: username, // ユーザ名
Unicorn: unicorn, // 移動したUnicornオブジェクト
UnicornName: unicorn.Name, // 移動したUnicornの名前
RequestTime: new Date().toISOString(), // requestされた時間
},
}).promise();
}
参考にさせてもらったブログなど
- AWS Doc
- https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control#Preflighted_requests
- https://qiita.com/poruruba/items/86668c9c63844e1afd32
今回の収穫
- Javascriptのコードをしっかり読めばそれほど難しいことはしていない。(aws-sdk最高)
- API GatewayとCognitoの連携が結構簡単にできる
- API GatewayからLambdaに渡されるeventに認証情報とかも入っている
- API GatewayでCORSが可能
- API Gatewayの設定項目が多くて、まだまだ奥が深そう
わからなかったこと
- tokenをどこで使うか・・・
Javascriptがわからないのを克服していく②(cognitoと連携したユーザSignIn)
概要
今回
- singinページのjavascriptを読む
- 対象は同じ
js/cognito-auth.js
前回と同じ様の読めるところすっ飛ばし
- signin.htmlで
js/cognito-auth.js読み込み - signin.htmlのsigninFormがsubmitされるとhandleSignin()が実行される
- signin()に、成功/失敗時のfunctionを渡して実行
- 成功:「Successfully Logged In」と表示してride.htmlへ繊維
- 失敗:エラーをalert()で表示
signin()
function signin(email, password, onSuccess, onFailure) {
// メールアドレスとパスワードから、Cognitoの認証用データオブジェクトを生成する
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails({
Username: email,
Password: password
});
// createCognitoUser()(後述)でcognitoユーザオブジェクトを生成する
var cognitoUser = createCognitoUser(email);
// 上記の認証用データオブジェクトで認証処理を行う。詳細は以下(簡単に言うと認証してcallbackしている)
// @see https://github.com/aws-amplify/amplify-js/blob/master/packages/amazon-cognito-identity-js/lib/CognitoUser.js#L253
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: onSuccess,
onFailure: onFailure
});
}
createCognitoUser()
- インラインでやってることを推測しながらコメントを追加
function createCognitoUser(email) {
// ユーザプールオブジェクトとメールアドレスから、Cognitoユーザオブジェクトを生成する
return new AmazonCognitoIdentity.CognitoUser({
Username: email,
Pool: userPool
});
}
本日javascriptと和解できたところ
- 基本フローは前回とほとんど一緒なのでスラスラ読めた!
- 読めるようになると楽しさが増してくる
本日和解できなかったところ
- CoginitoのJavascript SDKのマニュアルが見つからない・・・
- おそらくAWS AMplifyのライブラリを使っているのだろうけど・・・どこだ?!
Javascriptがわからないので少しずつコード読んで和解していく(cognitoと連携したユーザ登録)
概要
- サーバーレスウェブアプリケーションの構築をやっていた
- 「モジュール2 ユーザ管理」通りにやっていたら、あまりにも簡単に認証ページが作成できた
- が、AWS CognitoとJavascriptがどう動いているか全くわからず・・・
- そろそろちゃんとJavascriptと向き合わないと行けないなと思い、コードを読んで理解することにした
コード
コード読んだ対象
- ユーザ登録を行っているregister.htmlで読み込んでるjavascript
長くなってしまったので、先にユーザ登録フローのまとめ
- register.htmlで登録フォーム表示
- メールアドレス
- パスワード
- パスワード確認
- submitすると
js/cognito-auth.jsのhandleRegister()が実行される - handleRegister()で以下の処理を実行
- フォームの値を取得する
- ユーザ登録処理成功/失敗時の処理を定義
- ユーザ登録処理(register())を実行
- register()で以下の処理
- ユーザの登録データオブジェクトを生成
- cognito-sdkのAmazonCognitoIdentityクラスを使ってcognitoにユーザを登録する
register.html
- SignUpページ
- メールアドレスと、パスワード2回入れる
- formのデータをsubmitしたときの処理はどこかのJSに書いてあるっぽい
- JS読み込みは以下の箇所
<script src="js/vendor/jquery-3.1.0.js"></script> <script src="js/vendor/bootstrap.min.js"></script> <script src="js/vendor/aws-cognito-sdk.min.js"></script> <script src="js/vendor/amazon-cognito-identity.min.js"></script> <script src="js/config.js"></script> <script src="js/cognito-auth.js"></script>
js/vendor/配下はおそらくjQuery、bootstrapと、cognitoのSDK用のライブラリjs/config.jsはcognitoに接続するためのプールIDやアプリクライアントIDの設定用js/cognito-auth.jsが怪しいので見ていく
bootstrap
- Twitter社が開発したCSSフレームワーク
- Webデザインの知識がなくてもOK!Bootstrapの使い方【入門者向け】
- このページのデザイン関連で使ってると推測(cognitoと無関係)
- 使用するためにjqueryが必要
js/cognito-auth.js が呼ばれるときに実行される処理ってどれ?
- 関数の最初と最後だけ抜き出してみる
(function scopeWrapper($) {
..[省略]..
}(jQuery));
- functionが()でかこまれてるのは「即時関数」にしている
- 即時関数: 関数を定義すると同時に実行する
- つまり、
js/cognito-auth.jsが読み込まれると同時に上記関数は実行される
- 後述の「userPoolオブジェクト生成」「onDocReady()」とかもここで実行されている
- 引数で「jQuery」が渡されて、引数定義の「$」がjQueryオブジェクトとなる
- つまり、「function scopeWrapper」内の「$」はjQueryオブジェクト
userPoolオブジェクト生成
js/cognito-auth.jsの呼び出し時にオブジェクト生成している- インラインでやってることを推測しながらコメントを追加
(function scopeWrapper($) {
// サインイン用のページ
var signinUrl = '/signin.html';
// config.jsからAWS Cognitoユーザプールの認証情報を取得
var poolData = {
UserPoolId: _config.cognito.userPoolId,
ClientId: _config.cognito.userPoolClientId
};
// userPoolオブジェクト定義
var userPool;
// ユーザプールID、アプリクライアントID、リージョンがセットされてなかったら、
// register.html内にある「noCognitoMessage」部分を表示(エラーメッセージ的なやつ)
if (!(_config.cognito.userPoolId &&
_config.cognito.userPoolClientId &&
_config.cognito.region)) {
$('#noCognitoMessage').show();
return;
}
// 認証情報から、Cognitoユーザプールオブジェクトを生成
userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
- 大まかに
- config.jsの情報からユーザプールオブジェクトを生成
js/cognito-auth.jsのonDocReady()メソッド
- formのsubmitの挙動に関する定義っぽい箇所があった
/*
* Event Handlers
*/
$(function onDocReady() {
$('#signinForm').submit(handleSignin);
$('#registrationForm').submit(handleRegister);
$('#verifyForm').submit(handleVerify);
});
- submit()メソッド
- フォームにおいて、データ送信時に引数のファンクションを実行する
- http://www.jquerystudy.info/reference/events/submit.html
- handleRegister()というメソッドにregistrationFormをsubmitしたときの処理が書いてあるっぽい
handleRegister()メソッド
- submitされると実行される
- インラインでやってることを推測しながらコメントを追加
// 画面上のイベントを引数でもらう
function handleRegister(event) {
// 「registrationForm」にあったパラメータの値を変数に入れる
var email = $('#emailInputRegister').val(); // メールアドレス
var password = $('#passwordInputRegister').val(); // パスワード
var password2 = $('#password2InputRegister').val(); // パスワード(確認用)
// 後ろにあるregister()メソッドの引数として、register()が成功したときに実行するfunctionを定義している
var onSuccess = function registerSuccess(result) {
// おそらく成功した場合にresultにユーザ情報が入っている
var cognitoUser = result.user;
// コンソールにユーザ名を表示
console.log('user name is ' + cognitoUser.getUsername());
// 「ユーザ登録成功してメール送ったから確認しろよ、認証コード入っているから、スパムフォルダとか見てね」的な確認メッセージを定義
var confirmation = ('Registration successful. Please check your email inbox or spam folder for your verification code.');
// 確認メッセージがあったら(っていうか上で定義しているから絶対あるのでは・・・)、verify.htmlに画面遷移する
if (confirmation) {
window.location.href = 'verify.html';
}
};
// 後ろにあるregister()メソッドの引数として、register()が失敗したときに実行するfunctionを定義している
var onFailure = function registerFailure(err) {
// エラーを表示
alert(err);
};
// eventの抑制。おそらくsubmitの処理をここで止めている。
// register()にわたす引数(function)内で画面遷移を行っているから、submitによる画面遷移処理が不要なんじゃないかな?
event.preventDefault();
// パスワードとパスワード確認が同一だったら
if (password === password2) {
// register()メソッド(おそらくユーザ登録処理)に処理を渡す
// 引数は
// email: 入力されたメールアドレス
// password: 入力されたパスワード
// onSuccess: 成功時に実行するfunction
// onFailure: 失敗時に実行するfunction
register(email, password, onSuccess, onFailure);
} else {
// パスワード一致しなかったらアラート出して終わり(submitは止められているので元の画面のまま)
alert('Passwords do not match');
}
}
- 大まかに書くと、以下のような感じ
- フォームの値を取得
- ユーザ登録処理(register)の成功時に実行するfunctionを定義する
- ユーザ登録処理(register)の失敗時に実行するfunctionを定義する
- submitを止める
- パスワード一致確認して、ユーザ登録処理を実行
register()メソッド
- インラインでやってることを推測しながらコメントを追加
/*
* Cognito User Pool functions
*/
// 引数4つ受け取る
// email: 入力されたメールアドレス
// password: 入力されたパスワード
// onSuccess: 成功時に実行するfunction
// onFailure: 失敗時に実行するfunction
function register(email, password, onSuccess, onFailure) {
// メールアドレスデータの連想配列
var dataEmail = {
Name: 'email',
Value: email
};
// AmazonCognitoIdentityクラスにメールアドレスデータをセットしたオブジェクトを生成してるっぽい
// @see https://github.com/aws-amplify/amplify-js/blob/bfa477662591bbd7a92bbb8486778924589e4adb/packages/amazon-cognito-identity-js/es/CognitoUserAttribute.js#L21
var attributeEmail = new AmazonCognitoIdentity.CognitoUserAttribute(dataEmail);
// userPoolオブジェクト(後述)のsignUpメソッドに以下の引数を渡している
// メソッド自体はここか? @see https://github.com/aws-amplify/amplify-js/blob/bfa477662591bbd7a92bbb8486778924589e4adb/packages/amazon-cognito-identity-js/es/CognitoUserPool.js#L93
// {string} username: 入力されたメールアドレス(email)
// {string} password: 入力されたパスワード(password)
// {(AttributeArg[])=} userAttributes: 登録するユーザの属性情報配列([attributeEmail])
// {(AttributeArg[])=} validationData: バリデーションデータを入れるっぽいが、ここはnull
// {nodeCallback<SignUpResult>} callback: コールバック用の関数定義(function signUpCallback)
userPool.signUp(email, password, [attributeEmail], null,
function signUpCallback(err, result) {
if (!err) {
// エラーじゃなかったら成功時に実行するfunctionを実行
onSuccess(result);
} else {
// エラーだったら成功時に実行するfunctionを実行
onFailure(err);
}
}
);
}
- 大まかに書くと、以下のような感じ
- 引数からユーザ情報に登録するデータオブジェクトを作成
- AmazonCognitoIdentityオブジェクトに登録データとコールバック用の関数を渡す
- AmazonCognitoIdentityがsignUp処理を実行して、結果によってfunctionを実行
- cognitoへの登録はAmazonCognitoIdentityがやってくれる模様
本日javascriptと和解できたところ
Elasticsearchを触りながら調べたことのメモ
概要
Amazon Elasticsearch Service Domainって何?
Amazon Elasticsearch Service ドメインは、Amazon Elasticsearch Service コンソール、CLI、 または API を使用して作成する Elasticsearch クラスターです。 各ドメインは、指定した コンピューティング リソースおよびストレージリソースを備えたクラウド内の Elasticsearch クラスターとなります。 お客様はドメインの作成や削除、インフラストラクチャ属性の定義、アクセスやセキュリティの制御が可能です。 Amazon Elasticsearch Service ドメインは 1 つまたは複数実行できます。
- つまりこれ
「Elasticsearch Service domain」は、Elasticsearchクラスターの実行に必要なすべてのリソースのコレクションです。
環境構築
- terraformでAmazon Elasticsearch Serviceを構築
- Elasticsearch触るだけならローカルでコンテナ動かしてもよかったかもしれない
- 上記つぶやいたら本業の人からコメントもらえた!
よく考えたらElasticsearch触るだけならAWS使わなくてもコンテナでいいな
— 𝔈𝔩𝔞𝔰𝔱𝔦𝔠 𝔎𝔞𝔧𝔦 𝔖𝔢𝔯𝔳𝔦𝔠𝔢@魔人ママチャリライダー (@Anorlondo448) 2018年9月19日- Javaが動く環境でtgz解凍→bin/elasticsearchで起動できる
- 軽量で良いらしい・・・!
データ登録・検索
- RESTで可能
- 登録
- /blogs/article/1
- 「blogs」というインデックスに
- 「article」というドキュメントを登録する
- 「1」「2」「3」は、登録された個々のドキュメントのID
- /blogs/article/1
curl -H "Content-Type: application/json" -XPUT https://[es domain]/blogs/article/1 -d '
{
"title": "Title First",
"content": "I ate a ramen",
"tags": ["dev", "calory", "Sugar"]
}
'
curl -H "Content-Type: application/json" -XPUT https://[es domain]/blogs/article/2 -d '
{
"title": "Title Second",
"content": "I brunk a beer",
"tags": ["dev", "alcohol", "purintai"]
}
'
curl -H "Content-Type: application/json" -XPUT https://[es domain]/blogs/article/3 -d '
{
"title": "Title Third",
"content": "I go to bed",
"tags": ["off", "sleep", "heeling"]
}
'
- データ取得
- 「content」フィールドが「beer」
$ curl -XGET https://[es domain]/_search?q=content:beer
{
"title": "Title Second",
"content": "I brunk a beer",
"tags": ["dev", "alcohol", "purintai"]
}
}]}}
- インデックス削除
- 一発でINDEXごと消せるのちょっと怖い
$ curl -XDELETE https://[es domain]/blogs
mappingとは
- RDBでいうテーブル定義
- データの型によって自動でmappingしてくれる
- 検索の要件によっては、自動でmappingしないで手動でやる
- INDEXに既にデータが入っている場合、mappingの変更はできない
- Updating existing field mappings
- フィールドの追加はできる
変更したい場合はaliasを使って、既存のmappingから新しいmapping定義に移行させる方法を取る
mapping取得
curl -XGET https://[es domain]/blogs/_mapping
{
"blogs": {
"mappings": {
"article": {
"properties": {
"content": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"tags": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
indexオプション
- 指定なしの場合は
analyzedが適用(解析する) not_analyzedの場合、解析しない
analyzerオプション
- データ解析してくれて、検索に使う
- 形態素解析とか
- Standard Analyzer
わかったこと
- Amazon Elasticsearch Serviceの「domain」はElasticsearchクラスターの実行に必要なすべてのリソースのコレクション
- 登録/検索がRESTで簡単にできるの素晴らしい
- 自動でmappingしてくれる
- フィールドの形態素解析とかやってくれるのとても便利
- 以下2つから、マスターデータは別途持っておいたほうが良さそう
- Elasticsearchのmapping変更はできない。aliasを使って新しいmappingに移行する
curl -XDELETEでINDEXごと消える。