サーバーレスでEvernoteに登録したブックマークをランダムにSlackへ通知するアプリを作る②
前回はEvernoteAPIを利用して、Bookmarkの一覧を取得するところまでを解説しました。今回は、AWS SAMを使ってサーバーレスなインフラを構築していきます。
参考
AWS CLIの準備
まずはこれがないと始まりません。pipでインストールしましょう。
pip install awscli
AWS CLIの初期設定を行います。 ※予めAWSコンソールからsam-sampleというユーザーを作成し、アクセスキーを取得しておきます。
$ aws configure --profile sam-sample AWS Access Key ID [None]: A**** AWS Secret Access Key [None]: U**** Default region name [None]: ap-northeast-1 Default output format [None]: json
S3バケットの準備
SAMでのデプロイまでの流れは以下の通りです。
- SAMテンプレートファイルをパッケージ化しS3に保存。
- パッケージ化したテンプレートを使用するよう記述したCloudFormationテンプレートファイルを作成。
- CloudFormationテンプレートファイルに基づいてデプロイ。
パッケージ化したSAMテンプレートファイルを保存するためのS3バケットをあらかじめ作成しておきます。さきほど作成したsam-sampleユーザーにバケット作成権限を持たせている場合には下記コマンドで作成可能です。バケット名はグローバルでユニークである必要がありますので、被らない名前をつけてください。
aws s3 mb s3://sam-bucket --profile sam-sample
SAMテンプレートの作成
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Create Lambda function by using AWS SAM. # 共通設定 Globals: Function: Runtime: python2.7 Timeout: 15 MemorySize: 256 # リソース定義 Resources: GetBookmarkLambda: Type: AWS::Serverless::Function Properties: CodeUri: 'get-bookmark/lambda_function.zip' Handler: handler.handler Events: Timer: Type: Schedule Properties: Schedule: rate(5 minutes)
AWSTemplateFormatVersionは現在のところ'2010-09-09'しかありませんので、これを指定してください。
TransformではAWS::Serverless-2016-10-31を指定するとyaml形式でテンプレートを記述することができます。元々はjsonで記述するしかなかったのですが、現在ではより可読性の高いyamlで記述するのがおすすめです。
Resourcesで実際に使用するリソースを定義していきます。SAMで使用できるリソースはLambda(AWS::Serverless::Function)、APIGateway(AWS::Serverless::Api)、DynamoDB(AWS::Serverless::SimpleTable)などがあります。今回の構成ではLambdaしか使いません。
Lambda functionの作成
# coding:utf-8 import random import logging from HTMLParser import HTMLParser from evernote.api.client import EvernoteClient from evernote.edam.notestore.ttypes import NoteFilter, NotesMetadataResultSpec import settings class LinkParser(HTMLParser): # ノート内のURLを抽出するパーサー def __init__(self): HTMLParser.__init__(self) self.url = "" def handle_starttag(self, tag, attrs): if tag == "a": # 開始タグがaであるかどうか判定 attrs = dict(attrs) # タプルを辞書に変換する if 'href' in attrs: # キー値(属性名)がhrefであるか判定 self.url = attrs['href'] @property def link(self): return self.url def handler(event, context): # Evernoteクライアントを初期化する(1) client = EvernoteClient(token=settings.AUTH_TOKEN, sandbox=False) note_store = client.get_note_store() # ノートブック一覧の取得(2) notebooks = note_store.listNotebooks() logger = logging.getLogger() logger.setLevel(logging.INFO) for notebook in notebooks: # bookmarksという名前のノートブックにブックマークを保存している。 if notebook.name == 'bookmarks': # 検索条件設定:指定ノートブックにひも付くノート(3) filter = NoteFilter() filter.notebookGuid = notebook.guid # 検索結果設定:ノートタイトルのみ取得(4) resultSpec = NotesMetadataResultSpec() resultSpec.includeTitle = True # ノートメタデータの検索(5) metalist = note_store.findNotesMetadata(filter, 0, 10000, resultSpec) parser = LinkParser() # ノートGUIDを検索条件にノートデータの取得(6) # ランダムに5件のbookmarkを抽出 for n in random.sample(metalist.notes, 5): content = note_store.getNoteContent(n.guid) parser.feed(content) logger.info('Note title: {}\tLink: {}'.format(n.title, parser.link)) return 'OK!'
前回書いたコードをLambda用に少し書き換えました。NOTEのタイトルとブックマークURLをログに出力するようにしています。
デプロイ
今回はLambdaでPython外部モジュールのevernoteを使うので、Lambda functionのコードとモジュールそのものを一緒にzipで固めてデプロイする必要があります。 まずは外部モジュールをカレントディレクトリにインストールします。
pip install evernote -t ./
次にzip圧縮
zip -r lambda_function.zip ./
テンプレートをパッケージ化させてさきほど作成したS3バケットにアップロードします。
aws cloudfront package \ --template-file template.yaml \ --s3-bucket sam-backet \ --output-template-file packaged-template.yaml \ --profile sam-sample
このコマンドでtemplate.yamlの内容をパッケージ化し、それをインポートしたpackaged-template.yamlという名称のCloudFormation用テンプレートファイルができあがります。後はこのテンプレートを基にCloudFormationでデプロイします。
aws cloudformation deploy \ --template-file packaged-template.yaml \ --stack-name get-bookmark-stack \ --capabilities CAPABILITY_IAM \ --profile sam-sample
CloudFormationでは、テンプレートで定義されたひと塊のリソース群をスタックと呼びます。今回は、get-bookmark-stack
というスタック名をつけてデプロイしました。あとは自動的にリソースが作製されてデプロイ完了です。5分ごとにLambdaが実行されているログが確認出来れば成功です。
まとめ
今回はLambdaを用いたサーバーレスなインフラ構成をAWS SAMを使ってデプロイするところまで紹介しました。次回はslackへの通知機能を実装していきます。
サーバーレスでEvernoteに登録したブックマークをランダムにSlackへ通知するアプリを作る①
みなさん、「これ後でしっかり見返そう」と思ってブックマークまではするけど、それで満足して全然見返さないなんてことありませんか?私はかなりあります。全然見ないブックマークばかりEvernoteにたまっております。
昔に登録したブックマークを定期的にお知らせしてくれれば、まったく目を通していなかったブックマークを見るのではないかとふと思い立ちました。EvernoteのAPIを使えば簡単に実装できそうです。
要件
- Evernoteに登録されたブックマークを1日1回ランダムに5件取得し、slackの個人チャンネルに投稿する。
- AWS Lambdaを使いサーバーレス構成で実現する。
- インフラ構成はSAMを使用してコード管理する。
将来的に実装したいこと
- 同じブックマークを連日投稿しないように制御する。
- EvernoteだけでなくPocketのブックマークも取得して通知する。
インフラ構成
CloudWatchのTimeBasedEventで24時間おきにLambdaを起動し、EvernoteAPIからブックマーク情報を取得し、Slackに通知するというシンプルな構成です。
Evernote APIの調査
まずはEvernote APIを使ってブックマークを取得できるか試してみましょう。
公式ドキュメント
Documentation - Evernote Developers
こちらの記事を参考に進めます。
認証
Evernote Clientの認証を通る方法として、APIキーもしくはデベロッパートークンの2種類があります。APIキーはOAuthでの実装が必要です。今回は、1年間の期限付きではありますが、手軽なデベロッパートークンで進めていきます。
Developer Tokens - Evernote Developers
検証用環境のサンドボックスと本番環境のプロダクショントークンがそれぞれ入手できます。※プロダクショントークンは2018.6.19現在Webページから取得することはできません。サポートに問い合わせ取得してください。
SDKのインストール
SDKはPython2にのみ対応している(Python3対応はbeta版)ということなので、Python2で進めていきます。インストールはpipで行います。
pip install evernote
Note一覧を取得し、ブックマークしたページのURLを抽出する
下記は今回実装したコードです。
# coding:utf-8 import random from HTMLParser import HTMLParser from evernote.api.client import EvernoteClient from evernote.edam.notestore.ttypes import NoteFilter, NotesMetadataResultSpec import settings class LinkParser(HTMLParser): # ノート内のURLを抽出するパーサー def __init__(self): HTMLParser.__init__(self) self.url = "" def handle_starttag(self, tag, attrs): if tag == "a": # 開始タグがaであるかどうか判定 attrs = dict(attrs) # タプルを辞書に変換する if 'href' in attrs: # キー値(属性名)がhrefであるか判定 self.url = attrs['href'] @property def link(self): return self.url if __name__ == '__main__': # Evernoteクライアントを初期化する(1) client = EvernoteClient(token=settings.AUTH_TOKEN, sandbox=False) note_store = client.get_note_store() # ノートブック一覧の取得(2) notebooks = note_store.listNotebooks() for notebook in notebooks: # bookmarksという名前のノートブックにブックマークを保存している。 if notebook.name == 'bookmarks': # 検索条件設定:指定ノートブックにひも付くノート(3) filter = NoteFilter() filter.notebookGuid = notebook.guid # 検索結果設定:ノートタイトルのみ取得(4) resultSpec = NotesMetadataResultSpec() resultSpec.includeTitle = True # ノートメタデータの検索(5) metalist = note_store.findNotesMetadata(filter, 0, 10000, resultSpec) parser = LinkParser() # ノートGUIDを検索条件にノートデータの取得(6) # ランダムに5件のbookmarkを抽出 for n in random.sample(metalist.notes, 5): content = note_store.getNoteContent(n.guid) print('\tNote title: ' + n.title) parser.feed(content) print('\tLink: ' + parser.link)
EvernoteAPIでのNote一覧の取得は以下のような手順で行います。
- 認証トークンを与えてEvernoteClientインスタンスを初期化。そのインスタンスからnote_storeを取得。
- note_storeからnotebook一覧を取得。
- NoteFilterインスタンスを生成。所望の検索条件を作成する。今回は、bookmarksという名前のnotebookにブックマークを保存していますので、そのnotebook内のNoteをすべて取得することとします。
- NotesMetadataResultSpecインスタンスを生成。
- NoteFilterインスタンスを与えて、ノートのメタデータリストを取得。
- メタデータリストから個別にノートのコンテンツを取得する。
5.でノートのいったんノートのメタデータを取得するのは、通信量を節約するためです。ノートのコンテンツには添付ファイルなどの大容量データが含まれる場合があるので、それを含めてリストを取得してしまうと莫大な量を通信してしまう可能性があります。コンテンツを含めてリスト取得するメソッドもあるようですが、それは非推奨となっています。
NoteのコンテンツはHTMLで記述されていますので、HTMLParserを使用してブックマークページのURLを抽出しています。
上記のコードでEvernote APIを利用してbookmarkを一括で取得し、その中からランダムに5件抽出してタイトルとリンクURLを表示させることができました。
まとめ
今回は、Evernote APIを利用してNoteの一覧を取得する方法について紹介しました。次回は、このコードをAWS SAMでデプロイしてみたいと思います。
大手電機メーカー組み込みエンジニアから零細Web系企業エンジニアへの転職時にやったこと
30歳を目前にして大手電機メーカーから零細Web系企業に転職したわけですが、転職後に前職と現職の畑違いっぷりに愕然としました。ある程度覚悟はしていたのですがそれをはるかに凌駕していました。
今回はその当時の心境とやっていたことなんかをまとめてみました。
入社前に思っていたこと&勉強したこと
同じプログラミングだし大丈夫でしょ。
組み込み系とWeb系といえどやっぱり同じプログラミングだし、少なからず共通項もあるだろうと思っていました。その期待は入社後に悉く打ち砕かれます。
Python
弊社ではサーバーサイド言語としてPythonを採用しているので、まず初めにPythonを勉強します。勉強は「みんパイ」こと「みんなのPython」で行いました。
https://www.amazon.co.jp/dp/B01NCOIC2P/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
非常にわかりやすく丁寧に書いてあるので、入門書には最適です。ほとんど戸惑うことなく進めることができました。ただ、私は前職でC言語を使っていましたので、オブジェクト指向&動的型付けの言語になじむのは少し時間がかかりました。
Django
PythonのWebアプリケーションフレームワークとしてDjangoを使っています。みんパイでの学習が終わったらこちらの学習を行います。
さぁ始めましょう | Django documentation | Django
Django公式ドキュメントのチュートリアルです。正直Web系の知識がほとんどない状態でやるのはおすすめしません。一般的なMVCフレームワークを理解していないと、身になりません。
入社後に感じたこと&やったこと
~入社2週間 まず言っていることがわからない
まず、同僚が話すことがほとんど理解できません。聞いたことないor意味の分からない用語がポンポン出てきます。LXC?SSH?vm?アルファベット3文字多すぎやろ・・・。とにかく毎日毎日わからない単語が出てきた場合は、evernoteに書き溜めて次の日までに調べるべきワードとしてストックしていました。これを1か月も続けると大体わかるようになってきます。知識の点と点が線でつながっていく快感はこの時期にしか味わえませんね。
~入社3週間 どこから手を付けていいかわからない。
わからないことしかないので、どういった順番で知識を習得していけばいいかわからない。とにかく気になったことを片っ端から調べていきます。その時は深く理解できなくても、ほかのことを調べていくうちに知識が補間されていきます。最初のうちは知識の幅を広げることを重視していました。
~入社5週間 実際にWebシステムを作ってみる。
やっぱり手を動かさないと見つきませんね。しかもDB設計からきっちりやる。流石に先輩に教えられながらじゃないとできませんが、これでWebシステムの全体像をつかむことができました。ログイン機能付きのTodo管理アプリなんかから初めてみるのがおすすめですね。
入社2か月~ プロジェクトを任される
入社して2か月目から実際のプロジェクト運営を任されました。数百万円規模の比較的小さな案件ではありましたが、要件定義・モデル設計・UI設計・実装などなど全てを担当することができました。全てのことが初めて尽くしでしたが、わからないなりにもやってみれば案外できるものです。といっても先輩からフォローは多分にして頂きました。この最初のプロジェクトをやり切ることで仕事の全体像をつかむことができたので、やはり実践に勝る学習はないと感じた時期でもありました。ただ、やはり初めてのプロジェクトとということもあり、今思い返してみると設計的にまずい部分がたくさんあるのでできれば再設計して作り直したいところではあります。
まとめ
転職時に感じたことと取り組んだことなんかについて書き連ねました。人によっては耐え難いほどの環境変化にさらされたわけですが、こういう状況が案外性に合っていたのでつらさは全然感じませんでした。むしろ楽しかったですね。自分の性格に感謝です。
大手電機メーカー組み込みエンジニアから零細Web系企業エンジニアへの転身がどの程度劇的か比較してみる
30歳目前にして大手電機メーカーから零細Web系企業へ転職した私が様々な観点から前職と現職を比較してみます。
企業規模
電機メーカー
- 資本金 数百億円
- 売上高 数兆円
- 従業員数 数万人
- 創業年数 約100年
Web系企業(入社当時)
- 資本金 100万円
- 売上高 1500万円
- 従業員数 1人(私が従業員第一号です)
- 創業年数 0年
福利厚生
電機メーカー
- 退職金
- 確定拠出年金
- 組合健保
- 財形貯蓄
- 各種共済
- 各種手当
- etc.
ここは流石の大企業。年収換算すると一体いくらになるんでしょう?
Web系企業
- 退職金なし
- 社保あり
- 協会けんぽ
必要最低限って感じですね。
年収
電機メーカー
500万円
Web系企業
600万円
額面は上がったけど、福利厚生考慮するとどうなんでしょう? でも創業0年でこれだけ出してくれるのはかなりありがたいです。普通あり得ないと思います。
残業時間
電機メーカー
0~20時間
Web系企業
0~60時間
ここはあまり変わりませんね。
使用言語
電機メーカー
- C
- アセンブラ
オブジェクト指向?聞いたことあるけどよくわからん。
Web系企業
- Python
- Node.js
- Typescript
- HTML
- CSS
- SQL(PostgreSQL)
- bash
- etc.
え?これ全部使えないとお話しにならないの? これだけじゃ役に立たない?フレームワーク?
必要となる技術的知識
電機メーカー
- マイコンの仕様
- 電気回路
- UART
- インバータ
ソフトエンジニアだけどハードもかなり触ります。
Web系企業
粒度がめちゃくちゃですが、思いつくままに書きました。実質1人でほとんどすべてのことをやらないといけないので、覚えるべきことはまだまだ書ききれないほど無数にあります。しかも覚えたことがどんどん陳腐化します。
統合開発環境
電機メーカー
- IAR Embedded Workbench
- High-performance Embedded Workshop (ルネサス)
+エディタとしてMIFESを使っていました。
Web系企業
- Vidual Studio Code
Vimキーバインドで使ってます。 流石の人気IDEめちゃ快適です。
バージョン管理
電機メーカー
他人が編集中のファイルは触ることができませんので、チェックアウトするときにはチームメンバーに一声かけます。
Web系企業
- git
バージョン管理といえばgitですよね。
プロジェクト管理
電機メーカー
- Excelかな?
Web系企業
- gitlab
自社サーバーにインストールして使ってます。
デバッグ
電機メーカー
仕様書を上からなぞってデバッグしていく。退屈だけどとても大事な作業なので嫌々やっていました。
Web系企業
- CI による自動テスト
テストコード書くことこそがテストなのだということを知りました。まあ、フロントエンドは手動テストだったりするんですが。
仕事内容
電機メーカー
- 性能系技術者と打ち合わせしソフト仕様の策定
- 実装
- デバッグ
- デザインレビュー資料の作成
- 設計変更時には関係者の合意を得るために設計変更書を持ち歩いて一人ひとりに変更内容を説明して押印してもらう。通称スタンプラリーを行う。10名ほど回らないといけないので、これで1日の業務が終わってしまうこともしばしば。
Web系企業
- 受託開発のプロジェクトマネジメント
- Webシステムの実装以外のフェーズ(要件定義・設計・デプロイ・運用・保守)
- 実装は学生アルバイト(10名ほど)に任せているので、学生アルバイトのマネジメント
- 学生アルバイトの教育
- インフラ構築
ローンチ後にバグ発生した場合
電機メーカー
- 全国のサービスマンがお宅訪問し、ソフトウェアの書き換え対応を行う。数十億円規模の損失となる。
Web系企業
- バグフィックスをデプロイする。
まとめ
今回は自己紹介的なものも兼ねて、私の前職と現職を比較してみました。次回は、転職直後に感じたことや勉強したことなどを紹介します。