Git のコミットメッセージに Semantic Commit Messages のテンプレートを追加する
Git のコミットメッセージ周りで毎回気になって調べているのでメモとしてまとめておく。
よく発生する問題
Git のコミット周りでは主に下記の 2 つの内容が問題として上がってくることが多い。
1. Git のコミットメッセージが個人によってばらつきが出てくる。
実際にどういう修正をしたのかを具体的にコミットメッセージに書いてほしいが、その時の状況によって特に意味のないメッセージでコミットしてしまうことがよくある気がする。
開発中には特に問題になることはないが、あとからコミットメッセージを見返すとき(例えば障害の原因調査など)にどんな修正をしたのかがコミットメッセージからは判別できなくなるという状況になってしまう。
2. 導入したコミットフォーマットを導入しても普段の開発で必ず遵守されるとは限らない。
上記の問題を解決するためにコミットのテンプレートを用意したとしても、必ずそれに従って書いてくれるかというとそれは別の問題となってくる。
よくありがちなのは README にルールは書いてあるが書いてあるということを知らなかった、ということが起きる。新規参画者向けに導入マニュアルを準備して「これに従ってね」と書いても見るのは最初の 1 度のみで開発を進めていくにつれ内容を忘れてしまうということが起きる。
解決したいこと
これを解決するためには極力ルール化による手作業に頼らず、開発フローの中で目につくところにルールを差し込んでおけるようにしておきたい。具体的には下記の 2 点を対応することである程度の問題が回避できるはずだと思っている。
- コミットメッセージを分かりやすくするためにフォーマットを導入する
- コミットの際に入力フォーマットを確認できるようにする。
1. コミットのフォーマットを導入する
1つ目のコミットメッセージのばらつきに関してはフォーマットを導入することで平準化することができる。自分たちでフォーマットを考えてもよいが、既にわかりやすい一般化されたルールがあるのでそれに従うのがわかりやすい。
Semantic Commit Messages · GitHub
ざっくりと説明するとコミットメッセージの prefix としてコミットタイプを記載することで「このコミットがどういう内容を含んでいるのか」を表現する。(詳細はリンク先を参照)
+ feat: (new feature for the user, not a new feature for build script) + fix: (bug fix for the user, not a fix to a build script) + docs: (changes to the documentation) + style: (formatting, missing semi colons, etc; no production code change) + refactor: (refactoring production code, eg. renaming a variable) + test: (adding missing tests, refactoring tests; no production code change) + chore: (updating grunt tasks etc; no production code change)
今までのプロジェクトでもこのようなルールは使っていたが、具体的に「Semantic Commit Messages」という名前がついているのは知らなかった。
2. コミット時に入力フォーマットを確認できるようにする
コミットルールを周知させるために Git の Commit Template
という機能を利用する。
テンプレートファイルの作成
Git の config で対象ファイルをコミットテンプレートとして使用するように設定しておくことで、コミット時のデフォルトメッセージとしてセットされる。ファイル名には特に指定はないので .git_commit_template
という名前でファイルを作成する。凡例はコメントアウトとして認識させるために先頭に #
を追加しておく。
# feat: (new feature for the user, not a new feature for build script) # fix: (bug fix for the user, not a fix to a build script) # docs: (changes to the documentation) # style: (formatting, missing semi colons, etc; no production code change) # refactor: (refactoring production code, eg. renaming a variable) # test: (adding missing tests, refactoring tests; no production code change) # chore: (updating grunt tasks etc; no production code change)
テンプレートとして認識させるための設定
このファイルをテンプレートとして使用するには下記のコマンドを実行する。
git config commit.template ~/.git_commit_template
自分のルールとして必ず適用させたい場合は --global
で一律設定してしまってもよいが、プロジェクトによってルールが異なることがほとんどだと思うので、プロジェクトごとのリポジトリ内にテンプレートファイルを配置してプロジェクト単位で設定するほうが良いと思う。
実際のコミット
この状態で git commit
を実行すると下記のように、
git commit template で指定した内容 ← ここが今回追加した箇所 git status の結果(デフォルトで表示されるもの)
という順番で表示されるようになる。コメントアウト部分はコミット時に無視されるので、先頭部分にフォーマットに従ってコミットメッセージを記述して保存するだけで良い。
# feat: (new feature for the user, not a new feature for build script) # fix: (bug fix for the user, not a fix to a build script) # docs: (changes to the documentation) # style: (formatting, missing semi colons, etc; no production code change) # refactor: (refactoring production code, eg. renaming a variable) # test: (adding missing tests, refactoring tests; no production code change) # chore: (updating grunt tasks etc; no production code change) # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # On branch master # Your branch is up to date with 'origin/master'. # # Changes to be committed: # new file: git/Git-Commit-Template.md #
まとめ
これである程度のルール化と周知ができるようになる。
ただ、初回の commit.template
の設定だけは必ずやってもらう必要があるので README に書いておくとか初回のセットアップマニュアルに組み込むなりで対応する必要があるのでご注意を。
2019 年振り返りと 2020 年の目標
年末年始ということで 2019 年の振り返りと 2020 年の目標を書いておこうと思います。
2019 年の振り返り
アウトプット
数自体は少ないですが、2018 年度がほとんどアウトプットできていなかったので、それに比べれば多少は進歩したかなと。 そもそもブログのエントリー自体も少ないので 2020 年はもっとアウトプットを増やしていきたいです。 いつも仕事に追われてバタバタしてしまうことが多いので、まずはアウトプットする時間が捻出できるように余裕をもって生きていきたいですね。
約9カ月でリリース、目標の2倍の成果――“品質”と“納期”を両立させる新規プロダクト開発でチームを崩壊させない方法 www.atmarkit.co.jp
未公開 - エンジニア交流会での LT
AWS サービスを使って社内業務システムと連携する ( Recruit Engineers Advent Calendar 2019 の 13 日目のエントリー ) hiro14aki.hatenablog.com
技術的なところ
- Terraform
アドベントカレンダーで書いたとおり AWS サービスを使って外部システムとガッツリ連携していくところで Terraform を使って環境構築をしました。 既存のサービスを動かしつつその横に連携用の環境を増築していく方針にしたので「既存に影響を与えないように構築していく」という観点で 新規構築とはまた違った方向のスキルを身につけることができたかなと思ってます。 今はアプリケーションエンジニアですが前職ではインフラエンジニアだったので、そのときに勉強した知識がベースになってるなーってのは結構感じました。 エンジニアだと勉強したスキルは無駄にはならないので継続してどんどん勉強していきたいですね。
- React 関連
副業でお手伝いさせていただいている企業で下記のライブラリでいろいろと実装させてもらいました。 このあたりのライブラリで実サービスに組み込むまでできたのは良かったです。 本業の方は toB のサービスなのでインタラクションは正直そこまで求められることはないのですが、 こういった toC のサービスで見た目とか UX も意識した実装ができるのはフロントエンドをやっていく上では重要ですね。
- Clean Architecture 輪読会
社内の有志で輪読会をやっていました。 tatsu-zine.com
なんとなく触りくらいは知っていましたが改めて読み合わせてみると理解できていないところがたくさんありましたね。 めちゃくちゃ納得する部分は多いんですが、じゃあ実際のどうやって作るかというところがなかなか難しいですね。 このあたりの設計思想を反映した最強のプロダクトみたいなのってあるんですかね...
他にも DDD とかありますが、これでなにか理想のプロダクトが出来上がるというわけではなく、このあたりの設計思想を理解した上で試行錯誤して設計していくことでメンテしやすいシステムができる確率を少しでも高くしていく感じなんだろうなと個人的には理解しています。
マネージメント的なところ
2019 年はどちらかというとこちらがメインだった気がします。 テックリードというかテクニカルディレクターに近い感じですかね。 ブログには書けない内容がほとんどなんですが、今まで以上にチームビルディングとかマネジメントを意識して行動することが多かったような気がします。
2018 年の延長に近いですが、チームマネジメントの核は変わっていないです。
- 目的を正確に伝えてメンバーに納得感を持って対応してもらうこと
- メンバーが自分の成長につながるイメージを持ってもらうこと
- 何かあればリーダー自ら対応する方針を示すこと
対応しているプロダクトがアーリーステージなこともあって方針が変わることもよくありました。 それでもキャッチアップできる限界はありますが、自分から企画側の方針を聞きに行ったりクライアントヒアリングに同行したりなど、 プロアクティブに情報を取りに行ってメンバーに伝えることで、少しでもチームやプロダクトとしての進む先を共有できるように心がけていました。
リーダーが落ち着きがなくなるとメンバーにも伝わってしまうので、 常に一歩引いて俯瞰して落ち着いて対応できるような大人を目指していきたいですね。
プライベートなところ
2019 年は趣味にも結構時間を割くことができてよかったです。 特に例年以上にゲームに力を入れることができたのでこちらも引き続き頑張っていきます。
ライブ
今年は狙ってたライブはほとんど参戦できました。 特に BUMP の aurora ark は初回公演と最終公演に参戦できたのでめちゃくちゃ最高でした。 人生初の夏フェスも行けたしミスチルのライブも行けたしライブに関してはほぼ満点ですね。
BUMP OF CHICKEN TOUR 2019 aurora ark
- 2019.7.12 メットライフドーム
- 2019.11.3 東京ドーム
- 2019.11.4 東京ドーム
Mr.Children Dome Tour 2019 "Against All GRAVITY"
- 2019.5.20 東京ドーム
ROCK IN JAPAN FES.2019
ゲーム
ポケモン剣盾
おそらく十数年ぶり、かつ初育成に手を出してみました。 最初はマジで何をやったら良いかわからなかったんですが、色々調べてようやくバトルパーティーが組めるようになってきました。 細かいところが分かってくるとめちゃくちゃ奥が深くて面白い... とりあえず 2020 年早々に会社のメンバーと Pair Pokemon、通称ペアポケの予定があるのでそれに向けて調整していきます。
キングダムハーツ
KHⅢ がやりたいんですが過去作品のストーリを結構忘れてしまってるので 1 作目から全部やり直してます。 進捗は下記のような感じですね。最近はポケモン剣盾に時間を取られているのでこっちも復活していきます。
No | Title | Status |
---|---|---|
1 | キングダムハーツ | Clear |
2 | チェインオブメモリーズ | ソラ編 : Clear / リク編 : Clear |
3 | キングダムハーツ2 | Clear |
4 | 358/2Days | WIP |
5 | バースバイスリープ | - |
6 | コーデッド | - |
7 | ドリームドロップディスタンス | - |
8 | キーバックカバー | - |
9 | 0.2 バースバイスリープ | - |
10 | ユニオンクロス | WIP (キーブレード戦争編のみ残) |
11 | キングダムハーツ3 | - |
2020 年の目標
- アウトプットを増やす
まずは 2 ヶ月に 1 回くらいの頻度でブログに発信していきたいと思います。
- 余裕をもって仕事をする
結構自分でやってしまう癖があるので今年こそは何とかしていきます。(具体的な手法は検討中) 自分がいっぱいいっぱいになってしまうとそれがボトルネックになってしまうので、 うまくチームメンバーに仕事を任せて更にいいチーム、プロダクトにしていきたいですね。
- 余裕を持って生活する
めちゃくちゃゲームができるくらいの生活ができていれば必然的に仕事もうまくまわっているはずかなと。 あ、あと 2019 年からトランポリンジムにも通い始めたのでそれも継続していきたいですね。 ライブにも行きたいし、仕事だけじゃなくて趣味にも全力でいきたいです。
まとめ
とにかく今年も自分を追い込んで色々とアップデートしていきたいと思います。 あと、人間余裕を持って生活していかないとダメですね。
というわけで今年もよろしくお願いします。
AWS サービスを使って社内業務システムと連携する
はじめに
この記事は Recruit Engineers Advent Calendar 2019 の 13 日目のエントリーです。
自分が担当しているプロダクトで社内の外部システムと API 連携する機会がありました。どうやって連携するのが構築や運用が楽になるだろうと検討した結果、AWS が提供しているサービスをフル活用して実現するのがよいのではと考え実際に構築運用してみました。 このブログでは具体的にどのような構成や仕組みで連携したのかを簡単に紹介していきたいと思います。
背景
自分が担当しているプロダクトは 2018 年に新規で公開したサービスなのですが、おかげさまで利用していただいているお客様がどんどん増えておりとても好調な状態です。このサービスとは別にリクルート社内が持っている別の管理システムがあるのですが、今のプロダクトを更に拡大するためにそのシステムと連携してユーザーの使い勝手を良くしていこう、という計画が去年の末頃からでてきました。
社内で使われている業務システムなので、下記のような要件が求められます。
- 対向のシステムは社内業務の必須ツールになっている
- そのため、日中帯の業務時間帯は高いサービスレベルが求められる
- 僕らのサービスでのアクションは(ほぼ)リアルタイムで外部システムに連携する必要がある
- もし何かトラブルがあったときのために修正は速ければ速いほうが良い(リリース不要で対応できるのがベスト)
上記の理由から AWS が提供している S3、SQS、Lambda を使用することで稼働しているシステムとは疎結合でリアルタイムに連携できる仕組みが構築できるのでは?と考え実際に構築してみました。
全体構成図
まず前提として、我々のサービスは Amazon Elastic Container Service(ECS) 上で Docker コンテナ形式で稼働しています。
今回は連携する対向システムの特性上、
- サービスレベルを上げる必要がある
- 正常動作の担保が最優先
という点からリクエスト送信用と外部システムからの受信用でアプリケーションコンテナを分けて構成しました。内部のアクションやアクセスしている DB は同じですが、コンテナを分けることで我々のサービス本体のデプロイライフサイクルとは完全に分離して外部システム用のコンテナを運用できるようになっています。
デプロイ構成
コンテナ自体は分かれており完全な別アプリケーションとして稼働していますが 2 つのコンテナは同じリポジトリ内で管理しています。コンテナが分かれているとはいえ同じサービスですしお互いに共通の DB も参照しています。同じリポジトリ内で管理することでお互いのアプリケーションのソースコードの乖離をできるだけ発生しないようにしています。
具体的には下記のような感じで Git のリポジトリ直下に、
を作成して管理しています。(一般的なモノレポ構成に近いはず。)
お互いに共有するリソース(データモデルやメールの生成ロジックなど)は common
というディレクトリを参照するようにしています。
. ├── README.md ├── api ← アプリケーション本体のソースコードディレクトリ │ └── Dockerfile ├── batch ├── external ← 外部連携用アプリケーションのソースコードディレクトリ │ └── Dockerfile ├── common ← どちらのアプリケーションからも共通で使うものたち ├── docker-compose.ci.yml ├── docker-compose.yml ├── drone ├── erd ├── flyway ├── frontend ├── openresty ├── resttest ├── spec └── static_contents
※ 実際の構成とは一部、構成を省略、ネーミングを変更しています。
アプリケーションのデプロイには drone.io を使用しています。
drone.io
は Github と hook API で連携しており、特定のブランチに指定したフォーマットで tag
を push すること、それぞれ対応したディレクトリの Dockerfile をベースにコンテナのビルドとデプロイが動くようになっています。
外部システムへのリクエスト方法の検討
リクエスト方法は様々な方法がありますが、今回の構成ではアプリケーションとは分離させつつ、問題発生時にトレースがしやすくなることを想定して、「S3、SQS、Lambda を経由して外部システムの API を叩く」という方法を採用しました。具体的な処理フローとしては下記のとおりです。
- 自サービス内でユーザーがアクションすると連携するデータをオブジェクト形式で S3 に保存する
- S3 のイベント連携で SQS にイベントをプッシュする
- SQS にキューが積まれたタイミングで Lambda を発火する
- Lambda が SQS のイベント内容から S3 に配置されたオブジェクトのロケーションを取得する
- Lambda で S3 から取得した連携データのオブジェクトをパースして外部システムへ API リクエストを投げる
- リクエストが正常に完了したら S3 の対象のオブジェクトを削除する
この方式だとリクエストする内容が S3 に保存されます。仮に 5 のタスクの API リクエストがエラーとなった場合、対象のイベントが Dead Letter Queue(DLQ)にプッシュされ、S3 にファイルが残ったままになります。DLQ から復旧も可能ですが S3 にファイルが残るので、例えば長期休暇中に何か問題が発生していた場合や外部システムの都合で単純なリトライが難しい場合でも後から S3 のオブジェクトを確認することができるのでトレースが楽になります。
外部システム連携の詳細
S3 から SQS へのイベント連携
S3 の プロパティ
-> イベント
から SQS への連携を設定します。今回は ファイルが置かれたとき
にイベントを発動するので PUT
, POST
を有効にしておきます。また サフィックス
に .json
を指定しておくことで、仮に異なる形式のファイルが配置されてしまったときに誤ってイベントが発動してしまう、といったケースを防ぐようにしています。
SQS から Lambda のトリガーを設定
Lambda の起動トリガーとして SQS を追加します。今回の連携ではお互いのシステム間で「社内の調整ごとのような順序性を担保した連携」が必要だったため Lambda を同時実行することはせずにバッチサイズを 1 にして SQS にイベントがプッシュされたタイミングで 1 つずつ処理されるようにしています。
システム連携する Lambda を作成
今回は Lambda は Node.js 10.x 系を使用しました。チームで特に言語は縛っていないのですが、担当しているプロダクトは SPA で構成されておりチームメンバーも全員 React / Redux は書けるのでメンテナンスは問題ないだろうというのと、一番は自分が好きだったという理由で Node.js にしています。
実際には下記のようなコードで API リクエストを送信しています。
// indes.js(一部省略) const AWS = require("aws-sdk"); const request = require("request-promise"); AWS.config.update({ region: "ap-northeast-1" }); const s3 = new AWS.S3({ apiVersion: "2006-03-01" }); const BASE_URL = process.env.API_BASE_URL; const ACCESS_TOKEN = process.env.API_ACCESS_TOKEN; const QUERY_PARAMETER = `accessToken=${ACCESS_TOKEN}`; const parseSQSEvent = event => { if (event.Records.length === 0) throw "SQS event resource not found."; const s3Event = JSON.parse(event.Records[0].body); if (s3Event.Records.length === 0) throw "S3 event resource not fount."; return s3Event.Records[0]; }; // "Content-type": "application/json で送信される。 const postWithJson = async values => { // API 送信処理 }; // "Content-type" : "multipart/form-data" 形式で送信される。 const postWithForm = async params => { // API 送信処理 }; exports.handler = async (event, context, callback) => { // SQS のイベントから S3 の情報を取得する const s3Detail = parseSQSEvent(event); const s3Params = { Bucket: s3Detail.s3.bucket.name, Key: s3Detail.s3.object.key }; try { // SQS のイベントをもとに S3 から対象のオブジェクトを取得 const targetObject = await s3 .getObject(s3Params) .promise() .catch(err => { throw "Get object from s3 is failed."; }); // オブジェクト内の実際に送信するデータをパース const targetValue = JSON.parse(targetObject.Body.toString()); // タイプによって送信フォーマットが異なるため呼び出すメソッドを変える if (targetValue.action === "JSON") { await postWithJson(targetValue.value); } else if (targetValue.action === "FORM") { await postWithForm(targetValue.value); } } catch (error) { throw "External system request failed."; } // 正常終了のため S3 から対象のオブジェクトを削除する await s3 .deleteObject(s3Params) .promise() .catch(err => { console.log("Delete object from s3 is failed."); }); return { statusCode: 200, body: JSON.stringify("Execution completed successfully.") }; };
Terraform でインフラ環境を構築
これらの構成は Terraform
を使用して管理、構築しています。
基本的には下記のブログの内容をベースにしているのですが、一点だけつまずいたポイントを紹介します。
Node.js で作成した Lambda をアップする場合、外部ライブラリを使用しているためそれらも一緒に zip でパッケージングする必要があります。Terraform 的には何かインフラを構築するわけではないが「事前に npm install
してからパッケージングをし Lambda へアップロードする」動きをさせたいと考えていたため、Terraform の null_resource
を使用して実現しようとしていました。
しかし null_resource
を使用した場合、後続のタスクから連携している depends_on
が正常に動作しなくなるという現象が発生したため、空ファイルを作るという一連の構築のフローに関係のないダミーのタスクが存在しています。(Terraform v0.11.13 を使用していたので最新版では問題ないかもしれません)
# main.tf(一部抜粋) # 本来ローカルファイルを作成する必要性は全く無いが、depends_on を動作させるために使用している resource "local_file" "main" { filename = "${path.module}/src/terraform.txt" content = "created by terraform on ${timestamp()}" provisioner "local-exec" { command = "npm i --production --prefix=${path.module}/src" } } data "archive_file" "zip_lambda" { depends_on = ["local_file.main"] type = "zip" source_dir = "${path.module}/src" output_path = "${path.module}/actionLinker.zip" } resource "aws_lambda_function" "main" { depends_on = ["data.archive_file.zip_lambda"] runtime = "nodejs10.x" : 以下省略
システム連携の一時停止機能
連携している社内システムですが、当然向こうの開発サイクルによるリリースや定期メンテナンスによるシステム停止が発生します。この間に我々のシステム内でアクションした処理を無邪気に連携してしまうと、連携先のシステムが落ちている状態のため、DLQ に連携失敗のキューがたまり続けてしまいます。
これを防ぐために、「SQS から連携用の Lambda を実行するトリガー自体を ON/OFF する Lambda」を別で作成して CloudWatch Events
の cron
形式でシステム連携を一時停止する運用にしています。
実際のコードは下記のような感じで作成しています。
// index.js(一部抜粋) // AWS SDK で Lambda の状態を更新する const getExecuteFunctionName = async param => { return await lambda.listEventSourceMappings(param).promise(); }; const updateFunctionEvent = async param => { return await lambda.updateEventSourceMapping(param).promise(); }; exports.handler = async event => { // システム連携用 Lambda の情報を取得 const baseParam = { FunctionName: functionName }; let currentFunctionInfo = {}; try { currentFunctionInfo = await getExecuteFunctionName(baseParam); } catch (e) { throw "Error getting function parameters"; } // システム連携用 Lambda のイベント連携状態を更新 const updateParam = Object.assign(baseParam, { UUID: currentFunctionInfo.EventSourceMappings[0].UUID, Enabled: false // イベントを ON にするときは true にする }); try { await updateFunctionEvent(updateParam); } catch (e) { throw "Error updating function parameters"; } : 以下省略
今回やってみての感想
アプリケーションと分離して AWS サービスのみで連携できて非常にメンテナンスが楽になりました。この辺りの仕組を自分たちで実装しようとするとバッチ環境を作らなきゃいけなかったり連携基盤自体もメンテナンスしていく必要が出てきてしまいますが、そこを AWS にお任せできるというのはかなりメンテナンスコストを削減できて良いですね。
ただ、ログの管理については今後の課題の一つかなと思っています。使用するサービスが分かれているため、今は何か問題が起こったときにそれぞれのログを見にいかなきゃいけない状態です。あとはローカル環境でこれと同じ環境を再現するのが手間がかかってしまうのもなんとかしたいですね。(開発中のトラブルシューティングがつらかった。)
この辺りは AWS SAM CLI でローカル環境を構築したり、X-Ray で横断的にログをトレースしたりなど、更に便利になるようにいろいろと検討していけたらなと思っています。
追伸
Lambda のバッチサイズを 1 にして順序性を保つと説明していましたが、厳密には SQS の標準キューを使用している以上、確率は低くはなりますがイベントが発生するタイミングによってはイベントの逆転は発生してしまいます。本来であれば FIFO キューを使いたかったのですが、こちらは S3 からのイベント連携に対応していないとのこと。。
今回は要件的に順序の逆転がそこまで発生しないだろうという前提のもと、仮に SQS で仮に順序が逆転しても業務的に問題ない方式でお互いのアプリケーションを機能実装することでそこをカバーしました。早く FIFO キューで S3 のイベント連携できるようになってほしいですね。
2018 年振り返りと 2019 年の目標
しばらくブログも更新していなかったが、平成最後の年末年始ということで振り返りの記事を書いてみたいと思う。結構あっさり書けるかなーと思ってたら、意外と言いたいことがたくさんあって長文になってしまった...。(記事の後半はちょっとバテ気味...)
人生の残り時間もどんどん減っていくし、無駄なことをやってる時間もないし、せっかくの人生だから 2019 年も新しいこととか面白いとかどんどんチャレンジしていきたいと思う。
2018 年の振り返り
仕事全般
仕事面で一番大きかったのは部署を異動したことだと思う。
今までは大規模転職支援サービスのフロントエンドチームのリーダーとして主にテクニカルディレクター的なことをやっていた。期間的には 3 年くらいになるのでキャリア的にも結構長い部類だと思う。とはいえ参画当初からテクニカルディレクターだったわけではなく、最初はコーダーとして参戦していたので、フロントエンドの開発からリーダー業務まで幅広く経験することができた。
大規模なサービスなので、当然ステークホルダーも多くなるし、その中でどれだけ技術的なチャレンジとか安定性を出していけるかというのはそれはそれでとても面白かった。なによりも「みんなが知ってるあのサービスのフロントエンドを任されている」というプレッシャーや責任感みたいなのもあって、個人的にはめちゃくちゃ成長できたと感じている。
一方、テクニカルディレクター業務がメインになってくるにつれてコードを書く時間がどんどんと減っていった。もともと今の会社に転職した理由が「コードを書きたい!」という要望だったので、途中から「これでいいんだっけ?」と思うようになってきた。
そんな感じでキャリアに悩んでいるときに、別の部署にいた同僚から声をかけてもらえたこともあって、コードが書ける今の部署に異動することができた。ポジションとしてはプロジェクトリーダーという肩書ではあるが、自らがコードを書いてメンバーを引っ張っていくというなかなか大変だけど面白そうなことをやらせてもらっている。その結果、今まで触っていなかった技術を大量に学ぶことができてエンジニアとしては非常に成長できた 1 年だったと思う。
エンジニアスキル
- React / Redux
React / Redux をガッツリ使用して SPA なサイトを作成していた。個人的にはやっぱりフロントエンドが好きなんだなーと。技術の移り変わりが速いとか言語仕様があまりイケてないみたいなことも言われるが、やっぱりユーザーがブラウザで直接触れるところを作れるのは魅力的だと思う。
実際に運用していく上では、設計思想やパフォーマンスの観点を最初からある程度理解しておかないと、リリース後のエンハンス開発がキツくなると思った。
設計思想については Atomic Design をベースに Stateless Fuctional Component で HOC で設計する方針でやっていた。コンポーネントを作成する際に、スコープがコンポーネントに閉じるからといって全体を俯瞰してみたときのロジックの最適化を意識しなかったり、CSS 設計の優先度を落とす(理解しないまま進める)のはメンテナンス性を著しく下げることにつながってしまう。
CSS に関してはスコープ内だからと言って命名規則とかをサボったりすると全く意味のわからない CSS が出来上がるし、設計思想が異なるコンポーネントを結合したときに途端にレイアウトが破綻する。コンポーネント分割に関してもセマンティックや命名規則など、通常の Web サイト構築時に意識するところを同時に考えないと、適切なコンポーネント分割を考えられないと思う。結局基礎が大事なんだなーと。
また、パフォーマンスについてもコンポーネントのレンダリングロジックや、State の更新によるレンダリングスコープをちゃんと理解していないといけない。パフォーマンスを意識することでも、コンポーネントが肥大化してしまったり、ロジックと View の分割がキレイにできなかったり、といったアンチパターンを防ぐことにもつながると思う。
- Kotlin / Spring boot
SPA のバックエンド API に Kotlin と Spring boot を使っていた。今まではバックエンドは PHP とか Ruby とかばかりやってたので、Kotlin や Java、Spring boot でのガッツリとした開発という意味ではほぼ初めてに近かった。
なんとなく今までは Java は書きにくいイメージがあったのだが Kotlin はこのあたりは気にならずにスラスラと書くことができた。その裏返しではあるが、自由にかけることでどんなふうにでも書けてしまい何がベストな書き方なのかを考えるのが難しかった。(let, run, apply, also といったスコープ関数など)全体的には非常に書きやすい言語なので、今後も積極的に使っていきたいと思う。
あと Spring boot はもっと勉強しなければ...。デザインパターンも大事...。
- AWS(ECS / ECR)
開発も商用サービスもすべてコンテナをベースにして対応した。一度このスタイルに慣れてしまうと仮想サーバーでの開発にも戻れないくらい便利に開発業務ができる。商用環境に関しても、既にステージング環境で動作確認ができているコンテナをデプロイするだけなので、リリース時のストレスなども軽減できていると思う。
通常フローでは問題なく運用が回ってはいるが、ちょっといレギューラーな対応をしたい場合の手順が確立できていないので、今年はそのあたりをキレイにしていこうと思う。(ECS タスクのビジョン管理とかリリースターゲットの管理など。)
- Embulk / DigDag
分析基盤へのデータ連携や、サイトのデータ移行で Embulk と Digdag を使った。率直な感想としてはめちゃくちゃ楽にデータを移行することができるという一言に尽きる。インプット/アウトプットのアダプターも豊富だし、自分で移行スクリプトとかを書かなくてもいい感じにデータを取り込んで整形してアウトプットしてくれる。
ただ、Bulk 処理ということでスレッド管理に気をつけないと意図しないシーケンシャルデータができてしまったり、複数の Embulk 処理をまたいで変数を渡すときにファイルや環境変数を駆使しないといけなかったりとちょっとハマりポイントがあるのでそこだけは要注意。
ビジネススキル
- オフショアマネジメント
異動前の部署から引き続き、今の部署でもオフショアで海外のエンジニアと一緒に仕事をしている。オフショアマネジメントに関しては 3 年くらいやっていたのである程度はこなせると思っているが、今の部署では更にそれを進化させて裁量をもたせるような取り組みを実施している。
まだ途中ではあるが、通常のオフショアではあまり任されないような上流工程も任されるので海外のエンジニアもモチベーション高く仕事に取り組んでいると思うし、うまくいけば今までにないような日本と一体感のあるチームが出来上がるかもしれない。
- チームマネジメント
これは今までやっていたテクニカルディレクターの延長として引き続き対応している。ポジションとしてはプロジェクトリーダーなので今までよりも高い視座をもって考えるように心がけている。リーダーとして重要なことはめちゃくちゃいっぱいあってこれだけでも記事がいくつか書けると思うのだが、個人的には、メンバーに納得感を持って対応してもらうこと、メンバーが自分の成長につながるイメージを持ってもらうこと、を意識することが結果的に良いチーム作りにつながるのではないかと考えている。
プライベート
- Sequelize (ORM for Node.js)
ちょっとプロトタイプを作ろうという機会があったので Sequelize を使って MySQL とやり取りをする API サーバーみたいなものを作ってみた。ActiveRecord みたいな感じでデータの関連性を定義してデータを持ってこれるので結構簡単に実装はできた。 結局実践で使うことはなかったが、何かいいタイミングがあったら検討してみたいと思う。
とあるサービスのお手伝いで Ruby on Rails を触っている。Ruby 自体は Sinatra ベースでサービスを作ったことはあったが、Rails は今回が初めて。やはり The Rails Way というか、Rails のお作法を理解するまでは思ったようなコーディングスピードが出なかった。確かに楽には書けるのだが、詳細を理解していないので「なんでこうなるんだっけ?」といった立ち止まりが結構発生した。徐々に慣れてはきたのでもっと効率よく開発できるようにしていきたい。
2019 年にやりたいこと
- フロントエンドスキルの向上
やっぱりフロントエンドは好きなので、新しいトレンドなどをキャッチアップしつつ、実践でもどんどん活用していきたい。しばらくは React と Vue.js が中心になると思うのでそれぞれの最新動向を掴みつつ、実際の業務へ展開していきたい。
- 新しい言語を使えるようにする
1 年につき 1 つは新しい言語に挑戦するようにしていく。書くこと自体はすぐにできると思うが、運用を見越して展開していくとなるとそれなりのスコープを理解しないといけないので、あえて「使えるように」という言い方をしている。今のところの候補は Go あたり。
- 新しい分野の勉強をする
同じ分野のことばかり勉強してても業界全体で見たときのスコープが広がっていないのでそこをあえて広げにいく。機械学習まわりは基礎的な知識は理解できるようにしておきたい。
- アウトプットを増やす
今書いているブログもそうだが、外部へ公開することによって自分の知識もより深く理解することができるようになると思う。理解した技術は定期的にブログに記事を書いて自分の理解を深めていくことにつなげる。
まとめ
とにかくエンジニアとしては立ち止まらずに、いろんな知識をインプット / アウトプットし続けていきたいと思う。そのためには今年も覚悟をもって自分を追い込んでいきたい。
AWS でサーバレスアーキテクチャを触ってみた感想
今年度になって業務とは関係ないところでサービスを試しに作る機会があったので、前々から気になっていたサーバレスアーキテクチャで構成してみた。 構築する上でのハマりポイントとか躓いた点がいくつかあったので、忘れないようにするためにも残しておこうと思う。
使用したサービス
一般的なサーバレスアーキテクチャ構成と同じ構成。
- AWS Lambda
- Amazon API Gateway
- Amazon DynamoDB
という組み合わせで構築してみた。
IAM ユーザーの作成
サービスを構築していく前に最初に IAM ユーザーを作成する必要がある。
作成したユーザーに対して使用するサービスのアクセス権を追加していく。
ルート権限のアカウントでも利用は可能だが、セキュリティ面や運用面を考慮するとルート以外の権限のユーザーを作成して対応することをオススメする。
IAM ユーザーの作成とアクセス権限の設定
今回のサーバレスアーキテクチャを操作するユーザーを作成する。そのユーザーに対して使用するサービスのアクセス権限を追加する。
- IAM を開き左メニューからユーザーをクリック
- 「ユーザーを追加」ボタンからユーザを作成
- 作成したユーザーを選択し「アクセス権限の追加」ボタンから権限を追加
- 「既存のポリシーを直接アタッチ」を選択し、リストの中から下記の権限を追加していく
- AmazonDynamoDBFullAccess
- AmazonAPIGatewayAdministrator
- AWSLambdaFullAccess
これで作成した IAM ユーザーにアクセス権限が追加された。
アクセスキー情報
次に作成した IAM ユーザーのアクセスキーを作成する。
- 作成したユーザーをクリックし「認証情報」タブをクリック
- 「アクセスキーの作成」ボタンをクリック
アクセスキーが生成されると同時にシークレットアクセスキーも作成される。 シークレットアクセスキーはこのタイミングしかダウンロードできないようなので要注意。
DynamoDB
ここからようやくサーバレスアーキテクチャの環境を構築していく。 今回は単純に ID と name とステータスをもたせるだけの簡単なテーブル構成とした。
テーブルの作成
- DynamoDB のコンソールを開き「テーブルの作成」をクリック
- 左のメニューから「テーブル」を選択肢、作成されたテーブルをクリックする
- 「項目」タブを開いて「項目の作成」からデータベースの情報を追加
編集モーダルが開くのでここから項目とデータを追加していく。
デフォルトは Tree ビューでの項目追加だが、Text ビューモードに切り替えるとオブジェクトのフォーマットで記述できるので階層構造やデータタイプが理解しやすいかもしれない。
DynamoDB の予約語
実装してから気づいたのだが、DynamoDB には予約語が存在している。
DynamoDB の予約語
これと同じ単語をテーブルの属性名に設定していると Query を発行するときにエラーとなる。後述するが、DynamoDB のアクセス時にステータス(status)を変更する際は下記のように予約語を置換してあげる必要がある。
Key : { "id" : event['body-json'].item.key }, UpdateExpression: 'SET #st = :val', ExpressionAttributeNames: { '#st': 'status' },
Lambda Function
API Gateway からリクエストを受け付け、DynamoDB を起動するための Lambda Function を作成する。
ロールの作成
まずは Lambda から DynamoDB へアクセスするためのロールを作成する。Lambda からログを残す必要があるため、CloudWatch のログ権限も追加しておく必要がある。
- IAM の左のメニューから「ロール」を選択
- ロールの作成で「Lambda」を選択
- ポリシー一覧から「AmazonDynamoDBFullAccess」を選択
- ロール名を入力してロールを作成
これでロールが作成された。
Function の作成
次に Lambda Function を作成していく。
- 「関数の作成」ボタンをクリック
- ベースとなるテンプレートを選択する
一から作成しても良いがベースとなる構成があったほうが楽なので
ここでは「hello-world」を選択する。 - 必要な情報を入力して関数を作成する
- ロール : 前のステップで作成したロールを選択
- Lambda Function のコード : 既にローカルなどで実装されていれば入力しても OK だが、とりあえずそのままでも OK。
これでベースとなる Function が作成された。
今回は GET リクエストと POST リクエストで処理を分けるため下記のようなコードを作成した。ランタイムは「Node.js 6.10」を使用している。
'use strict'; let AWS = require('aws-sdk'); AWS.config.update({ accessKeyId: process.env.IAM_USER_API_KEY, secretAccessKey: process.env.IAM_USER_SECRET_KEY, region: "us-east-2" }); let db = new AWS.DynamoDB.DocumentClient({}); exports.handler = (event, context, callback) => { let table = { TableName : 'test' }; let query = ""; switch(event.context['http-method']){ case "GET": query = table; console.log("get method"); db.scan(query, function(err, data) { callback(null, data); }); break; case "POST": let param = { Key : { "id" : event['body-json'].item.key }, UpdateExpression: 'SET #st = :val', ExpressionAttributeNames: { '#st': 'status' }, ExpressionAttributeValues: { ":val": event['body-json'].item.val }, ReturnValues:"UPDATED_NEW" } query = Object.assign({}, table, param); console.log("post method"); db.update(query, function(err, data) { callback(null, data); }); break; } }
DynamoDB へのアクセス
IAM ユーザを作成した時に生成した「アクセスキー」と「シークレットアクセスキー」を使用する。直接コード内にキー情報を書くのはセキュリティ的にも良くないので、環境変数化して使用する。
AWS.config.update({ accessKeyId: process.env.IAM_USER_API_KEY, secretAccessKey: process.env.IAM_USER_SECRET_KEY, region: "us-east-2" });
環境変数は Lambda コンソールの「環境変数」メニューから作成できる。
Lambda Function のテスト
Lambda コンソールの上部の「テストイベントの作成」からテストを実行できる。「テストイベントの設定」からテストを作成していくが、サーバレスアーキテクチャのテストであれば「API Gateway AWS Proxy」が一番テストデータに近いのでそれを使用する。
テストの結果は Lambda コンソール上部に表示されるのでそれをベースにデバッグや修正を行っていく。
API Gateway
最後に Lambda Function を呼び出すための API を作成する。
API の作成
下記の手順で API を作成していく。
- API Gateway のコンソールを開き「API の作成」をクリック
- API 名と説明を記載して「API の作成」ボタンをクリック
既に作成済みの API があればそれからクローンも可能。 今回は新規で作成したため「新しい API」を選択
これで API の枠が完成した。作成した API にはまだ何もアクションが設定されていないため下記の手順で追加していく。
- 左メニューから「リソース」を選択
- ページ上部の「アクション」タブから「メソッドの作成」をクリックする
- 作成するアクションを選択してチェックマークをクリックする。ここでは GET メソッドを選択。
すると GET アクションの初期セットアップ画面が表示される。
- 統合タイプにて「Lambda 関数」を選択
- 「Lambda のリージョン」を選択して適切なリージョンを選ぶ
- 上記手順で作成した Lambda Function をテキストボックスに入力して「保存」ボタンをクリック
- 「Lambda 関数に権限を追加する」というモーダルが出てくるため「OK」ボタンをクリック
内容に問題がなければ API Gateway が作成される。
この状態で Lambda 側のトリガーを確認すると、API Gateway と統合されていることがわかる。
マッピングパラメータ
API 作成後の初期状態だとリクエストしたパラメータは API Gateway から Lambda Function には引き継がれない設定となっている。Lambda 側で値を受け取って処理をさせたい場合は、API Gateway でパラメータをマッピングし、それを Lambda 側で受け取るようにする必要がある。
テンプレートの生成から「メソッドリクエストのパススルー」を選択すると、すべてのパラメータを受け渡すテンプレートが生成される。ここから使用したいパラメータを探して使用していくと設計がしやすいかもしれない。
API の公開
作成した API を公開する。
- 「アクション」ボタンから「API のデプロイ」をクリックする
- API のデプロイモーダルが開くのでデプロイされるステージを選択
今回は「新しいステージ」を選択して公開する - 「デプロイ」ボタンをクリック
これで作成した API が公開された。ステージエディターを開くと API を実行する URL が表示される。
使用量プランの作成
API を使用する上での使用量を設定することができる。システム構成やサービスレベル、利用料に合わせて API のスロットリングやクォータを設定することができる。
- 左メニューから「使用量プラン」を選択
- 使用量プランの作成画面にて名前やスロットリング、クォータの設定を入力して「次へ」を選択
- 関連付けられた API ステージが表示されるので「API ステージの追加」ボタンをクリック
- 対象の API とステージを紐付ける
API キーの追加
このまま API を公開するとどこからでも誰でも叩ける API となってしまう。公開サービスなら問題ないが、限定公開するような仕組みの場合、誰でも叩けてしまうとセキュリティ的にも利用料金的にもよろしくない。これを防ぐために、API キーを追加してキーを持っているクライアントからしか API を実行できないようにする。
- 「メソッドリクエスト」をクリック
- 「API キーの必要性」の横の編集ボタンをクリックし、パラメータを「true」に変更する
- 左メニューから「API キー」を選択
- 「アクション」ボタンから「API キーの作成」を選択
- API キーの作成画面が表示されるので、名前を入力して「保存」ボタンをクリック
API キーはカスタムでも問題ないが、ここでは自動生成で作成する - 「使用量プランに追加」ボタンをクリックし、作成した使用量プランに紐付ける
作成した API キーは「表示」リンクをクリックすることで表示される。API キーを使用して API を実行するには API キーを x-api-key という HTTP ヘッダーに含めてリクエストを送信する必要がある。
$ curl -H 'x-api-key:xxxxxxxxxx' https://xxxxxxxxxx.execute-api.us-east-2.amazonaws.com/test
作成してみた感想
サーバの構築が不要なのは楽
サーバレスアーキテクチャの利点ではあるが、自分でサーバリソースを用意しなくともこのようにバックエンドのシステムが構築できてしまうのは魅力的である。AWS なのでもちろんだが、サーバレスアーキテクチャの構築作業のすべてが AWS コンソール上でできてしまうのは非常に便利だと感じた。
簡単なサービスやログの取得の仕組みであれば EC2 を用意するよりもこちらの構成で組んだほうが楽かもしれない。
元インフラエンジニアとして気になったこと
ただ、自分もそうなのだが、今まで自前でサーバを構築してきた人にとっては AWS コンソールで全ての作業をすることに少し抵抗感があるかもしれない。当然 AWS でのお作法に則った上で環境を構築していくことになるのだが、これに慣れていないと「あれってどこで確認できるんだっけ?」「ここってどういう仕組みになってるんだっけ?」といった疑問が次から次へとでてきて構築に手間がかかってしまう。
EC2 であれば自分でサーバを構築していったり直接ログを確認したりできるが、サーバレスアーキテクチャの場合、このあたりも含めて AWS コンソールに包括されてしまうため、より一層 AWS に慣れた上で使っていく必要があると感じた。とりあえず触り続けて慣れてくれば問題はないと思うのだが、操作感に慣れるまでは少し時間はかかりそうだと感じた。
テスト環境が欲しい
AWS 上で構築していくため、当然動作テストも AWS 上で行う必要がある。コンソール上からテストを実施する方法も提供はされているものの、画面からの操作なので多少不便なところはあるし、ターミナルのようにログをリアルタイムで表示しながらリクエストを飛ばして確認するというやり方は難しい。
ローカルに同様の環境を作成して問題がなければ AWS へデプロイ、という手法が使えればこのあたりのストレスはある程度解消されるはずだが、現状ではこのような環境は自分で構築していくしかない。
aws-sam-local という Docker を利用してサーバレスアーキテクチャ構成をローカルに構築できるツールが AWS からオフィシャルに提供されてはいるものの、
- DynamoDB が含まれていない
- API Gateway のマッピングパラメータの機能がない
- ネットワーク構成も Docker に依存するため外部リクエストができない (やり方はあるのかもしれないが見つけられなかった)
という状況なので、完全にローカルにクローンできるわけではなさそう。
まとめ
まだ痒いところに手が届かない感はあるが、そのあたりの代替は可能だしサーバレスアーキテクチャでの構成も使用するサービスによっては非常に使いやすい。当然ではあるが、構成ありきで環境構築していくのではなく、サービスの用途や使用する機能によって、自前でサーバーを構築するのかサーバレスアーキテクチャにするかはうまく見極めながら使い分けていくのがよいと思う。
Yarnがどのくらい速いのか実際に使ってみた
Yarnとは
Facebookが公開したパッケージマネージャー。
npmと同じようにパッケージを管理できる。
yarnでもpackage.jsonをそのまま使用可能なため、既存のプロジェクトにも導入が可能。
詳細な機能は公式サイトを参照。
Yarn 公式サイト
インストール
Homebrew を使っていれば下記のコマンドだけでインストールが完了。
$ brew update $ brew install yarn
どのくらい速いのか
下記のpackage.jsonで試してみた。
{ "name": "sample", "version": "1.0.0", "description": "default template for development.", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "Hiroaki Imai", "license": "", "dependencies": { "jquery": "^3.0.0" }, "devDependencies": { "babel": "^6.5.2", "babel-core": "^6.10.4", "babel-polyfill": "^6.9.1", "babel-preset-es2015": "^6.9.0", "babel-preset-stage-0": "^6.5.0", "babelify": "^7.3.0", "browserify": "^13.0.1", "gulp": "^3.9.1", "gulp-load-plugins": "^1.2.4", "gulp-plumber": "^1.1.0", "gulp-sass": "^2.3.2", "gulp-util": "^3.0.7", "html-browserify": "0.0.6", "lodash.assign": "^4.0.9", "vinyl-source-stream": "^1.1.0", "watchify": "^3.7.0" } }
使用したnpmのバージョンは下記の通り。
$ npm -v
3.10.8
npmとyarnとでどのくらい差があるかを試してみた。 まずは通常のnpmの場合。キャッシュをクリアしてインストール。
$ npm cache clean $ time npm i npm i 45.52s user 11.52s system 39% cpu 2:23.88 total
キャッシュが効いた状態で再度実行。
$ time npm i npm i 41.34s user 10.01s system 49% cpu 1:42.92 total
次にyarnの場合はこちら。
インストールはyarn
もしくはyarn install
で実行。
$ rm -rf ~/.yarn-cache $ rm -rf ~/.yarn $ time yarn ✨ Done in 107.47s. yarn 27.98s user 12.92s system 37% cpu 1:48.29 total
次にキャッシュがある状態で実行。
$ time yarn ✨ Done in 23.35s. yarn 11.03s user 5.61s system 69% cpu 24.049 total
yarnの場合、初回のインストール時でもnpmに比べて30秒ほど速い。 キャッシュが効いた場合だと、npmが1分42秒なのに比べてyarnは24秒とかなりの差がある。
キャッシュ無し | キャッシュあり | |
---|---|---|
npm | 2:23.88 | 1:42.92 |
yarn | 1:48.29 | 24.049 |
yarnは一度インストールするとローカルにパッケージを持つため2回目以降のインストールが速くなる。
まとめ
yarnを使うことでインストールにかかる時間を短縮でき、 開発着手までの手間を減らすことができる。 今回の検証では記載していないが、パッケージのバージョン固定もできるため、 開発規模が大きくなればなるほど効果が出てきそう。 逆に規模が小さい開発の場合はあまりメリットがないかもしれない。
また、リリースされたばかりということもあり、今後どのようにyarnがメンテナンスされていくかが不明。 実プロジェクトに適用していくかはもう少し様子を見ておいたほうがよいかもしれない。
ベトナムライフ 食事編
5月10日からお仕事でベトナムのダナンに来ています。
期間は1ヶ月ほど。
最初は異国の地でうまく生活できるか不安でしたが,
今のところ何とか生活できています。
ご飯も美味しいし,自然もたくさんあるし,
ベトナム人もみんな優しい人で良かったです。
そんなベトナムライフにて,特に美味しかった食事を
個人的ベトナム料理ランキングとして紹介したいと思います。
どれも美味しいお店なので是非とも行ってみてください。
タクシードライバーに写真を見せればきっと連れて行ってくれると思いますw
バインセオ
ベトナムの粉物料理。ベトナム風お好み焼きって言われているみたいですが,
個人的にはそんなに似てる感じじゃなかったですね。
ライスペーパーにこのパリパリしたやつと野菜を巻いて食べる感じです。
このお肉を巻いて食べると更に美味しかったです。
カオラウ
米で作ったうどんみたいな麺の上に,野菜と豚肉を乗ってます。
汁なしうどんみたいな感じですね。
有名なので食べたことある方も多いかと思います。
ミークワン
きしめんみたいは平べったい麺の上に,豚肉や魚が乗っています。
ゆでたまごを入れたり野菜をいれたり,パリパリのごませんべいを入れたりなど
いろんな食べ方や味があって美味しかったです。
ここからベトナム料理ランキング TOP3 を紹介していきたいと思います。
No.3 バインカイン
モチモチした麺の上に鶏肉やたまごを乗せた麺料理です。
このブログを書いてるときに調べていて始めて知ったのですが,
この麺はタピオカ粉で作られているみたいですね。
写真はトッピング全部乗せで,タップカムって言うみたいです。
No.2 コムガー
ベトナム風チキンライスです。
鶏肉もライスも絶品です!
ちなみに鶏肉を裂いたやつはコムガーセイって言うみたいです。
こちらも絶品でした。
No.1 ソイガ―&ブンマンガー
ダナンで一番美味しかった料理です。
こちらがソイガ―。鶏おこわみたいなやつです。
ご飯はもち米で,鶏肉や野菜が乗っています。
そしてこちらがブンマンガー。
鶏出汁のスープでブンという米麺にたけのこが乗ってます。
セットで頼んでブンマンガーのスープをソイガーに掛けて食べると絶品です!
あまりにも美味しかったので休みの日も一人で食べに行ってきました。
最後に
食事で当たりやすいと言われるベトナムですが,
自分が行ったところはそんなことはありませんでした。
もしダナンに行った時には是非行ってみてください。
ちなみにどのお店に行っても唐辛子や辛い調味料が
置いてあるのですが,自分はこっちのほうが効きました。
辛い食べ物が好きなのですが,食べ過ぎると次の日が大変。
みなさんもご注意を。
次はベトナムライフ 観光編をお伝えする予定です。