TECH PLAY

株式会社スタメン

株式会社スタメン の技術ブログ

231

スタメン エンジニアの松谷( @uuushiro )です。 2019年12月01日(日)〜12月06日(金)に AWS 最大のカンファレンス AWS re:invent 2019 がラスベガスで開催されました。 AWS Summit Tokyo 2019 の Startup Architecture Of The Year で スタメンがグランプリを獲得したとき の副賞として re:invent のペアチケットを頂いたので、CTOの小林と2人で初参加してきました! 目的 re:inventの予定を組む前に、 AWS re:Invent 参加7回目のエンジニアがおくる re:Invent 2019 おすすめの過ごし方 というブログを拝見し、「現地でしか得られないもの」にこだわって過ごそうと決めていました。現地でしか得られないものは人それぞれですが、僕は keynote , GameDay, SecurityJam は絶対参加しようと意気込んで行きました。それぞれについて簡単に振り返りたいと思います。 Keynote AWS CEO Andy Jassy の3時間にわたる熱演は圧巻でした。今年も多くのサービスが発表され、ひとつ発表があるたびに会場の熱気を感じました。特に、SageMaker など 機械学習 に関するリリースが多かったことが印象に残っていて、サービスのマネージド化が進み、 コモディティ化 していく勢いの速さを感じました。 Keynote だけでなく、GameDayに参加した海外のエンジニアと交流する中で 機械学習 を使うこと自体のハードルはかなり下がっていて、逆に利用していないだけで大きなビハインドになってしまうという危機感を感じました。 AWS が推し進める技術の コモディティ化 にちゃんとついていくことが最低限スタートラインに立つためには重要だなと感じました。 また、 Apache Cassandra がマネージドサービスとして AWS でサポートされるなど、 AWS は多くの選択肢を用意してくれます。自社の ユースケース をちゃんと理解し、それに適したサービスを選択しより良い設計にしていきたいと思います。 普段から手を動かしてサービスを触り、自分の中で引き出しを持っておき、必要な時に適切な クラウド サービスを選択できることがとても重要になると感じました。 GameDay と SecurityJam AWS GameDayとは、 AWS 上に構築したサービスの改善や、障害対応など競うコンテストです。3~4人のチーム戦で行われます。システムが安定して稼働し、サービスとして意味のある処理ができていればポイントが加点されます。 逆にサービスがダウンしていたり、適切に処理が動いていないとポイントが減点されます。今回、現地でチームを組んだので、USのエンジニア3人と僕の計4人チームを組みました。英語を半年前くらいから勉強し始めたので、どれくらい通じるだろうとドキドキしながらGameDayが始まったのですが、チームメンバーは気さくな方達で、とても楽しく会話することができました。そして役割分担、序盤のオペレーションまではなんとか英語でコミュニケーションをとりながら進めることができたのですが、障害対応のような素早いチーム連携において、今の僕の英語のレベルではかなり厳しく、日本語なら対応できるはずのスピードよりも2テンポくらい遅れてしまいました。 また、対象サービスが、 Amazon EKS・ Amazon Forecast・ AWS CodePipeline など業務で利用したことのないサービスで主に構成されていて、その場で必死にドキュメントを読みながらの対応になり、ここでもスピードを出すことができなかったです。日頃から1回は手を動かしてみることが必要だったなーと後悔していました。 AWS から「最低限これくらいは手を動かせるよね?」という暗黙のメッセージを受け取った気分でした。英語の壁だけでなく、技術的な面でも課題を感じたイベントでした。 SecurityJamは AWS 環境で起きている セキュリティインシデント に対応するコンテストです。これも3~4人のチーム戦で行うイベントですが、時間ギリギリで参加したからか、1人での参加になりました。普段セキュリティに関する業務を担当している分、早く手を動かすことができ、一時平均以上のスコアを取ることができましたが、後半失速していき微妙な結果で終わってしまいました。問題を解く中で、新しい学びになることが多かったので業務に活かしていきたいと思います! GameDay・SecurityJamともに、長丁場でとても疲れましたが、海外のエンジニアと一緒に開発をし、リアルな温度感を体感できてとても満足できました。業務の中で AWS の力がついている実感と、まだまだ実力不足だなという感覚を同時に味わうことができてとても良い機会になりました。この感覚を忘れずに成長に繋げていきたいと思います! おまけ せっかくラスベガスまで行ってきたので、グランドキャニオンに行ってきました!re:inventで、 Netflix のデプロイ回数や、 NASDAQ のデータ量など規模がスタメンとは桁違いのスケールを感じましたが、 アメリ カ大陸のスケールの大きさにもやはり驚きました。まだまだ自分やシステムの規模は小さく、これから大きくするぞ!という気持ちになりました。 最後に re:inventは「 クラウド の今」を身を持って知る良い機会でした。業務で使わないサービスに触れるいい機会でしたし、海外のエンジニアの方たちとの交流を通じて、日本だけではなくグローバルな視点で自分の位置を確認することができました。GameDayのように、色々な価値観のエンジニアの方たちと楽しく開発することはとてもワクワクするので、引き続き英語・技術ともにレベルアップして、もう一度チャレンジをしたいと思いました。現地では見れなかったセッションも Youtube や資料が公開されているみたいなので、チェックして、なるべく手を動かして理解していこうと思います! 今回、re:inventには業務として参加しており、宿泊費と交通費を会社に負担してもらいました。 スタメンでは AWS を利用する機会がたくさんあります! クラウド の進化の波に乗りながら、プロダクトを開発するエンジニア募集中です!興味がある方は Wantedly よりご連絡ください。
アバター
こんにちは。フロントエンドエンジニアの渡邉です。 先日参加した勉強会でAWS AmplifyについてのLTを聞き、興味が湧いたので、実際に使ってみました。 その詳しい内容と感じたことについて紹介します。 目次 Amplifyとは Amplifyによる公開 セットアップ 実装 Webアプリを公開 AppSyncとは AppSync導入 queries mutations おわりに Amplifyとは AWS を使用したスケーラブルなモバイルアプリおよびウェブアプリの作成、設定、実装を容易にします。Amplify はモバイルバックエンドをシームレスにプロビジョニングして管理し、バックエンドを iOS、Android、ウェブ、React Native のフロントエンドと簡単に統合するためのシンプルなフレームワークを提供します。また、Amplify は、フロントエンドとバックエンドの両方のアプリケーションリリースプロセスを自動化し、機能をより迅速に提供することができます。 引用: AWS Amplify公式 Amplifyによる公開 今回はReact + AWS Amplifyを使って認証付きの、シンプルなWEBアプリを作成、公開してみます。 事前に以下の 準備 が必要です。 - Amplify CLI のインストール - AWSアカウント設定 セットアップ まずReactのアプリケーションを作成します。 create-react-app amplify-app cd amplify-app 次にAmplifyのセットアップです。 amplify init ? Enter a name for the project amplify-app ? Enter a name for the environment prod ? Choose your default editor: Visual Studio Code // 普段使用しているeditor ? Choose the type of app that you're building javascript Please tell us about your project ? What javascript framework are you using react ? Source Directory Path: src ? Distribution Directory Path: build ? Build Command: yarn build ? Start Command: yarn start Using default provider awscloudformation For more information on AWS Profiles, see: https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html ? Do you want to use an AWS profile? Yes ? Please choose the profile you want to use amplify-test // Amplify用のIAMアカウント 認証機能を導入します。 今回はすべての項目でデフォルト値を使用します。 amplify add auth ... amplify push amplify push コマンドで追加した機能を有効化させます。 実装 yarn add aws-amplify aws-amplify-react src/App.js を以下のように編集します。 import React from 'react' ; import Amplify from 'aws-amplify' ; import awsconfig from './aws-exports' ; import { withAuthenticator } from 'aws-amplify-react' ; Amplify . configure ( awsconfig ) ; const App = () => { return ( <div > amplify - app < / div> ) ; } export default withAuthenticator ( App, true ) ; Webアプリを公開 ここでもすべての項目でデフォルト値を使用します。 amplify add hosting ? Select the environment setup: DEV (S3 only with HTTP) ? hosting bucket name amplify-app-20191126224819-hostingbucket ? index doc for the website index.html ? error doc for the website index.html You can now publish your app using the following command: Command: amplify publish amplify publish ✔ Uploaded files successfully. Your app is published successfully http://~~~ ここにアクセス amplify publish コマンドが成功すると、中身はシンプルですが無事に 認証機能付きwebアプリ 公開完了です。 今回はホスティングを試すためにHTTPを指定しています。 上記のサイトをみる場合は普段使わないアドレス・パスワードをお使いください。 認証機能を本格的に実装する場合は必ずHTTPSを指定しましょう! これでセットアップは完了です。 これをベースに簡単なWEBアプリケーションを作っていきたいと思います。 WEBアプリのデータを保存するためにAppSyncも導入します。 AppSyncとは AWS AppSync を使用すると、1 つ以上のデータソースからのデータに安全にアクセス、操作、結合するための柔軟な API を作成でき、アプリケーション開発がシンプルになります。AppSync は、GraphQL を使用してアプリケーションが必要なデータを正確に取得できるようにするマネージド型サービスです。 AppSync を使用すると、NoSQL データストア、リレーショナルデータベース、HTTP API、AWS Lambda を使用したカスタムデータソースなどのさまざまなデータソース上で、リアルタイムの更新を必要とするアプリケーションを含む、スケーラブルなアプリケーションを構築できます。モバイルおよびウェブアプリケーションの場合、AppSync はさらに、デバイスのオフライン時にローカルデータアクセスを提供し、オンラインに戻るとカスタマイズ可能な競合の解決処理を使用したデータ同期を提供します。 引用: AWS AppSync公式 AppSync導入 amplify add api ? Please select from one of the below mentioned services: GraphQL ? Provide API name: amplifyApp ? Choose the default authorization type for the API API key ? Enter a description for the API key: ? After how many days from now the API key should expire (1-365): 7 ? Do you want to configure advanced settings for the GraphQL API No, I am done. ? Do you have an annotated GraphQL schema? No ? Do you want a guided schema creation? Yes ? What best describes your project: Single object with fields (e.g., “Todo” with ID, name, description) ? Do you want to edit the schema now? Yes AWS AppSync APIキーは作成後7日で有効期限が切れます。 初期設定後にAWS AppSync認証タイプを変更する場合は amplify update api コマンドを実行します。 amplify update api ? Please select from one of the below mentioned services: GraphQL ? Choose the default authorization type for the API (Use arrow keys) ❯ API key Amazon Cognito User Pool IAM OpenID Connect // 以下省略 amplify push コマンドを実行して、バックエンドにAPIを構築します。 そのあとにいくつか質問されますが、全てEnterでいきます。 amplify push current Environment: prod | Category | Resource name | Operation | Provider plugin | | -------- | ------------------ | --------- | ----------------- | | Api | amplifyApp | Create | awscloudformation | | Auth | amplifyappf9e59d08 | No Change | awscloudformation | | Hosting | S3AndCloudFront | No Change | awscloudformation | ? Are you sure you want to continue? Yes The following types do not have '@auth' enabled. Consider using @auth with @model - Todo Learn more about @auth here: https://aws-amplify.github.io/docs/cli-toolchain/graphql#auth GraphQL schema compiled successfully. Edit your schema at /Users/yuma/workspace/myapp/amplify-app/amplify/backend/api/amplifyApp/schema.graphql or place .graphql files in a directory at /Users/yuma/workspace/myapp/amplify-app/amplify/backend/api/amplifyApp/schema ? Do you want to generate code for your newly created GraphQL API Yes ? Choose the code generation language target javascript ? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes ? Enter maximum statement depth [increase from default if your schema is deeply nested] 2 GraphQL endpoint: https://xxxxxxxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql GraphQL API KEY: xxxxxxxxxxxxxxxxxxxxxxxx この情報は src/aws-exports.js に自動的に挿入されます。 Amplify CLIで構築すると、 amplify/backend/api/amplifyApp/schema.graphql にデフォルトでTodoモデルを作成してくれるのでそのまま使います。 type Todo @model { id: ID! name: String! description: String } queries src/graphql/queries.js に標準的なクエリまで自動生成してくれます。 /* eslint-disable */ // this is an auto generated file. This will be overwritten export const getTodo = `query GetTodo($id: ID!) { getTodo(id: $id) { id name description } } ` ; export const listTodos = `query ListTodos( $filter: ModelTodoFilterInput $limit: Int $nextToken: String ) { listTodos(filter: $filter, limit: $limit, nextToken: $nextToken) { items { id name description } nextToken } } ` ; src/App.js を編集します。 import React , { useEffect } from 'react' ; import Amplify from 'aws-amplify' ; import awsconfig from './aws-exports' ; import { withAuthenticator } from 'aws-amplify-react' ; import { API , graphqlOperation } from "aws-amplify" ; import { listTodos } from "./graphql/queries" ; Amplify . configure ( awsconfig ) ; const App = () => { useEffect (() => { const getTodos = async () => { const todos = await API . graphql ( graphqlOperation ( listTodos )) console . log ( todos ) ; } getTodos () ; } , []) return ( <div > amplify - app < / div> ) ; } export default withAuthenticator ( App, true ) ; 何も格納していないので、空ですが無事に接続できました。     mutations src/graphql/mutations.js にこちらも自動生成してくれます。 /* eslint-disable */ // this is an auto generated file. This will be overwritten export const createTodo = `mutation CreateTodo($input: CreateTodoInput!) { createTodo(input: $input) { id name description } } ` ; export const updateTodo = `mutation UpdateTodo($input: UpdateTodoInput!) { updateTodo(input: $input) { id name description } } ` ; export const deleteTodo = `mutation DeleteTodo($input: DeleteTodoInput!) { deleteTodo(input: $input) { id name description } } ` ; src/App.js編集します。 import React , { useState , useEffect } from 'react' ; import Amplify from 'aws-amplify' ; import awsconfig from './aws-exports' ; import { withAuthenticator } from 'aws-amplify-react' ; import { API , graphqlOperation } from "aws-amplify" ; import { listTodos } from "./graphql/queries" ; import { createTodo } from "./graphql/mutations" ; Amplify . configure ( awsconfig ) ; const App = () => { const [ name , setName ] = useState ( '' ) ; const [ description , setDescription ] = useState ( '' ) ; const [ todos , setTodos ] = useState ([]) ; useEffect (() => { const getTodos = async () =>; { const { data } = await API . graphql ( graphqlOperation ( listTodos )) const { items } = data . listTodos setTodos ( items ) } getTodos () ; } , []) const handleCreateTodo = async () => { if ( ! name || ! description ) return setTodos ([ ... todos , { name , description }]) await API . graphql ( graphqlOperation ( createTodo , { input : { name , description }})) setName ( '' ) ; setDescription ( '' ) ; } const handleSetName = ( e ) => { setName ( e . target . value ) ; } const handleSetDescription = ( e ) => { setDescription ( e . target . value ) ; } return ( <div > amplify - app < label > name: < / label> <input name="name" type="text" value="{name}" onChange={ ( e ) => handleSetName ( e ) } / > <label > description: < / label> <input name="description" type="text" value="{description}" onChange={ ( e ) => handleSetName ( e ) } / > <button onClick = { () => handleCreateTodo ()} >add < / button> {todos.map (( todo, i ) => ( <p key={i}>name: {todo.name} description: {todo.description}</p> )) } < / div > ) ; } export default withAuthenticator ( App , true ) ; nameとdescriptionを追加できました。 今回は mutaions で値を格納しただけなので、リアルタイムではありませんが subscriptions を使えばでリアルタイムで反映されるのもAppSyncの良いところです。 おわりに AWSをあまり触ったことがない自分でも簡単に認証機能付きのWEBアプリを公開することができました。 今回使用したauthやapi以外にも amplify add xxx で一部のAWSリソースを使うことができます。 他にも色々触ってみたいなと思いました。 最後に、株式会社スタメンでは一緒に働くエンジニアを募集しています。ご興味のある方はぜひ エンジニア採用サイト をご覧ください。
アバター
はじめまして、9月からスタメンにJoinさせていただいた @sokume です。 入社して数ヶ月ですが、熱い仲間たちとわくわくした日々を過ごしています。 今回は、スタメン本社のある名古屋ではなく、大阪で開催された Android Dev Summit 2019報告会 にLT登壇させて頂いたので、その話をさせていただきます。 Android Dev Summit '19 Android Dev Summit は Google が開催する Android 開発者のための技術イベント 今年は 10/23 、24 に Google Event Center[MP7] で開催 Android Dev Summit 2019 報告会 GDG Osaka が主催する、 Android Dev Summit '19で発表された技術を共有するためのイベント 今回が GDG Osaka の主催する初イベント :smile: メインセッション Google でRoom 1 の開発に従事されている方からの最新の発表や、今後の追加機能の話 Android Dev Summit `19 のセッション動画から気になった部分を掘り下げての説明 セッションの内容からすぐにプロジェクトに導入したいあれこれ といった内容がメインセッションの内容になります。 各セッションの内容まではふれませんが、Roomを使用した際のQuery処理の一覧は「額にいれて飾ろう」とか「公式のドキュメントにそのまま載せてほしい」というコメントがあるくらい有益な情報でした。(さすが中の人!) LT&懇親会 私もLT登壇の一人として「 Android Studio 4.0 つまみぐい!」という内容でお話をさせていただきました 内容としては、 Android Dev Summit '19で発表&リリースされた Android Studio 4.0 Canary 1 の新機能の検証をした内容になります。 ご興味のあるかたはご覧いただければと :smile: Android Studio 4.0 つまみぐい! from tsutomuhayakawa 懇親会もいろいろな方に話をしたり、話しかけて頂けたり、新しい繋がりもできとても有意義にすごせました 最後に 今回の登壇については、業務として参加させていただき、交通費等は会社に負担して頂き、大変ありがたかったです。 Android についての話をさせていただきましたが、スタメンでは他にもフロントエンド、バックエンド、デザインなどなど各分野の技術者が頻繁に外部での発信を行なっています。 今後もスタメンの技術者がいろいろな所で発信する機会があると思いますので、弊社メンバーを見かけた際はぜひお声がけください! スタメンに興味が湧いてきたという方は コチラ ご覧ください。 Roomは Android Architecture Componentsに含まれる SQLite データベースのラッパー  ↩
アバター
スタメン エンジニアの松谷( @uuushiro )です。 Rails アプリケーションにおいて認証機能にDeviseが利用されるケースは多いと思いますが、サービスの特性次第で メールアドレスをIDとした認証だけでなく、携帯電話番号をIDとした SMS認証、外部ソーシャルID連携や SAML 認証、MFA設定など多様な認証機能に対応する必要があります。その際にDeviseにモジュールを追加したりカスタマイズするなど、アプリケーション側で対応することも1つの手ですが、クリティカルな作業で 工数 がかかる割にはサービスの本質的な競争力の向上には繋がることは少ないです。できれば自前で実装するのは避けたいなと考えていたときに、 Amazon Cognito というアプリケーションで必要とする一連の認証機能を提供してくれるサービスを思い出しました。Deviseを利用した認証を Amazon Cognitoに委譲することで、実装コストを抑えることができそうだと思い調査してみました。 TL;DR (概要) Deviseを利用したユーザー認証を、 Amazon Cognito に委譲しました。主に Rails アプリケーション側で対応した内容と、 Amazon Cognito側で対応した内容の一部を共有します。 この記事では、 既にDeviseが導入されているアプリケーションの認証機能を Amazon Cognitoに委譲する想定とします。認証以外の機能(ヘルパー・ルーティング・コントローラー・セッション管理...etc)は引き続きDeviseを利用します。 認証フローは、 RFC 6749: 4.1. Authorization Code Grant で定義されている認可コードフローを採用しています。ここではOpen ID Connectに関する仕様の詳細は説明しません。必要な認証情報を Rails アプリケーションに設定をしている前提で説明をします。 認証の流れとしては、クライアントが Amazon Cognito に対して認証をし、その認証結果(認可コード)を Rails アプリケーションで受け取り、認可コードをID トーク ンと交換して、ID トーク ンの検証に問題が無ければWebサーバーがクライアントに対して Cookie (SessionID)を発行し、そのSession情報をサーバーに保存し、ログインします。 Rails アプリケーション側の対応 今回、 Amazon Cognito に委譲する Deviseの機能は以下3点です。 サインアップ・サインイン機能(database_authenticatable, :omniauthable, :registerable のモジュールが提供) ユーザー確認機能(:confirmableモジュールが提供) パスワードリセット機能(:recoverableモジュールが提供) Deviseには、独自の認証ストラテ ジー を追加できる機構があるので、それを利用してこれらの機能を Amazon Cognitoに移譲します。上記以外の、ルーティングやコントローラー、そしてセッション管理まわりの機能は引き続き、Deviseを利用します。 以下、UserモデルをDeviseで扱う場合のコード例です。 認証ストラテ ジー クラスの作成 lib/utils/devise/strategies/cognito_authenticatable.rbファイルを作成し、 Amazon Cognito の認証結果を受け取りアプリケーション側で検証・認証するストラテ ジー を作成します。ストラテ ジー クラスでは、authenticate! メソッドに認証ロジックを書き、valid?メソッドに認証をする条件を書きます。 valid?メソッドには、パラメータに認可コード(params['code'])が存在する場合 かつ 後述するstate値がセッションに記録されている値と一致すること を必須としています。 authenticate!メソッドには、認可コードを元に、認証情報が正当であるかどうかをチェックして、問題がなければWebサーバーがクライアントに対して Cookie (SessionID)を発行し、そのSession情報をサーバーに保存するという、Deviseによる認証後のフローと同じセッション管理をしています。 # lib/utils/devise/strategies/cognito_authenticatable.rb require ' devise/strategies/authenticatable ' module Devise module Strategies class CognitoAuthenticatable < Authenticatable def valid? # stateが一致(CSRF攻撃対策) && code が存在する の場合のみ認証処理に入る params[ ' code ' ].present? && params[ ' state ' ] == stored_state end def authenticate! # OpenIdConnect::UserAuthorizationは 認証情報を管理するクラス # OpenIdConnect::UserAuthorization.new.verify! の部分で適宜IDトークンの検証をする id_token = OpenIdConnect :: UserAuthorization .new.verify!(params[ ' code ' ], stored_nonce) sub = id_token.raw_attributes[ :sub ] user = User .active.find_by( sub : sub) success!(user) rescue StandardError => e Rails .logger.debug(e.message) fail ! end end end end モジュールの作成 Strategyに対応するDeviseのモジュールを作成し、対応するコントローラー・ルーティングを設定し、Deviseのモジュールに追加します。ここでは、Deviseがデフォルトで指定する、sessions_controller を Amazon Cognitoによる認証機能に対応させています。 # frozen_string_literal: true require ' utils/devise/strategies/cognito_authenticatable ' module Devise module Models module CognitoAuthenticatable end end end routes = [ nil , :new , :destroy ] Devise .add_module :cognito_authenticatable , controller : :sessions , route : { session : routes } コントローラーの修正 モデルの作成で、対応付けをした sessions_controllerを修正します。SessionsControllerのnewアクションがログイン画面に相当するため、 Amazon Cognitoがホストしている認証画面にリダイレクトするようにします。 また、 CSRF 攻撃を防ぐためのstate値と リプレイ攻撃 を防ぐためのnonce値をセッションに保存して、後に認証結果が渡ってきたときの検証に利用できるようにしています。 class Users :: SessionsController < Devise :: SessionsController def new redirect_to authorization_uri end private def authorization_uri # OpenIdConnect::UserAuthorizationは 認証情報を管理するクラス OpenIdConnect :: UserAuthorization .new.authorization_uri(set_state, set_nonce) end # CSRF 攻撃対策 def set_state session[ :state ] = SecureRandom .hex( 16 ) end # リプレイ攻撃対策 def set_nonce session[ :nonce ] = SecureRandom .hex( 16 ) end end state値とnonce値の説明については、以下の記事がわかりやすかったので必要があればご参照ください。 https://tech-lab.sios.jp/archives/8492 Deviseの設定 上の「認証ストラテ ジー の作成」で作成したStrategyをwardenに追加します。 また、デフォルトで使用する認証ストラテ ジー をuserスコープに限定して宣言をします。 # config/initializers/devise.rb require ' utils/devise/strategies/cognito_authenticatable ' config.warden do | manager | manager.strategies.add( :cognito_authenticatable , Devise :: Strategies :: CognitoAuthenticatable ) manager.default_strategies( scope : :user ).unshift :cognito_authenticatable end Userモデルも、追加したモジュールを利用するように変更します。 変更前は、Deviseのサインアップ・サインイン機能(database_authenticatable)、ユーザー確認機能(confirmable)、パスワードリセット機能(recoverable)も設定してありましたが、今後は Amazon Cognitoに移譲するため コメントアウト (削除)し、cognito_authenticatableのみにしています。 class User < ApplicationRecord # 以下のモジュールの機能は Amazon Cognitoが提供するので不要 # devise :database_authenticatable, :recoverable, :confirmable devise :cognito_authenticatable end Amazon Cognito から渡ってくる認証結果の受け口をつくる 認可コードフローの場合、認証結果として認可コードを Amazon Cognitoからアプリケーションが受け取り、検証をする必要があるので、それに対応するルーティング及びコントローラーを作成します。 # config/routes.rb namespace :auth do namespace :cognito do get ' /callback ' , action : :callback end end 以下のように authenticate!メソッド が呼ばれると、上で定義したストラテ ジー 内の、Devise::Strategies::CognitoAuthenticatable#valid?メソッドが呼ばれ、その結果がtrueの場合のみ、Devise::Strategies::CognitoAuthenticatable#authenticate!メソッドが呼ばれます。 module Auth class CognitoController < ApplicationController skip_before_action :authenticate_user! def callback resource = request.env[ ' warden ' ].authenticate!( :cognito_authenticatable , scope : :user ) redirect_to after_sign_in_path_for(resource) end end end ここまで簡単な説明でしたがアプリケーション側の説明は以上です。今後、認証方式を拡張する必要があったとしても Amazon Cognitoで対応可能な認証方式なら、アプリケーション側の認証インターフェースは変わらず対応可能です。 Amazon Cognito側の対応 スクリーンショット など設定画面の詳細は説明しませんが、アプリケーションの要件に合わせて Cognito User Poolの設定をします。アプリケーションとの連携時に必要なコールバックURLを、 Rails アプリケーション側の対応で、実装したAuth::CognitoControllerのcallbackアクションに対応するようにします。 これらの設定がうまく出来ていれば、認証が必要なページにアクセスすると、以下のような Amazon Cognitoが用意したログイン画面が表示されるので、この画面で Amazon Cognito User Pool に対して認証を行えば、その認証結果を Rails アプリケーション側が受け取り、ログインすることができます。 まとめ Deviseを利用した認証を Amazon Cognitoに委譲しました。そうすることで、 Rails アプリケーション側で実装していた、サインアップ・サインイン機能、ユーザー確認機能、パスワードリセット機能のコードが不要になりメンテナンスするコードを減らすことができました。 いままでDeviseを利用していて、以下のような問題を感じていました。 1点目は、ログインIDがメールアドレス前提の実装になっている箇所が多いことです。ユーザー確認URL・パスワード再発行などの通知はメール前提となっているので、電話番号をIDとしたSMS認証や、メールアドレス or 電話番号のどちらかをIDとするような、現在では一般的な認証IDに対応するために、Deviseのコードを読み解いてカスタマイズする必要がありました。 2点目は、外部ソーシャルID連携です。Deviseにはomniauthという機構が備わっており、外部ソーシャルID連携が認証プロバイダを介したユーザ認証方法によって標準化されています。omniauth系のgemを利用すれば外部ソーシャルID連携に対応することができるのですが、多少コードを書く必要があり、すべてのgemが活発に更新されているわけではないこともあって気軽には導入でません。 しかし、 Amazon Cognitoを利用すれば、上で言及したIDのパターンに対応しており、IDの種類に応じたメールや、SMSなどの通知機能にも対応しています。また、いくつかの外部ソーシャルIDプロバイダとの連携にも対応しており、選択するだけで簡単に利用することができます。その他もSAML2.0に対応していたり、MFAの強制ができたり、一般的に想定される認証のケースにほとんど対応することができます。今後、よほど複雑でない認証に関しては、自前で実装する前に Amazon Cognitoをまず検討してみると良いと思いました。 おわりに 今回 Amazon Cognitoを試してみて、とても簡単に認証機能を実現することができました。 Rails アプリケーションでDeviseを利用した認証を置き換える場合に参考になれば幸いです。 最後に、株式会社スタメンでは一緒に働くエンジニアを募集しています。ご興味のある方はぜひエンジニア採用サイトをご覧ください。 wantedly
アバター
こんにちは。スタメンで iOS / Android アプリを開発している @temoki です。最近、社内のモバイルアプリエンジニア体制も強化され、チームでの開発ができるようになってきました。弊社の TUNAG アプリの改善も加速していますので、今後にご期待ください! さて、先月にこのブログにて iOSDC Japan 2019 での登壇 について投稿しましたが、弊社アプリエンジニアによる外部での登壇が活発になってきていますので、この最近の登壇活動について書きたいと思います。 Nagoya iOS meetup #6 2019/8/16 に Nagoya iOS meetup という iOS アプリ開発 に関する勉強会の #6 が開催されました。この勉強会はヤフー株式会社の名古屋オフィスに在籍されているエンジニアさんが中心となって運営されているものです。今回、名古屋オフィスが移転されてから初の開催です。 この勉強会は有志による発表と懇親会で構成されています。ちょうど iOS 13 のリリースを翌月に控えていたタイミングでしたので、 iOS の新バージョンに関するホットな話題の発表が多く、会場はその期待で大きく盛り上がりました。 スタメンはドリンクスポンサーとして協賛 スタメンは今回初めてドリンクスポンサーとして協賛させていただき、弊社エンジニアたちがこよなく愛するハイ コスパ エナジードリンク 、 ライフガード ・インフィニティ♾を大量に用意しましたよ! TUNAG iOS アプリの再構築における選択 弊社からは私を含めて2名が発表しました。私の発表内容は、TUNAG iOS アプリを再構築していくあたって迫られた様々な選択、そしてそれぞれの選択結果と理由についてです。Swift UI のような新しい開発 フレームワーク が発表されましたが、その前の時点での最適な選択ができたと思っています。 この内容に関する話は、このブログにもいくつか掲載しておりますので、よろしければ併せてご覧ください。 TUNAG iOSアプリの技術的な解説 TUNAG iOSアプリのチャット機能をVIPERアーキテクチャで開発した話 Cloud Firestore のセキュリ ティー ルール そして、 インターン で iOS / Android アプリ開発 を助けてくれているカーキ @khaki_lit も発表に挑戦しました。 私の発表内容にもあるとおり、TUNAG では Firebase の Cloud Firestore を使用しています。その勉強のために個人で作っているアプリで Cloud Firestore を採用し、そこでぶつかった壁「セキュリ ティー ・ルール」について、自分で理解していった過程をわかりやすく解説してくれました。Nice play! Mobile Act Osaka #11 2019/9/20 には フェンリル 株式会社が主催されている Mobile Act OSAKA の #11 が開催されました。場所は フェンリル 本社オフィスもある グランフロント大阪 の 大阪イノベーションハブ です。 開催の4日前に登壇者にキャンセルが出たというのを聞き「ついに アレ を供養できる!」と急遽登壇を決め、大阪に遠征することにしました。急な決定でしたが、このようなチャレンジを出張として送り出してくれる弊社は本当に素晴らしいですね! この日は新しい iPhone の発売日でもあり、会場ではすでに iPhone 11 を手に入れた人がちらほら見えました(私も Apple Store 名古屋栄で実物を参拝だけしてから大阪に向かいました😅)。Mobile Act も発表と懇親会の2本立てとなります。 発表内容の中で個人的に気になったのは、どのバージョンにどの機能をリリースするかの管理をしやすくするための Feature Flag の話題 や、そろそろ TUNAG でも本格的に検討していきたいと思っている アプリのアクセシビリティの話 でした。 アクセシビリティ の話では、ちょうど往復の新幹線で読もうと思ってリュックに入れてきた Apple Human Interface Guidelines について触れられるという偶然な出来事もありました(今は入手が困難ですが一度は読んでみることをおすすめします!)。 そしてもう一つ嬉しい偶然な出来事として、なんと会場に TUNAG の元ユーザーさんがいらして、懇親会で私のところに話しかけにきてくれました!最近転職したことによりユーザーではなくなってしまったそうですが、TUNAG をポジティブに捉えていただいていたというお話も聞いたりして、普段直接ユーザーさんと接する機会は少ない私はとても嬉しかったです。 RoR + Cloud Firestore のハイブリッド構成で構築するチャット機能 そして先ほどの アレ ですが、こちらのブログ( iOSDC Japan 2019 にスタメンのエンジニアが登壇します! )でも触れた iOSDC に提出したプロポーザルの採択されなかった方のことです。これを発表できないと私の iOSDC は終わらない気がしていたので、今回この場で発表できて良かったです。 さいごに ここではモバイル アプリ開発 に関する勉強会での登壇の話となりましたが、スタメンでは他にもフロントエンド、バックエンド、デザインなどなど各分野の技術者が頻繁に外部での発信を行なっています。各メンバーが「圧倒的発信」していることにより互いに刺激を受け、さらに成長しているのが弊社のプロダクト開発チームです。スタメンに興味を持たれた方はぜひ コチラ をご覧ください。また、勉強会で弊社メンバーを見かけた際はぜひお声がけください! 直近のモバイル アプリ開発 勉強会としては、今回参加してきた Mobile Act の名古屋開催である Mobile Act NAGOYA が 2019/12/13(金) に開催される予定で、弊社のアプリエンジニアも登壇を予定しています!
アバター
こんにちは。スタメンで主にバックエンドの開発を担当しています、河井です。 この度 Firebase Cloud Messaging (以下 FCM)を使ってプッシュ通知機能を実装したのですが、具体的な実装まで踏み込んだ情報があまりなかったのでまとめようと思います。 FCM 選定の背景 世の中にプッシュ通知のサービスは多くあり、ユーザーをセグメントに分割して一斉に送信したり、配信後の分析を詳細にできたりと、それぞれ特徴をもっています。 弊社サービスの TUNAG でプッシュ通知したいケースとしては、チャットの新着通知やタイムラインでのメンションなど、通知相手が明確に定まっていて確実に通知したいものしかありません。そのため、 マーケティング 支援的な機能よりもとにかく端末指定で速く届けたいというモチベーションがありました。 また、モバイルアプリのためにもともと Firebase を使っていて、Firebase SDK がすでに導入されていました。 以上のような理由から Firebase Cloud Messaging を選定しました。 実装 FCM を使って通知を送る手段としては REST API を直接叩く方法と AdminSDK を使う方法があります。 AdminSDK が Ruby に対応していないため、 Rails アプリケーションなどから利用したい場合は REST API を直接利用して送信します。FCM の REST API にもまた2種類あり、レガシー API と v1 API が存在しますが、ここでは HTTP v1 API を使います。 事前準備 エンドポイントの設定 リク エス トを送信するエンドポイントは POST https://fcm.googleapis.com/v1/projects/プロジェクトID/messages:send の形式で設定します。プロジェクトIDは、Firebase コンソールの「プロジェクトの設定」→「全般」で確認できます。ここでは test-project とします。 秘密鍵 の取得 Firebase コンソールの「プロジェクトの設定」→「サービスアカウント」から 秘密鍵 をダウンロード。パスは './firebase-test-key.json' とします。 メッセージの送り先 FCM では instance_id という トーク ンを指定してメッセージを送信します。 iOS のデ バイス トーク ンはそのままの形では使うことができないので、Firebase SDK を使って instance_id を生成します。 ここでは 'test_instance_id' とします。 また、今回は使っていませんが、すでに保存済みのデ バイス トーク ンを instance_id に変換できる API が用意されています。 Instance ID API リファレンスページの "Create registration tokens for APNs tokens" の項目 から確認できます。 通知までの流れ OAuth 2.0 認証キーを取得 authorizer = Google :: Auth :: ServiceAccountCredentials .make_creds( json_key_io : ' ./firebase-test-key.json ' , scope : ' https://www.googleapis.com/auth/firebase.messaging ' ) access_token = authorizer.fetch_access_token! access_key = "#{ access_token[ ' token_type ' ] } #{ access_token[ ' access_token ' ] }" リク エス トボディの設定 基本形は次のようになります。 body = { message : { token : ' test_instance_id ' , # 送り先の instance_id notification : { title : ' test push notification ' # プッシュ通知タイトル body : ' notification message from fcm. ' # プッシュ通知の内容 } } } なお、HTTP v1 API では複数端末の トーク ンを指定することができません。複数端末に通知するときはトピックに対して通知する必要があります。(AdminSDK や レガシー API では複数の トーク ンを指定できるようです。) OS毎にカスタマイズしたいときは次のようにして実現できます。 body = { message : { token : token, notification : { title : ' test push notification ' body : ' notification message from fcm. ' }, apns : { payload : { aps : { sound : ' default ' , # 通知音 badge : 1 # iOS のホーム画面でのバッジ更新用 } } } android : { notification : { channel_id : ' test_category ' # Android 向けの通知カテゴリ } } } } iOS 、Andoroid のほか、ブラウザプッシュにも対応しています。 リク エス トの送信 ここでは Ruby 組み込みの HTTP クライアントを使います。 uri = URI .parse( ' https://fcm.googleapis.com/v1/projects/test-project/messages:send ' ) https = Net :: HTTP .new(uri.host, uri.port) https.use_ssl = true req = Net :: HTTP :: Post .new(uri) req[ ' Content-Type ' ] = ' application/json ' req[ ' Authorization ' ] = access_key req.body = body res = https.request(req) ステータスコード が 200 であれば送信成功です。 それ以外であれば エラーコード表 を見てリトライやエラー通知など適切に対処します。 補足: 認証キーの保持について OAuth2.0 認証キーを通知の度に取得すると、時間が余計にかかってしまうという問題がありました。 この認証キーは有効期限付きで、1時間はもつので、うまく保持できればキーのリク エス トは1時間に1回で済みます。 そこで、以下のようなメソッドを定義して、 インスタンス 変数にアクセスキーを保持します。 def get_access_key authorizer = Google :: Auth :: ServiceAccountCredentials .make_creds( json_key_io : StringIO .new(sdk_hash.to_json), scope : ' https://www.googleapis.com/auth/firebase.messaging ' ) access_token = authorizer.fetch_access_token! "#{ access_token[ ' token_type ' ] } #{ access_token[ ' access_token ' ] }" end def access_key @access_key ||= get_access_key end これで連続して通知するときに毎回キーをリク エス トしなくて済むようになります。 制限時間があるので、1時間たつと送信リク エス トがエラーになります。 リトライ処理の中で認証エラー(401)のときは @access_key = nil としてからリトライすることで、期限切れにも対応します。 おわりに Ruby で FCM の HTTP v1 API を使ってプッシュ通知を送信する方法を解説しました。 FCM は通知速度が速く、通知先のデ バイス の違いにも対応が容易です。加えて、すべての機能を無料で使うことが出来るのも嬉しい点ですね。 同じような状況の方がいらっしゃいましたら参考にしていただけると幸いです。 最後に、株式会社スタメンでは一緒に働くエンジニアを募集しています。ご興味のある方はぜひ エンジニア採用サイト をご覧ください。 アイキャッチ Photo by Jamie Street on Unsplash
アバター
スタメンのデザイナーの ( @kiyoshifuwa )です。 先日、 Adobe XD ユーザーグループ名古屋 vol.3 にて、XDの作業スピードアップについてのライトニング トーク を行いました。 そこでお話しした、いくつかのかんたんなTipsをご紹介します。 その1.便利なものは使い倒す 自分は計算しない!「四則演算機能」 数値フィールドに計算式を入力すると、計算結果がフィールドに入ります。 Illustrator や InDesign を使われる方にとってはお馴染みの機能ですね。 自分で計算するよりも速く、また計算ミスも起こらないため 変形をするときは必ずこの機能を使うようにしています。 計算の仕方は以下の通りです。 足し算:100 +2 引き算:100 -2 掛け算:100 *2 割り算:100 /2 また、カッコのついた計算にも対応しています。 ( 100+50 )*2 変形はマウスよりキーボード 少しの変形のためにわざわざマウスを動かすのが面倒なので、 Adjust Size by Shortcut という プラグイン を使っています。 オブジェクトやアートボードのサイズ変更を キーボードで、1px または 10px 単位で 行える プラグイン です。 ※デフォルトのショートカットコマンドから、作者様オススメのコマンドに割り当て直して使用しています。 もうこれ無しでは困ってしまうほど便利です✨ アートボードのリサイズは一瞬で終わらせよう アートボードのサイズをマウスで調整すると、狙ったところにピタッと止まらないことがありませんか? 何回もやり直したり、拡大表示してゆっくりあわせたりするのは時間がもったいないです。 Resize Artboard toFit Content を使えば、なんと一瞬でコンテンツに合わせてアートボードがリサイズされます。さらに、複数のアートボードを選択した場合、それぞれのコンテンツに合わせてリサイズされます。 アートボードを長めに用意して、後から縮めることが多い方には大変オススメです。 その2.ショートカットは覚えやすく押しやすく デフォルトのショートカットを覚えたり、自分で追加したりするのはもちろんですが、早いスピードを求めるのであれば「覚えやすく」「押しやすく」カスタマイズすることをおすすめします! 覚えやすく ペンツールならpenのP、ロックはLockのLなど、XDには連想しやすく覚えやすいショートカットがデフォルトで用意されています。 しかし、中にはキーと挙動がいまいち結びつかないもの、commandだったかcontrolだったかわかりづらいものもあります。 ショートカットを間違えて手が止まったり、メニューバーから選んだりするのは時間のロスになるので、 わかりにくいコマンドは自分がわかりやすくなるようカスタマイズしています。また、新規で追加する場合も同じです。 例えば、小数点以下を切り捨てる「Remove Decimal Numbers」という プラグイン を実行するショートカット。デフォルトでは何も設定されていませんが、よく使うので小数点のピリオドをそのまま使い「 command+. 」に設定してみました。 安易な考えですが、連想しやすいのですぐに覚えることができ、業務のスピードアップに大変役立っています。 押しやすく 押しやすさも重要です。 「水平/垂直方向中央揃え」のショートカットは「control+command+C/M」です。 これは「Center」のC、「Middle」のMで十分覚えやすいのですが、私は連続して使うことが多いので、隣同士でもっと押しやすい位置に変えました。 「control+command+ [ / ] 」です。 ちなみに Photoshop でも同じショートカットで登録しています。 同じことをしている人は見たことがありませんが、押しやすくてとてもおすすめなのでぜひ試してみてほしいです。 Mac でのショートカット編集方法 Photoshop や Illustrator のようにアプリケーション内からショートカットを編集することができないため、以下の方法で編集しています。 Mac のシステム環境設定 ↓ キーボード ↓ ショートカット ↓ アプリケーション ↓ 「+」から追加、または既存のショートカットをダブルクリックで編集 コマンドの名前を正確に入力し、設定したいショートカットキーを押します。 たったこれだけです👍 おわりに Adobe XDで爆速作業するためのかんたんなTipsをご紹介しました。 作業スピードを上げる方法を検討されている方は、ぜひお使いのXDでも試してみてください。 株式会社スタメンでは一緒に働く仲間を募集しています。 こちら からぜひお気軽にお話を聞きに来てください!
アバター
はじめに スタメンでエンジニアをしている 田中 です。趣味でIoTの開発をやっていて、特に環境のセンシングや情報の可視化に興味・関心があり、自宅では 二酸化炭素 濃度の計測・グラフ化をしています。 本記事では、スタメンで運用しているLaMetric Timeとその運用方法についてご紹介したいと思います。 LaMetric Timeとは Wi-Fi 経由で様々なデ バイス やサービスと連携でき、時刻の表示やアラームといった時計の機能のみならず、天気やニュース、各種 SNS の情報を取得、表示することができる、IoT時計です。 公式サイト 専用アプリからどのような情報を表示するか簡単に設定でき、それらをドット絵で表示してくれる点も魅力の一つです。 また、様々な Webサービス と連携できるIFTTTにも対応しているため、Webhookを利用してLaMetric Timeに任意の文字を表示することも可能です。 スタメンでは主に3つの方法で運用しています。それぞれについて説明します。 スタメンにおける運用方法 Bugsnagの通知 スタメンが提供しているサービス「TUNAG」ではフロントエンドにReact、バックエンドに Ruby on Rails を採用しています。 スタメンのエンジニアが作っている『TUNAG』の技術的な解説 また、それらのエラー監視としてBugsnagというサービスを利用しています。 【React】ErrorBoundary × Bugsnagによるエラー対応 Bugsnagと黒魔術で、Railsの不具合調査を楽にする仕組み! スタメンではBugsnagとSlackを連携させ、特定のチャンネルに通知を送ることでエラー内容を確認していますが、作業に集中しているとSlackの通知を見落としたり、PCから離れていると通知に気づくことが出来ません。 そこでLaMetric TimeとSlackを連携することで、Slackに送られてきたBugsnagの通知をLaMetric Timeへと送り、エラーメッセージの表示と音を鳴らしています。 ・・・しかし、エラーメッセージは様々な種類があり、また一つ一つのメッセージが長く、横スクロールの速度も早いため、LaMetric Timeのディスプレイで読み取ることは正直難しいです。そのため、エラーメッセージを読み取るのではなく、あくまでエラー通知が来たことを音で認識するために利用しています。 先日、席を離れているときにデザイナーから「LaMetric Timeから音が繰り返し鳴っているよ!」とお声がけいただいたことで、エラーに気づくことがありました。このようにエンジニアではない方でも「何かしらのエラーが起きている」と気づくことが出来るため、何か問題が起こった場合でも早急に対応することが可能です。 リリース通知 スタメンでは本番環境へのリリース時、Circle CIによるテストの通過後、 Capistrano を用いて本番環境へデプロイしています。 しかし、リリースが完了したことを確認するためには、 Capistrano の処理が完了しているかを、ログで確認するしかありません。 そこで、本番環境へのリリース時にLaMetric Timeにて通知を出すようにしました。 これにより、わざわざ確認しにいかなくてもリリースできたかを認識することができます。 仕組みとしては、IFTTTのWebHookを利用してLaMetric Timeへ通知を送っています。 本番環境へのリリース時に、 Capistrano の処理の中で、IFTTTで設定したWebhook URLにPOSTリク エス トすることでLaMetric Timeに表示することができます。 また、リク エス ト送信時にパラメータとしてリリースされたPull Requestのタイトルを含めることで、どのPull Requestがリリースされたのかが把握することが出来ます。 日々、多くのリリースを行なっていますが、「どのタイミング」で「何がリリースされたのか」をリアルタイムで把握できるため、大変役立っています。 環境センシングの可視化 スタメンでは環境のセンシングとしてNetatmoウェザーステーションを導入しています。 Netatmoウェザーステーションについて簡単に説明すると、気温や湿度、 二酸化炭素 濃度といった環境データを計測するモジュールを設置し、 Wi-Fi 経由でNetatmoサーバーに送り、公式アプリから観測することが出来るものです。 公式サイト NetatmoウェザーステーションとLaMetric Timeの連携は専用アプリにてサポートされているため、手軽に設定することが可能です。 LaMetric Timeで環境データを表示することで、表示されている気温が高ければ冷房の設定温度を下げたり、 二酸化炭素 濃度が高ければ換気をしたりと室内の環境に応じた行動の判断の手助けをしてくれています。 おわりに スタメンで運用しているLaMetric Timeとその運用方法についてご紹介しました。 実際に運用してみて思ったことは以下の点です。 IFTTTと連携すれば幅広い情報を簡単に取得、表示できる アイコンや表示する文字、通知音といった通知内容のカスタマイズがしやすい 個人ではなくチームで利用する場合は、表示内容よりも通知音の設定が重要。表示内容はLaMetric Timeが見える位置の人にしか情報が伝わらないが、通知音であれば何の音が鳴ったかによって通知内容の種類を判別できる 様々な情報を手軽に可視化・可聴化したい!と思う場合に良いと感じました。 株式会社スタメンでは一緒に働く仲間を募集しています。 こちら からぜひお気軽にお話を聞きに来てください!
アバター
こんにちは!スタメンで iOS / Android アプリ開発 を担当している @temoki です。 前回の 投稿 にて告知しましたとおり、日本最大級の iOS アプリ開発 者向けカンファレンス iOSDC Japan 2019 で登壇してきました! カンファレンスそのものも大変素晴らしいものでしたが、登壇することで得られるものはさらに大きかったので、登壇にいたるきっかけから登壇後までをブログに記録したいと思います。   きっかけ 私は父親業を最優先としてるため、昨年まではこういったカンファレンスには参加していませんでしたが、今年は子育ても少し落ち着いてきたため、 try! Swift Tokyo 2019 に1日だけ参加することができました。その参加レポートが コチラ です。ここで刺激をもらった私は、いつかはこういう場で自分が登壇できるようになりたいと思うようになりました。 そして今年の6月、弊社のテッ クリード 松谷 ( @uuushiro ) が AWS Summit Tokyo 2019 の中の Startup Architecture Of The Year 2019 というイベントにてなんとグランプリを獲得しました(その様子は コチラ のブログ記事にて)。彼は CTO に勧められたわけでもなく自らこのイベントに応募し、どうしたらグランプリを獲れるかということを最後まで考え、行動し続けてこの結果を勝ち取ったのです。 彼のこの偉業の影響を受けた私は いつかとか言っている場合ではない! と思い、iOSDC での登壇にチャレンジすることを決意しました。 このように影響を受けたのは私だけではなく、スタメンの他のエンジニアやデザイナーも然りで、最近のメンバーの社外での登壇等の活躍が増えてきています。こういった刺激の連鎖が社内で起きているのは素晴らしいですよね。 登壇のテーマ 以前に日本 マイクロソフト の開発者イベント de:code で、 エバンジェリスト 西脇さん ( @waki ) のプレゼンに関するセッションに参加した際に「プレゼンをした後にオーディエンスがアクションを起こさなければ、そのプレゼンの意味はない」というようなことを仰っていましたが、まさにその通りですよね。 「楽しかった〜」「勉強になった〜」という感想だけで終わってしまわないようなプレゼンにするためには、私自身も熱量を持って話せる内容でなければなりません。そこで提出したプロポーザルが下記の2つです。 Ruby on Rails + Cloud Firestore のハイブリッド構成で構築するリアルタイム・チャット機能 Swift Playgrounds でタートルグラフィックスしよう!🐢 前者はスタメンに入社してから最も力を入れて開発したチャット機能に関するテーマで、後者は3年前から個人的に開発しているプロダクトに関するテーマです。この2つのテーマであれば自信を持って話せる内容です。結果としては後者を採択していただきました。 そしてこのテーマで登壇するにあたり、オーディエンスにどのようなアクションをしてもらいたいかということを決めました。次の3つです。 個人開発しているエンジニアのモチベーションを高める 子供とプログラミングを楽しむ機会をつくる 名古屋のIT ベンチャー での就職を検討する 起こしたいアクション1 - 個人開発しているエンジニアのモチベーションを高める 同じエンジニアでも、なぜエンジニアをしているかという点はそれぞれ違いますよね。私はプログラミングそのものも大好きですが、それ以上に 自分がつくったものが他者に影響を与える という点が強い理由になっています。 そのため、業務とは別に常に個人的なプロダクトづくりを進めていますが、そのモチベーションを維持して作り続けるのが一番大きな課題だったりします。そこで、僕のプロダクトづくりの話を通じてそのモチベーションを上げたいと思いました。 起こしたいアクション2- 子供とプログラミングを楽しむ機会をつくる 父親になったエンジニアの多くの人に共感してもらえると思いますが、子供が大きくなってくるにつれて一緒にプログラミングをしたいなという思いが強くなってきました。そうすることができれば、私がどのような仕事をしているのかということも知ってもらうことができますし、何より自分が大好きなことを我が子と一緒に楽しめるというのは最高だからです。 しかしながら、プログラミングって難しい。私も高校生の時に本格的にプログラミングの勉強を初めては挫折するというのを何度か繰り返したので、子供に簡単に教えられるとは思えません。とはいえ Scratch のようなブロックベースのプログラミングですと、コードベースのプログラミングへの移行の壁があります。 このような課題に対して見出した答えの一つが、私がつくっている Tortoise Graphics というプロダクトで、今回の発表のテーマとなるものです。おそらく iOSDC に参加するようなみなさんでしたら、自宅に iPad があり、そのお子さんは誰に教えられることもなく、自然とその iPad を操作しているのではないかと思います。そこに Tortoise Graphics というエッセンスを加えることで、子供と一緒にプログラミングするきっかけとなるのではないかと思います。 起こしたいアクション3 - 名古屋のIT ベンチャー での就職を検討する 私が iOS アプリ開発 を本業にしようと決めた2014年のタイミングでは、名古屋でそのような就職先はほとんどありませんでした。その中で何とか就職できた後に名古屋で アプリ開発 の勉強会を始めましたが、人を集めるにもとても苦労しました。それから5年が経ち、次のようなブログ記事が話題になります。 名古屋ネット系ベンチャー地図をざっくり描いてみた(2019新春) 名古屋も面白くなってきていると思いませんか?私も2014年の時点で、事業会社で自社サービスに関わるエンジニアになりたいと思っていましたが、昨年スタメンに出会い、それを実現することができました! このように選択肢も増えてきていますので、就職や転職活動の際にはぜひ名古屋にも目を向けてもらいたいです。もちろんその中でスタメンに興味を持ってもらえると嬉しいですが、名古屋でのエンジニアコミュニティもっと活性化することが私の一番の願いです。 登壇準備 こんな思いを込めて7月中旬から登壇の準備をはじめました。準備の開始から当日までは下記のようなざっくりとした計画を立て、概ねこの計画通り進めることができました。基本的には子供が眠ってからの深夜か早朝でほぼ毎日1〜2時間程度の作業時間を捻出しました。 7月中旬〜8月中旬: 登壇内容にも関わる Tortoise Graphics の機能改善 登壇内容の流れの整理と書籍等による参考情報収集 8月中旬〜8月末 登壇用スライドの作成 9月初〜当日 登壇用スライドのブラッシュアップと発表練習 いざ登壇 私の登壇した会場は Track E で、メインの会場である Track A, B と比べると広くはありませんが、それでも100名程度は収容でき、かつ私の想定よりもたくさんの人が入場されたので、発表の直前はとても緊張しました。 しかしながら会場のスタッフの方々がみな温かく、PCの接続やマイクのチェックの方法が工夫されていたため、その準備の中で緊張が和らぎ、とても楽しく発表することができました。ありがとうございます! 私はレギュラー トーク という30分の枠での登壇でしたが、事前に2回(小声ですが)声を出しての発表練習をして、喋る内容やスライドを調整しておいたおかげで、ぴったり30分で発表を終えることができました。事前の練習は大事ですね。 そして発表したスライドはこちらになります。 後半で出てきたカナダの学校での活用事例ですが、おそらく当日もっとも会場の反応があっと思います。活用していただいていることのご連絡はそこの先生からいただいていたので、iOSDCの発表が決まってから「具体的にどのように活用されているんですか?」というメールを送ったら、実際に作成された教材や生徒のメモ、写真などを多数送っていただきました。これには私自身も感無量でしたし、今回の発表のストーリーとしては最後に目に見える成果が重要でしたので、とても助かりました。 その後 発表後の質疑応答では、子供にプログラミングを教えるにはどのような手順がベストなのか?など、私と同じくらいの子供を持つ父親からの質問をいただくきました。答えながら私が普段から考えていたことが整理されていくのも感じたので、発表してフィードバックしてもらえるというのはありがたいな〜と思いました。 また、下のリンクはTrack E の私の発表時間のツイートですが、「起こしたいアクション」として挙げていたことに繋がりそうな反応がたくさんで、ある程度うまく伝わったようで安心しました。また、この発表内容をきっかけに同僚のエンジニアが個人プロダクト開発を始めたという嬉しい話もあり、私の目的は概ね達成できたのではないかと思います。 Twitterの反応まとめ: 14:15~15:00 #iosdc #e そして一番想定外だった出来事は、なんと会場である 早稲田大学 に通う私の甥が発表を見に来てくれていました!この日の数日前に Facebook で登壇することを告知していたのですが、それを聞きつけてかけつけてくれたんです。2年ぶりくらいの再会でとても嬉しかったですし、就職活動を始めたばかりの甥に、叔父がどんなことを仕事にしているかを見せることができたのは偶然ながらも良かったです。これも iOSDC が学生に開放していてるおかげです。ありがとうございました! さいごに プロポーザルが採択された時から当日まで、毎日 iOSDC のことで頭がいっぱいで、期待と不安が入り時混じりながらの2ヶ月でしたが、とても充実していましたし、大きな達成感も得られました。また、ずっと実現したいと思っていた Tortoise Graphics のアニメーション機能も、この登壇をトリガーにして実装を終えることができました。大変なのは間違いないですが、それ以上に得られるものも大きいので、ぜひみなさんにもチャレンジしてもらいたいです! そして最後に、登壇にあたってご協力をいただいた方々に深く感謝いたします。 @uuushiro には登壇のきっかけとなる大きな刺激をもらいました @kiyoshifuwa は Tortoise Graphics とスライドのアートワークを快く引き受けてくれ、とてもカワイイものに仕上げてくれました ラッセ ル先生に Tortoise Graphics を学校の授業にご活用いただき、その様子を伝えるための資料をたくさんいただきました ありがとうございました!
アバター
こんにちは!スタメンで iOS / Android アプリ開発 を担当している @temoki です。 いよいよ明日から iOS アプリ開発 者向けカンファレンス iOSDC Japan 2019 が開催されますね!今年の iOSDC ではなんとスタメンのエンジニアも登壇します!(私 🙋🏻‍♂️ です) この一週間くらいで 事前ブログ が盛り上がっているので、それに乗っかって私の登壇内容を簡単に紹介したいと思います。 提出したプロポーザル 今回の iOSDC に向けて、私からは下記の二つのプロポーザルを提出しました。 Ruby on Rails + Cloud Firestore のハイブリッド構成で構築するリアルタイム・チャット機能 Swift Playgrounds でタートル・グラフィックスしよう!🐢 前者は弊社の創業事業である TUNAG の iOS アプリにおけるテーマで、後者は私が個人的に開発しているプロダクトの話です。 今回、後者の方をレギュラー トーク 枠(30分)で採択していただきましたので、前者の方はまた別の勉強会等でお話したいなと思っています。 Swift Playgrounds でタートル・グラフィックスしよう!🐢 3年前のある出来事をきっかけに、僕は Swift Playgrounds で動作するタートル・グラフィックス・エンジンの開発をはじめました。タートル・グラフィックスというのは教育を主の目的とした プログラミング言語 Logo の重要な機能で、私がプログラミングを始めた起源はここにあります。 そういった背景もあり、この トーク では技術的な話は半分、残り半分は私がなぜこのプロダクトを作るのかといった少しエモめの内容という構成になっています。 Logoとタートル・グラフィックスについて、そして後世に与えた影響 Swift, CoreGraphics, CoreAnimation によるタートル・グラフィックスの実装 Playgrounds Book の作り方と Subscriptions による配布方法 Swift Playgrounds 動くタートル・グラフィックスを配布して起きたこと 今回の発表のために、アートワークを弊社デザイナーの @kiyoshifuwa に作ってもらいました!自分のプロダクトが綺麗にデザインされるのはテンションが上がりますね〜 ❤️ Day1 9/6(金) 14:20〜 Track E で待っています 私の トーク は初日 9/6(金) の Track E にて 14:20 から開始します。 Logoやタートル・グラフィックスというワードに懐かしさを感じた方、Swift Playgrounds のコンテンツを作ってみたい方、自分のプロダクトを作っていくモチベーションをあげたい方、などなどこのテーマに興味のある方は、ぜひ私の トーク を聴きに来てください!
アバター
はじめに こんにちは、バックエンドチームの河井です。 スタメンでは TUNAG という社内 SNS を開発・運用しています。 SNS としての基本的な機能はそろっていますので、各ユーザーは プロフィール画像 を登録できるし、投稿には画像を添付することができます。 ですので、例えば プロフィール画像 を元に、そのユーザーの写っている画像を振り返れたら楽しそうだなーと思っていました。(※今のところ TUNAG 本体への実装予定はありません) そこで今回は、 Amazon Rekognition を使って 「入力された画像内に登録済みのユーザーが写っていれば、そのユーザーの顔を囲う枠とユーザー名を描画した画像を返す。」 というものを作ってみます。 Amazon Rekognition の概要 Amazon Rekognitionとは、深層学習をベースとした汎用的な画像認識サービスです。 画像のクラス分類から、物体検出、感情分析、テキスト抽出など、よくある画像処理タスク全般に対応しています。 その中から、顔の検索機能を使っていきます。 顔の検索機能とは、画像から検出した顔に関する情報をコレクションと呼ばれるコンテナに保存しておき、そのコレクション内の顔情報に対して検索できるものです。 使用する機能 今回の目的のために必要になる機能は、 コレクションを作る 顔情報を登録する 顔情報を検索する の3つになります。それぞれ解説していきます。なお、これから紹介していくのは Ruby SDK のコードになります。 ドキュメントは こちら を参照ください。 コレクションを作る コレクションの作成には CreateCollection を使います。 リク エス ト resp = client.create_collection({ collection_id : " myphotos " , }) レスポンス # resp.to_h { collection_arn : " aws:rekognition:us-west-2:123456789012:collection/myphotos " , status_code : 200 , } 顔情報を登録する 続いて、顔情報の登録には IndexFaces を使います。 リク エス ト resp = client.index_faces({ collection_id : " myphotos " , image : { bytes : image_bytes, s3_object : { bucket : " mybucket " , name : " myphoto " , }, }, }) 作成済みコレクションの collection_id に対して顔情報を登録します。 image キーに対しては、画像ファイルを直接指定することもできるし、S3の バケット とファイル名を指定することもできます。 現状だと入力画像に写っている大きい顔から100人分まで処理してくれるようです。 当然ですが、写真がボケていたり暗かったりすると精度が出ないので、それぞれの顔がしっかり写っている必要があります。 レスポンス # resp.to_h { face_records : [ { face : { bounding_box : { height : 0.33481481671333313 , left : 0.31888890266418457 , top : 0.4933333396911621 , width : 0.25 , }, confidence : 99.9991226196289 , face_id : " ff43d742-0c13-5d16-a3e8-03d3f58e980b " , image_id : " 465f4e93-763e-51d0-b030-b9667a2d94b1 " , }, face_detail : { # 省略 }, } ], orientation_correction : " ROTATE_0 " , } 顔の向きやパーツの位置など色々な情報が返ってきますが、今回は face_id と bounding_box を使用します。 face_id は画像から検出された顔の特徴ベクトルに対応するもので、同じ人物と判断されるからといって同じ face_id をもつわけではないことに注意してください。 顔情報を検索する 登録済みの顔情報の検索には SearchFaces を使います。 リク エス ト resp = client.search_faces({ collection_id : " myphotos " , face_id : " 70008e50-75e4-55d0-8e80-363fb73b3a14 " , face_match_threshold : 90 , max_faces : 10 , }) IndexFaces で返ってきた face_id で登録済みの顔情報を検索します。 face_match_threshold を指定しておくことで、confidence の 閾値 を設けることができます。 レスポンス resp.to_h outputs the following: { face_matches : [ { face : { bounding_box : { height : 0.3259260058403015 , left : 0.5144439935684204 , top : 0.15111100673675537 , width : 0.24444399774074554 , }, confidence : 99.99949645996094 , face_id : " 8be04dba-4e58-520d-850e-9eae4af70eb2 " , image_id : " 465f4e93-763e-51d0-b030-b9667a2d94b1 " , }, similarity : 99.97222137451172 , } ], searched_face_id : " 70008e50-75e4-55d0-8e80-363fb73b3a14 " , } リク エス トした face_id をもつ顔と同じものだと判定された顔情報が、一致度が高い順に並んだ配列として返ってきます。 補足 顔の検索には他にも SearchFacesByImage というオペレーションがあり、SearchFaces は face_id での検索だったのに対し、こちらは画像による検索ができます。 しかしながら、このオペレーションでは画像に一番大きく写っている顔で検索されるため、画像に写っている全員で検索することはできないので今回は使用しません。 実装 まず、User と RekognitionFace という2つのテーブルを定義します。 ※将来的に Web 上で動かしたいと思っているので、(今回の実装上あまり意味がありませんが) Rails 上でテーブルやそれを操作するクラスの定義をしています。 User は自身の名前 name を持ちます。 RekognitionFace には、user_id と face_id を結びつける役割を持たせます。 前述の通り face_id は User 固有ではないため、User と RekognitionFace を一対多の関係として保持します。 これによって、 face_id を使うことで RekognitionFace を絞り込み、入力画像に写っているユーザー名を取得できる image_id を使うことでそのユーザーが写っている画像の一覧を取得できる という機能が実現できます。前者の機能を実装してみます。 AmazonRekognition というクラスを定義しておき、入力と出力を必要なものだけにしておきます。 class AmazonRekognition class << self def index_faces (image_path) res = client.index_faces({ collection_id : collection_id, image : { bytes : File .open(image_path, & #039;r+b') } }) res.to_h[ :face_records ].map { | face | [face[ :face ][ :face_id ], face[ :face ][ :bounding_box ]] }.to_h end def search_faces (face_id) res = client.search_faces({ collection_id : collection_id, face_id : face_id, face_match_threshold : 95 }) searched_face_id = res.to_h[ :searched_face_id ] matched_faces = res.to_h[ :face_matches ] return if matched_faces.blank? matched_face_id = matched_faces[ 0 ][ :face ][ :face_id ] { searched_face_id : searched_face_id, matched_face_id : matched_face_id } end def search_all_faces_by_image (image_path) faces = index_faces(image_path) matched_faces = faces.map do | face_id , bounding_box | result = search_faces(face_id) next if result.blank? result[ :searched_face_bounding_box ] = bounding_box result end matched_faces.compact end private def client @client = Aws :: Rekognition :: Client .new( region : & #039;ap-northeast-1', access_key_id : & #039;*****', secret_access_key : & #039;*****' ) end def collection_id & #039;myphotos' end end end RekognitionFace では、face_id と user の橋渡しをします。 class RekognitionFace < ApplicationRecord belongs_to :user class < 1 user.rekognition_faces.create( face_id : res.keys[ 0 ]) end def search (image_path) faces = AmazonRekognition .search_all_faces_by_image(image_path) matched_face_ids = faces.map { | face | face[ :matched_face_id ] } where( face_id : matched_face_ids) end end end User クラスでは、クラスメソッドとしてその画像に写っているユーザーを取得するもの、 インスタンス メソッドとしてそのユーザー顔画像を登録するためのものを定義しておきます。 class User < ApplicationRecord has_many :rekognition_faces class << self def in_the_image (image_path) faces = RekognitionFace .search(image_path) where( id : faces.pluck( :user_id )) end end def register_face (image_path) rekognition_faces.factory!( self , image_path) end end これらを使って進めていきます。 動かしてみる 下準備として、 画像の用意、ユーザーの作成、画像の登録をします。 画像はぱくたそさんからもらってきた以下の3枚で、最初の2枚を登録に使い、最後の1枚を検索に使います。それぞれ画像の下に書かれているファイル名で保存しておきます。 Okawa-san.jpg Yusei-san.jpg test.jpg 次にユーザーを作成します。 user1 = User .create( name : ' Okawa-san ' ) user2 = User .create( name : ' Yusei-san ' ) 最後に、各ユーザーの画像を登録します。 user1.register_image( ' Okawa-san.jpg ' ) user2.register_image( ' Yusei-san.jpg ' ) 結果を表示するのに、RMagick を使って直接画像に書き込みます。 画像に写っている人物のうち、登録されているユーザーについて、顔の位置とそのユーザー名を表示するものです。 image_path = ' test.jpg ' matched_faces = AmazonRekognition .search_all_faces_by_image(image_path) return if matched_faces.size == 0 image = Magick :: ImageList .new(image_path) gc = Magick :: Draw .new matched_faces.each do | face | user = RekognitionFace .find_by( face_id : face[ :matched_face_id ])&.user next if user.blank? bbox = face[ :searched_face_bounding_box ] x1 = rimg.columns * bbox[ :left ] y1 = rimg.rows * bbox[ :top ] x2 = rimg.columns * (bbox[ :left ] + bbox[ :width ]) y2 = rimg.rows * (bbox[ :top ] + bbox[ :height ]) gc.fill_opacity( 0 ) gc.stroke( ' red ' ) gc.stroke_width( 3 ) gc.rectangle x1, y1, x2, y2 gc.pointsize( 50 ) gc.text(x1, y1 - 25 , user.name) end gc.draw(image) image.write " tmp/result.jpg " 一つ注意点として、bounding_box の値は入力画像の幅と高さの割合を示しているので、実際の座標を得るためには計算する必要があります。 実行結果↓ うまく認識してくれましたね。 まとめ Amazon Rekognition の顔の検索機能を使って、写真から個人を検出する機能を実装してみました。 非常に高精度で、基本的にユーザーあたり1枚登録してあれば検出できます。 機械学習 、特に深層学習は精度を出すのにコツがいるところもあるので、まずはこういった クラウド サービスで挙動を見てみるのは、イメージを掴むのに良いのではないかと思いました。 最後に、スタメンではこれからの TUNAG の機能を一緒に作っていくエンジニアを募集しています。ご興味のある方はお話だけでもできると嬉しいです。よければ こちら (や DM でも)からお願いします!
アバター
こんにちは。スタメン エンジニアのミツモトです。 スタメンで開発しているサービス TUNAG では、JSの フレームワーク として部分的にReactを採用しています。 最近、Reactのバージョンアップが行われ、それに伴いエラー対応を行ったので、 今回はその事についてご紹介させていただきます。 目次 はじめに ErrorBoundary ErrorBoundary × Bugsnag おわりに はじめに Reactのバージョンをv15.4.2 → v16.8.6 に上げたことで、エラーが発生した場合の表示が変わりました。 v15では、描画された コンポーネント はエラーが発生しても画面が変わらなかったのが、v16だと コンポーネント のツリー全体がアンマウントされる(表示されなくなる)ようになりました。 壊れた コンポーネント がアンマウントされるのは良いのですが、このままだと、サービスを利用するユーザーにとっては、「何が起きたんだ?」となり、UXとして良くありません。そこで登場するのがErrorBoundaryです。 ErrorBoundary 公式 自身の子 コンポーネント ツリーで発生した JavaScript エラーをキャッチし、エラーを記録し、クラッシュした コンポーネント ツリーの代わりにフォールバック用の UI を表示する React コンポーネント class ErrorBoundary extends React.Component { constructor(props) { super (props); this .state = { hasError: false } ; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true } ; } componentDidCatch(error, errorInfo) { // You can also log the error to an error reporting service logErrorToMyService(error, errorInfo); } render() { if ( this .state.hasError) { // You can render any custom fallback UI return ( <div> アプリケーションエラーにより表示できません。時間を置いて、再度更新してください。 </div> ) } return this .props.children; } } // エラーハンドリングしたい他のコンポーネントを囲う <ErrorBoundary> <MyWidget /> </ErrorBoundary> getDerivedStateFromError と componentDidCatch は、 コンポーネント のレンダー中、ライフサイクルメソッド内、コンスト ラク タ内でエラーが発生したときに呼び出されます。 上記のコードでは、 getDerivedStateFromError により、 hasError の state を切り替えて代わりとなる コンポーネント を表示し、 componentDidCatch によってエラー情報を記録しています。 囲った コンポーネント だけでなく、配下の コンポーネント のエラーもキャッチします。 ErrorBoundary を使うと、以下のように代わりの コンポーネント が表示されます。 ErrorBoundary × Bugsnag 当初は、公式のようにErrorBoundaryを実装していました。スタメンでは、TUNAGのエラー監視に Bugsnag というサービスを採用しているのですが、Bugsnag が ErrorBoundary を扱える パッケージ を提供していることを知り、エラーの通知含めシンプルに実装できるため、そちらに変更しました。 必要なパッケージ dotenv @bugsnag/js @bugsnag/plugin-react 実装 まずは、 Bugsnag の API キーを 環境変数 として定義します。 // .env BUGSNAG_API_KEY=*****************************(取得したBugsnagのAPIキーを入力) webpack の DefinePlugin により、 process.env.BUGSNAG_ API _KEY で、 環境変数 の API キーを呼び出せるようにします。 // webpack.config.js // 抜粋 const webpack = require( 'webpack' ); require( 'dotenv' ).config(); <200b> module.exports = () => { return ( { plugins: [ new webpack.DefinePlugin( { 'process.env' : { 'BUGSNAG_API_KEY' : JSON.stringify(process.env.BUGSNAG_API_KEY), } } ), ] , } ); } ; bugsnagClient、ErrorBoundary、エラー発生時に代わりに表示する コンポーネント ( DefaultFallbackComponent )を用意します // src/utilities/bugsnag/bugsnagClient.js import bugsnag from '@bugsnag/js' ; const bugsnagClient = bugsnag(`$ { process.env.BUGSNAG_API_KEY } `); export default bugsnagClient; // src/utilities/ErrorBoundary.js import React from 'react' ; import bugsnagReact from '@bugsnag/plugin-react' ; import bugsnagClient from 'utilities/bugsnag/bugsnagClient' ; bugsnagClient.use(bugsnagReact, React); const ErrorBoundary = bugsnagClient.getPlugin( 'react' ); export default ErrorBoundary; export const DefaultFallbackComponent = () => { return ( <div> アプリケーションエラーにより表示できません。時間を置いて、再度更新してください。 </div> ); } ; 最後に、エラーをキャッチしたい コンポーネント を囲います。その際、 ErrorBoundary の FallbackComponent プロパティに、代わりに表示する コンポーネント を渡します。 import ErrorBoundary, { DefaultFallbackComponent } from 'src/utilities/ErrorBoundary' ; <ErrorBoundary FallbackComponent= { DefaultFallbackComponent } > <MyComponent /> </ErrorBoundary> 以上で、ユーザーに対して適切なエラー画面を表示でき、同時にエラーを通知できます。 おまけ(Bugsnagのエラー通知にユーザー情報を付与) bugsnagClientにユーザー情報を渡すことで、Bugsnagのエラー通知にユーザー情報を含められます。 bugsnagClient.user = { companyId: 1, userId: 1, name: 'テスト 太郎' , } ; Bugsnagの画面 4. おわりに ErrorBoundary × Bugsnag による React のエラー対応についてご紹介しました。 React をバージョンアップしたことで、新たな API やライブラリを使うことができるようになったので、これから新機能を開発するのが楽しみです。 スタメンではフロントエンド含め、 エンジニアを募集 しています。 興味のある方は是非ご覧ください!
アバター
TL;DR こんにちは、スタメンの 津田 です。スタメンが提供しているサービス、TUNAGには、チャット機能があります。ブラウザベース、 Rails による REST API + Reactで構築されていたのですが、特にモバイルアプリケーションのユーザー体験を向上させたいということで、昨年末にモバイルアプリケーションチャット機能のネイティブ化と、それに伴うサーバー側の一部再構築を行いました。 その際、 Cloud Firestore を クラウド 上のキャッシュデータのような形で利用したため、その利用方法について紹介させていただきます。 なお、同時期に行っていたモバイルアプリケーション側の改修については、 @temoki が、「 TUNAG iOSアプリのチャット機能をVIPERアーキテクチャで開発した話 」で紹介しています。 設計概要 Cloud Firestore は、NoSQL クラウド データベースで、 SDK を通してモバイルアプリケーションから直接アクセスできます。そのため、いわゆるWeb API + DBを用意せずにモバイルアプリケーションのみで他ユーザーとのデータ共有を含むアプリケーションを作成できます。 今回のチャット再構築に関しては、すでにDBと API は存在していたのですが、そのような状況でも、Cloud Firestoreには以下のメリットがあると考え、採用しました。説明に関しては、 Cloud Firestore からの引用です。 リアルタイム アップデート Realtime Database と同様に、Cloud Firestore はデータ同期を使用して、すべての接続端末のデータを更新します。ただし、シンプルな 1 回限りの取得クエリを効率的に実行するようにも設計されています。 オフライン サポート Cloud Firestore は、アプリでアクティブに使用されるデータをキャッシュします。これによりアプリは、端末がオフラインになっている場合でもデータの書き込み、読み取り、聞き取り、クエリを実行できます。端末がオンラインに戻ると、Cloud Firestore でローカルに行われた変更がすべて同期されます。 上記の、モバイルアプリケーションとのデータ同期に関する利点を得るために、Cloud Firestoreをモバイル端末に対するデータの配信・同期の仕組みとして利用することにしました。具体的には、以下の図のような構成になります。 データの更新、チャットの発言、設定の更新等に関しては、 Webブラウザ のクライアントもモバイルアプリケーションも REST API 経由で行います。モバイルアプリケーションから、Cloud Firestoreへ直接書き込むことはありません。 データの表示に関しては、 Webブラウザ は従来通りDB(Aurora)から REST API 経由での取得、モバイルアプリケーションは、AuroraからCloud Firestoreを経由して伝達されます。 REST API は Ruby on Rails で作成しているため、Cloud Firestoreへのデータ同期は、非同期処理のGem、 sidekiq を利用しました。 比較対象とした構成 Cloud Firestoreを利用しない場合、オフライン時キャッシュや、リアルタイムのデータ取得などの機能をモバイルアプリケーション側で実装する必要があるのはもちろん、サーバー側でもデータ取得の効率等を考えて API を追加実装する必要があります。Cloud Firestoreにも制限はありますが、メリットがデメリットを上回ると考えました。 同期するデータ 同期するデータは、基本的には RDB にあるもののコピーなのですが、「セキュリティルール」と、「平坦化」に留意する必要がありました。 セキュリティルール モバイルアプリケーションは直接Cloud Firestoreに対してクエリを発行するため、Cloud Firestoreは単体で特定のユーザーに対して公開可能な情報を判断できる必要があります。 具体的には、 Cloud Firestore セキュリティ ルールを使ってみる で説明されているセキュリティルールでデータに対するアクセス制御が表現できるように設計する必要がありました。Cloud Firestoreのデータは ファイルシステム のようなツリー状になっているので、「あるパスに対しての権限を カスタム関数 で表現できるか?」を考えると判断しやすかったです。 平坦化 データの内容に関しては、どこまでデータの平坦化をするかを考える必要がありました。Cloud FirestoreはNoSQLデータベースなので、 RDB のようなデータのジョインは得意ではありません。たとえば、あるユーザーのデータをCloud Firestoreに同期する際、所属する部署の情報が RDB では別のテーブルで管理されていたとしても、Cloud Firestoreへ同期する際は全てを結合して、一つのドキュメントとして同期する必要があります。 RDB 内でのデータ。 Cloud Firestoreのドキュメントとしては、以下のように平坦化。 user: { name: 'マルチ 太郎', departments: ['花火部', '火薬部'] } 平坦化自体はそれほど大変ではないのですが、平坦化した結果、 RDB 側の一つのレコードに対する変更が、Cloud Firestoreの複数のドキュメント更新の引き金となるケースが出てきます。ユーザーと所属部署の例で言えば、部署の名前が変更されると、その部署に所属している全てのユーザーデータをCloud Firestoreへ同期し直す必要が発生します。このようなケースの洗い出しは、データの構造によってはかなり大変になります。 また、基本的にデータは平坦化するのですが、5つの部屋に入室しているユーザーがいたとして、その5室すべてに、ユーザーの情報を登録していると、データの重複が多くなってしまい、また同期も大変になります。こういったケースではデータの平坦化をせず、クライアント側でジョインしてもらうような選択も行いました。 認証 Cloud Firestoreへの接続は、ネイティブアプリケーションから直接行うため、そのネイティブアプリケーションがどのユーザーに該当するのかを、Cloud Firestoreへ認証させる必要があります。 「 Firebase : カスタム トークンを作成する 」には、以下のように記載されています。 Firebase では、保護された JSON Web Token(JWT)を使用したユーザーまたはデ バイス の認証が可能であるため、認証に対する完全な制御が得られます。サーバーでこうした トーク ンを生成し、クライアント デ バイス に返した後、signInWithCustomToken() メソッドで認証するためにこの トーク ンを使用します。 これを行うには、ユーザー名やパスワードなどのログイン認証情報を受け入れて、その認証情報が有効であればカスタム JWT を返すサーバー エンドポイントを作成する必要があります。 今回は、既存のアプリケーション自体でログインの認証が行えるため、認証後にカスタムJWTを返すエンドポイント( REST API )を追加しました。 トーク ンの作成に関しては、Firebase Admin SDK が Ruby をサポートしていないので、「 カスタム トークンを作成する : サードパーティの JWT ライブラリを使用したカスタム トークンの作成 」の例を参考にして実装しました。 同期の制御 データの同期に関しては、各モデルで若干の違いはあるのですが、概ね以下のような方針で共通の処理を作り、利用しました。 対象のドキュメントを更新する必要があるかどうかを、 RDB 側で判断できる情報を持たせる リトライを前提とし、何度実行しても問題ないように作成する 複数のドキュメント更新が必要となるケースがほとんどなので、一括書き込みを使用する 1.同期判定 sidekiqのワーカー自体、ドキュメント更新のきっかけとなりうる処理が発生したために起動しているのですが、その中でも、「どのドキュメントが実際に同期する必要があるのか?」は個別にレコードをみて判断するようにしました。これは、同じレコードに対して複数の同期処理が呼び出されるケースがあるのと、リトライを前提としているために、リトライ時、既に同期が終わっているドキュメントを再度処理しないようにするためです。 Cloud Firestoreに登録されているドキュメントと、 RDB に登録されているレコードに差が生じているかについては、DBのレコードに、updated_at(更新時刻)と、firestored_at(前回Cloud Firestoreに同期された際のupdated_at)を持たせ、一致しているかどうかを見ることにしました。差が生じていた場合は、Cloud Firestoreへ同期を行い、完了後にfirestored_atをupdated_atの値で更新します。 データの平坦化を行った関係で、あるテーブル(A)のレコードに基づくドキュメント更新要否が、他のテーブル(B)のレコードに依存する場合は、 Aのfirestored_atと、A, Bのupdated_atのより新しい方、を比較する Bのレコードに更新があった場合、関連するAのupdated_atを更新しておく(Active Recordのtouchを利用) のどちらかで行いました。ここが一番面倒だったので、もっといい方法はないかなー、と思っています。 2. リトライを前提とする Cloud Firestoreの API は結構エラーを返します。リトライをすれば成功することがほとんどなので、リトライ自体はsidekiqの仕組みを利用し、更新の処理は何度実行しても問題ないように作成を行いました。 3. 一括書き込み Cloud Firestoreには、 トランザクションと一括書き込み という機能があります。処理が失敗した場合は単純にリトライするため、 トランザクション 機能は必須ではなかったのですが、一つ一つ書き込むのに比べてパフォーマンスが出るため、基本的にこちらを利用して書き込みを行いました。 ただ、一括書き込みには最大 500 件という制限があります。前述の注意点も含め、以下のような処理を行う共通機能を作成しました。 1. 更新が必要な可能性のあるレコードのリストを受け取る 2. レコードがなくなるまでループ a. Cloud Firestoreのトランザクションを開始 b. 一件ずつループ - レコードの更新要否をチェック - 必要であれば、 Cloud Firestoreに対して書き込み c. 書き込みが500件溜まったら、トランザクションをコミットし、対象となったレコードのfirestored_atを更新 ただ、この方法だとCloud Firestoreへデータが反映されるタイミングが遅くなる(他のレコードも含めてコミットされた後になる)ため、特に更新を早くしたいドキュメントがある場合は、 トランザクション を分けるなどの対応もしています。 テスト環境の構築 Realtime Databaseと違い、Cloud FirestoreはFirebaseプロジェクト一つにつき、一つしか存在しません。そのため、開発環境や、ステージング、テスト環境用に独立したものをいくつも用意するのが困難です。 そこで、production以外の環境では、Cloud Firestoreをルート直下から、 /env/developer-A/ここ以下にプロダクションのツリー /developer-B/ のように分けて、 環境変数 で指定するようにしました。 ただ、開発環境では、しばしば RDB の中身を入れ替えたりします。その場合は、全データの再同期をしない限り、どうしても RDB のデータとは差が出てしまいます。これについて、現在は諦めています。 データの検索 Frebase : 全文検索 では、Cloud Firestoreでの 全文検索 について、以下のように説明されています。 Cloud Firestore では、ネイティブ インデックスの作成やドキュメント内のテキスト フィールドの検索をサポートしていません。さらに、コレクション全体をダウンロードして、クライアント側でフィールドを検索することは現実的ではありません。 基本的には、Cloud Firestoreではテキストの検索は実現できません。今回は、Cloud Firestoreへ同期するのと同じように、 Amazon Elasticsearch Service へ同期し、検索に関してはそちらで行うようにしました。 Elasticsearchをモバイルアプリケーションへ直接公開するのは難しいので、検索リク エス トは Rails から行うようにしました。モバイルアプリケーションはまず Rails に対して検索を行い、レスポンスに含まれる、Cloud Firestore上のドキュメントのパスリストから、Cloud Firestoreに格納されている実際のデータを参照することになります。 料金 料金に関しては、「 Cloud Firestore の課金について 」のデータをもとに試算し、試験運用で確認しました。すでに動いているアプリケーションなので書き込み数は計算しやすかったのですが、読み取り数に関しては実際に動かしてみないとどれほど発生するのかが推測しづらかったです。 まとめ Cloud Firestoreを使用した新しいモバイルアプリケーションでは、大きくユーザー体験を向上させることが出来ました。モバイルアプリケーションからは直接更新しないなど、若干、変則的な使い方ではあると思いますが、利用して正解だったと思っています。
アバター
2019年4月に新卒入社した、フロントエンドエンジニアの渡邉です。 8/6に開催された「ヤフー名古屋 Tech Meetup #3 - Webフロントエンドを支えるノウハウ」に登壇してきました。 そこで発表した内容と、自分がエンジニアとして初めて登壇して感じたことを今回記事にしました。 目次 登壇内容 Reactのバージョンを上げた経緯 バージョンを上げた時のハマりポイント 登壇して感じたこと おわりに 登壇内容 今回話した内容は、「Reactのメジャーバージョンアップ ~React- Rails を使った事例~」です。 バージョンを上げようと思った経緯、実際にバージョンを上げるまでにハマったポイント、バージョンアップをした後の対応について話しました。 https://speakerdeck.com/yun8boo/react-version-up 今回の記事で全てを話すと長くなるため、一部をご紹介します。 詳細はスライドを見ていただければと思います。 バージョンを上げた経緯 既存のReactのバージョンは15.4.2で、使いたい機能や関連ライブラリが使えませんでした。(ex. Fragment, Suspence, lazy, styled-components 等)そのため、バージョンを上げることに決めました。 バージョンを上げた時のハマりポイント package. json 最初にpackage. json を見たのですが、Reactの新バージョンが記載されているのに、旧バージョンのエラーが起きていました。 その理由としてはwebpackのexternalsオプションでReactが設定されていたためです。 React- Rails をのReactが読み込まれていた externalsで外部ファイルを見ていたため、 cdn でReactが読み込まれているかと思いきや、React- Rails のReactが読み込まれていました。 そこで自分はReact- Rails が使われていることに気づきました。 今回バージョンを上げて感じたのは、「開発環境をちゃんと知ること」の重要性です。 最近は簡単に1コマンドとかで環境構築ができますが、実際にどのように構築されているかを理解しておかないと、自分のようにwebpackの設定で躓いたりして、調査に時間がかかり無駄な時間を過ごしてしまいます。 なので、環境を知った上で開発することが大事だと感じました。 登壇して感じたこと 登壇する前は、 「勉強会には参加しているけど、登壇やLTするのは気が引ける」 「自分なんかが発表しても需要があるのか」 と考えてしまい、苦手意識を持っていました。 でも実際に発表してみると、同じ経験を実際にしている方がいたり、思っていたより需要があったため、今回登壇してよかったと思いました。 登壇を経て自信がつき、これからもガンガン発信していきたいと思うようになりました。 フロントエンド業界を盛り上げて行きたいと思います。 おわりに 今回の TechMeetup を経て、普段扱っている技術について、より深い知識を身につけることができ、改めて登壇して良かったと思いました。 スタメンでは、エンジニアチームの行動指針として「誇りに思えないなら十分でない」という指針を定めているのですが、今回の登壇資料を作成する時も意識しました。 エンジニアチームの行動指針については こちら をご覧ください。 行動指針に共感し、自分自身、そしてサービスを改善&成長していけるエンジニアを募集しています! 是非 エンジニア採用サイト からご応募ください。 ブログを最後まで読んでいただいて、ありがとうございました!!
アバター
こんにちは。エンジニアの河井です。 7月11に日に開催された Google Developers ML summit Tokyo の参加報告記事です。 参加の背景 サービスの規模が拡大してきたこと、データ集計基盤を構築したこと、 ダッシュ ボードを整備したことなどから、次は分析を自動化できると良いのではないかと個人的に考えていて、情報収集のために参加しました。 Jeff Deam 氏によるキーノートセッション Google の AI 研究チーム Google Brain のトップを務める Jeff Dean 氏のセッションです。 機械学習 を必要とする問題に対して専門家が少なすぎるという問題意識から、現在の solution = ML expert + data + computation という関係性を、将来的には solution = data + computation にしたいという強いモチベーションをもっていることが伝わってきました。 そのような文脈から研究が進んでいる AutoML は、データに適合する 機械学習 モデルを自動で探索します。 画像・ 自然言語 ・テー ブルデー タなど、様々な形式のデータに対して成果が出てきているようです。 画像認識において、AutoML の自動探索によるモデルが専門家が構築したものより高精度を出したというものです。上のスライドは こちらの論文 に載っています。 こちらは 機械学習 モデルの精度を競う コンペティション で、テー ブルデー タ課題において AutoML チームが2位に入賞した話です。報告記事は こちら です。 他にも 機械学習 全般の話や、AI開発におけるガイドなどありましたが、総じて AutoML を強く推している印象を受けました。 機械学習 の各分野で次々と成果を出している Google が、この世界観をどのレベルで実現するのかを考えるとワクワクしますね。 Auto ML について 続いて クラウド サービスとしての AutoML 関連のセッションに参加しました。 精度の高いモデルを作るという点が強調されがちだが、プロダクションへの配信からその後の運用までを幅広くカバーするものだということを説明していました。 学習時に優先する要素の設定やモデルのバージョン管理機能なども含め、 機械学習 のワークフローにおいて属人性を排することに繋がりそうなので、プロダクション環境で 機械学習 システムを運用する際には選択肢として入ってきそうだなという印象を受けました。 AutoML Vision 活用事例 Twitter で話題になった ラーメン二郎 分類器の開発者である土井さんの発表で、もともと自前で実装・チューニングしたモデルと AutoML Vision で学習したモデルを比較してみたという話です。 土井さんが自前で学習したモデルは、最新の手法を論文から取り入れつつモデル構築やデータ拡張・パラメータチューニングをしたもので、分類精度は 99% を超えるということで、非常に精度が良いモデルが出来上がっています。 対する AutoML の成果ですが、限界まで学習させた結果、なんと 98% の精度を達成しました。 本当にデー タセット と アノテーション だけで、専門家がチューニングしたモデルに匹敵するものが出来上がるとは、と感動しました。しかしながら今はまだ完璧ではなく、学習時間が長い、モデルサイズが大きくて推論時間が遅い、それでいて精度はまだ負けています。 PoC的なものはこれでサクッと立ち上げて、プロダクションに乗せるとなったら専門家がチューニングしたものを使うのが良さそうです。 まとめ 機械学習 研究の最先端を行く企業の中の人達の話を直接聞ける機会はとても貴重で、多くの学びがありました。 専門家がいなくても 機械学習 を、というメッセージを色々な場面よく見かけるようになりました。誰でも精度の高いモデルを作れるということだけがその真意ではなく、学習から配信・運用までを クラウド サービスのエコシステムでまとめて取り扱えるようにする、という意味合いも兼ねています。 精度面ではまだまだ専門家に劣るところはあるものの、 機械学習 技術の研究は日々めまぐるしく進歩しているので、今後に期待しつつキャッチアップを怠らないようにしたいですね。
アバター
こんにちは! 最近、高速化にハマっているRailsエンジニアのシュール( @shule517 )です。速くなった時の感動が半端ないですよね! はじめに 不具合調査ってめちゃめちゃ大変じゃないですか? 問題の原因が分かっていなくて、手元で不具合が再現できない時は、調査がかなり難しいです。そのため、不具合が発生したタイミングにできるだけ多くのデバッグ情報を残したくなりますよね! ということで、今回のテーマは「不具合調査の時間を短縮するための仕組み」についてです! Bugsnagで例外がどこで発生したのかすぐ分かる! 「 スタメンの開発環境について 」で紹介をしましたが、Railsのエラー監視に Bugsnag というサービスを使っています。Slackとの連携ができ、リアルタイムに通知が飛んでくるので、すぐに問題に気づくことができます! 実際の画面はこちらです。 Bugsnagで分かる内容 どこで、どんな例外が発生したのか 例外を踏んだユーザーは誰なのか どんな環境なのか(OS / ブラウザの種類) などなど、Bugsnagを導入するとたくさんの情報を集めてくれます! とても便利!! でも、もう少しデバッグ情報がほしい! 不具合を調査するとなると、Bugsnagでこんな情報が見れると嬉しいですよね! 例えば・・・ メソッドに渡ってきた引数はどんな値? モデルのidは? モデルに保存されている値は? 今回はこの2つの情報を通知する仕組みを作っていきます。不具合の再現方法を頑張って探すよりも、Bugsnagを見たら原因がすぐに分かるようにしたいですよね! 調査①  Bugsnagの公式ドキュメントを読む Bugsnagの公式ドキュメント( bugsnag DOCS - Rails integration guide )を確認すると、新しいタブを追加して、こちらで設定した情報を載せられるようです! ここに追加のデバッグ情報を載せられたら、良さそうです。 class ApplicationController < ActionController :: Base before_bugsnag_notify :add_diagnostics_to_bugsnag # Your controller code here private def add_diagnostics_to_bugsnag (report) report.add_tab( :diagnostics , { product : current_product.name }) end end 次にデバッグ情報をどのように取得するか検討していきましょう。 メソッドに渡ってきた引数はどんな値? モデルのidはいくつ? モデルに保存されている値は? 「うーん。。 どうしたら良いんだろう? 周りのサービスなどではどうしてるんだろう?」と思い、2つのgem(Better Errors / New Relic)のコードを読んでみました。 調査②  BetterErrors の gem を参考にする みんなお世話になっているであろう Railsのデバッグといえばこれですよね。BetterErrorsのコンソールに変数名を入力すれば、値を出力できるので、参考にできそう! Better Errors ※ BetterErrors より 画像を引用 ① BetterErrorsのコンソールは、binding.eval(str)を実行してるだけ module BetterErrors module REPL class Basic def initialize (binding, _exception) @binding = binding end def send_input (str) [execute(str), " >> " , "" ] end private def execute (str) " => #{ @binding .eval(str).inspect }\n" rescue Exception => e " !! #{ e.inspect rescue e.class.to_s rescue " Exception "}\n" end end end end ② そのBindingは、Exceptionを拡張し例外クラスに保持している module BetterErrors module ExceptionExtension prepend_features Exception def set_backtrace (*) if caller_locations.none? { |loc| loc.path == __FILE__ } @__better_errors_bindings_stack = :: Kernel .binding.callers.drop( 1 ) end super end def __better_errors_bindings_stack @__better_errors_bindings_stack || [] end end end なるほどなるほど。例外元のBindingを取得することができれば、引数やModelの情報が取れそうですね! 調査③  New Relic の gem を参考にする 次に目をつけたのは、パフォーマンスの監視でいつもお世話になっているNew Relicです。DBの検索時間などの処理時間を細かくデータを取っているので、参考になりそう。gemのコードを読んでみると、ActiveRecordでよく使われているメソッド達(saveなど)に測定用の処理を埋め込んでいました。また、イベントをフックして、処理を埋め込んでいるみたいです。思った以上に泥臭いぞ。。。! New Relic ※ New Relic より 画像を引用 # encoding: utf-8 # This file is distributed under New Relic's license terms. # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details. require ' new_relic/agent/prepend_supportability ' module NewRelic module Agent module Instrumentation module ActiveRecordPrepend ACTIVE_RECORD = ' ActiveRecord ' .freeze module BaseExtensions def save (*args, &blk) :: NewRelic :: Agent .with_database_metric_name( self .class.name, nil , ACTIVE_RECORD ) do super end end def save! (*args, &blk) :: NewRelic :: Agent .with_database_metric_name( self .class.name, nil , ACTIVE_RECORD ) do super end end end # saveと同じように下記のメソッドの実装がされていた(略 # - touch # - update_all # - delete_all # - destroy_all # - calculate # - pluck end end end end なるほどなるほど。例外のイベントが取得できれば、なんとかなりそうですね! 調査④  TracePointで例外をフックする Rubyのイベントフックを探してみると、TracePointクラスを使うと例外発生時のイベントがフックできるみたいです! るりま - TracePointクラス TracePoint .new( :raise ) do |tp| # ブロック内は例外が発生した時に動く tp.raised_exception # Exceptionが取得できる end でも、TracePointって遅いのでは? 単純にTracePointを実行すると、約8倍遅くなる!! (Traceなし:45.1ms → Traceあり:370.2ms) trace = TracePoint .new {} 10 .times.map { Benchmark .realtime { 1000000 .times { 1 + 2 } } }.sum/ 10 # Traceなし => 0.04516370000201277 10 .times.map { Benchmark .realtime { trace.enable { 1000000 .times { 1 + 2 } } } }.sum/ 10 # Traceあり => 0.3702467000024626 例外だけをフックすれば、影響がない! イベントフックの対象を例外だけに限定すれば、通常の処理には影響がありません!! (Traceなし:42.9ms → Traceあり:42.5ms) trace_raise = TracePoint .new( :raise ) {} 10 .times.map { Benchmark .realtime { 1000000 .times { 1 + 2 } } }.sum/ 10 # Traceなし => 0.04297380000061821 10 .times.map { Benchmark .realtime { trace_raise.enable { 1000000 .times { 1 + 2 } } } }.sum/ 10 # Traceあり => 0.04251920000097016 これなら速度も問題なさそうですね! これで材料が揃いました!! 調査結果のまとめ Bugsnagに新しいタブを追加して、情報を追加できる 例外発生元のBindingが取得できれば、引数やModelのデータを取得できる TracePointで例外のイベントをフックして、例外発生元のBindingが取得できる Bugsnagにデバッグ情報を追加できました! 完成!! 調査結果を元に、Bugsnagの通知内容にデバッグ情報を増やしてみました。 対応した内容 BugsnagのページにModelAttributesタブを追加! メソッドに渡した引数の値が確認できるようになった! Modelで例外が発生した場合は、idや各カラムの値が見えるようになった! やったー! これで、どのModelで例外が起きたかは分かるけど、どのレコードか分からないっていうことが無くなります。引数に渡ってきたパラメータも分かるので、不具合の調査も捗りそうです! 最終的なコードはこちら! 説明のためにコードを簡略化し、解説コメントを追加してあります。 実際には、機密情報に関わる部分はBugsnagで通知しないなどの対応を行っています。 例外時に、例外発生箇所のbindingを保持する # 例外発生イベントをフックする TracePoint .trace( :raise ) do |trace_point| exception = trace_point.raised_exception # 例外の発生元の情報を取得するためBindingを保持する if trace_point.binding.respond_to?( :callers ) bindings = trace_point.binding.callers.drop( 1 ) else bindings = [trace_point.binding] end # exception.error_bindingsに、例外発生元のbindingを保持させる exception.define_singleton_method( :error_bindings ) do bindings end end Controllerで例外が発生した時に、Bugsnagを通知する class ApplicationController < ActionController :: Base rescue_from Exception , with : :notify_bugsnag # Controller内で例外が発生した場合に、Bugsnagを通知する def notify_bugsnag (exception) BugsnagReporter .notify(exception) raise exception end end 例外の発生箇所の引数や、ActiveRecordのモデル情報をBugsnagへ通知する module BugsnagReporter class << self def notify (exception) Bugsnag .notify(exception) do |report| # Bugsnagに新しいタブを追加する report.add_tab( :model_attributes , models : model_attributes(exception)) end skip_bugsnag(exception) # 二重で通知されないように通知をスキップする end def skip_bugsnag (exception) def exception. skip_bugsnag true end end def model_attributes (exception) # 例外に保持しておいたbindingを使う exception.error_bindings.map do |binding| instance = binding.eval( ' self ' ) debug = {} # ActiveRecord::Modelの中で例外が発生した場合は、カラムのデータを出力する debug.store( "#{ instance.class.name.downcase } _attributes " , instance.attributes) if instance.respond_to?( :attributes ) # 例外発生場所のローカル変数を出力 debug.store( :local_variables , local_valiables(binding)) # 例外発生場所のインスタンス変数を出力 debug.store( :instance_variables , instance_variables(instance)) [trace_path(binding), debug] end .compact.to_h end # 例外発生場所のローカル変数を取得 def local_valiables (binding) binding.local_variables.map { |name| [name, format(binding.local_variable_get(name))] }.to_h end # 例外発生場所のインスタンス変数を取得 def instance_variables (instance) instance.instance_variables.map { |name| [name, format(instance.instance_variable_get(name))] }.to_h end # データを読みやすいように加工 def format (value) return value.attributes if value.respond_to?( :attributes ) return value.to_sql if value.respond_to?( :to_sql ) value end # 例外発生場所のファイルパスを生成する def trace_path (binding) instance = binding.eval( ' self ' ) method_name = binding.eval( ' __method__ ' ) file = binding.eval( ' __FILE__ ' ) line = binding.eval( ' __LINE__ ' ) "#{ instance.class.name } # #{ method_name } - #{ file } : #{ line }" end end end おわりに Bugsnagを拡張して、不具合の調査を楽にする仕組みについて解説しました。この対応は、僕が不具合の原因を突き止めるまでに時間がかかってしまった失敗をもう一度起こさないようにするための改善の1つです。このように、 スタメンのエンジニアチームの行動指針 にもある「失敗に向き合う」が実践でき、自分自身、そしてサービスを改善&成長していけるエンジニアを募集しています! ぜひ、まずは エンジニア採用サイト を見てみてください! ブログを最後まで読んでいただいて、ありがとうございました!!
アバター
スタメン エンジニアの松谷( @uuushiro )です。 2019年6月12日(水)〜6月14日(金)に AWS の日本最大級のカンファレンス AWS Summit Tokyo が 幕張メッセ で開催されました。そのイベント2日目に実施される 「 AWS Startup Architecture Of The Year 2019」というコンテストのファイナリストとしてスタメンが選出されたこともあり、せっかくならと3日間フルで参加してきました。1日目から3日目までで、それぞれ印象に残った出来事をお伝えしたいと思います。 素晴らしい機会を頂いた1日目 幕張メッセ で開催ということもあり、さすがの規模と盛り上がりでした。 ただ、1日目はほとんど会場で過ごす時間がなかったのでセッションは聞けなかったのですが、 AWS 主催のMeeting of Mindsという招待制ディナーに参加してきました。 そのディナーでは、 Amazon WorkSpaces や AWS Glue のマネージャーの方々や、WEB業界で著名な企業のCTOやエンジニアの方々とざっくばらんにお話することができました。 AWS という世界最前線で活躍するエンジニアの方々の輝かしいキャリア話も、グローバルなチームならではのマネジメントにおける苦労話も全てが自分の想像を遥かに超えるスケールで、聞いているだけで興奮しました。詳細はこれ以上書きませんが、普通では手が届かないような学びをたくさん持ち帰ることができました。今回の Meeting of Minds は日本では第1回目とのことで、次回もあったら是非参加させていただけたら嬉しいです。 最後に全員でパシャリ。 ついにコンテスト当日の2日目 コンテスト当日です。 Startup Architecture Of The Year のファイナリストの特典として、Startup Central内でのソリューション展示ブースを出展できる権利を頂きました。弊社のCTO小林と、エンジニアの津田が応援に駆けつけてくれて、一緒にブースの設営をしました。ちなみに、このブースでアピールする アーキテクチャ ーのポスターは、弊社デザイナーの松本が手伝ってくれました! Startup Architecture Of The Year では下の写真のように、パネル展示された各 アーキテクチャ 図に投票用のIoTボタンが設置されており、プッシュが最も多かった アーキテクチャ がオーディエンス賞を獲得できるという企画がありました。 17時からの本番に先立って、展示会場で15分間ピッチの機会を頂いたので、 アーキテクチャ のアピールとスタメンエンジニアチームの紹介をさせていただきました。 スタメンのブースに立ち寄ってくださった方、オーディエンス賞のボタンを押してくださった方、15分ピッチを聞いてくださった方、ありがとうございました! そして、ついに本番です! 今回スタメンがエントリーした アーキテクチャ は TUNAG のETL基盤(データ処理基盤)でした。この アーキテクチャ ーで工夫したポイントは大きく2つです。 1つ目は、S3をデータレイクとして活用したことです。組織エンゲージメントという、確立した分析指標がまだない分野において、データレイクのような アジャイル な分析環境の構築は TUNAG のビジネス上重要な鍵となっていました。また、S3に保存することで、多様なフォーマットの安全な保存・高い可用性とスケーラビリティ・従量課金によるコスト削減・他 AWS サービスとの容易な連携などのメリットを獲得することができました。 2つ目は、マネージド・サービスとサーバーレスを組み合わせてETL基盤を構築したことです。全て従量課金制のサービスなので、増加するデータ量に合わせて最低限の料金でシステムを稼働させることができます。また、マネージド・サーバーレスのメリットを最大限に活用し、構築・運用コストを最小限に抑えながら大規模なデータに対応可能な基盤になりました。 アーキテクチャ の詳細については、 AWS Start Up ブログ を御覧ください! 結果としては、グランプリを受賞することができました! 今回評価していただいたETL基盤をフル活用し、データに基づいた組織のエンゲージメント分析をより一層進めていきたいと思います。また、この受賞をきっかけにスタメンの アーキテクチャ に興味を持ってくれるエンジニアの方が増えれば嬉しいなと思います。関係者の皆さま、応援してくださった皆さま、本当にありがとうございました。 今回のピッチで使用したスライドはこちらです。 https://speakerdeck.com/uuushiro/tunag-false-etlji-pan-aws-summit-startup-architecture-of-the-year-2019 グランプリの副賞として re:inventペアチケット を頂いたのですが、re:inventはずっと行きたいと思っていた海外カンファレンスの1つだったのでとても嬉しいです。ペアチケットということで、CTO小林と2人で行ってきます! re:inventに行かれる方、是非ラスベガスでお会いしましょう! やっと落ち着いてセッションを聞けた3日目 3日目は、ようやくじっくりセッションを聞くことができました。 とくに印象に残っているセッションは以下です。 * Amazon RDS におけるパフォーマンス最適化とパフォーマンス管理 * Kubernetes on AWS ( Amazon EKS実践入門) * サービスメッシュは本当に必要なのか、何を解決するのか * Amazon Pinpoint でユーザーを掴んで離すな TUNAGのシステムにおいて、直近に課題になっているパーソナライズされた通知基盤( AWS Pinpoint)や、コンテナ技術・マイクロサービスなどTUNAGの将来的な展望になっている技術の話が聞けて、より具体的にTUNAGの技術的ロードマップをイメージできた3日目でした。 まとめ 3日間とても充実した時間を過ごせました。特にスタートアップという括りで参加した分、Meeting of Minds や、 Startup Architecture Of The Year など本当にいい経験をすることができました。re:inventにも参加できるということで一層 AWS へのモチベーションが上がりました。もっともっと上手く AWS を使い倒し、より良い アーキテクチャ を追求していきたいと思います。 スタメンでは、エンジニアの成長支援を行う一環として、技術書の購入費や、カンファレンス参加費の補助の制度があります。今回の AWS Summitは出張扱いとして参加費・交通費・宿泊費を会社に負担してもらいました。 スタメンでは AWS など クラウド の進化の波に上手く乗りながら、一緒にTUNAGを良くしていくエンジニアを絶賛募集中です!!興味がある方は Wantedly よりご連絡ください。    
アバター
TL;DR (概要) ​ GitHub の Webhook を用いて、PullRequest が merge されたときに、 WordPress の テーマを自動更新する設定です。 この設定をすることで、デザイナーが WordPress テーマを修正した際の、本番への反映の手間を大幅に削減でき、LPO等のサイトの改善がぐんぐん進むようになります。 ​ 背景 ​ 広報や採用担当でWebサイトを更新しやすくするために WordPress で、サイトを構築することがあります。この場合、サイトのデザインは、 WordPress の テーマ を自作することで行います。 ​ スタメンでは、テーマは、社内の Webデザイナー が作り Github で管理していますが、 WordPress への 反映は、 WordPress のファイル管理の プラグイン である File Manager を用いいて、更新したファイルを手動でアップデートしていました。 この方法は、手間もかかるし、ミスも起きやすく、スマートではありません。 ​​ WordPress と Github との連携は、 WP Pusher のようなサービスで実現できますが、テーマを Github の Private repository で管理していることもあり、 それなりに費用がかかる こともあり、 Github Webhook を用いて自作してみることにしました。 ​ Github で PulLRequest が Merge されたときに、 WordPress に反映する手順 ​ Git pull するための設定 ​ まず、 WordPress が稼働しているサーバーにて、 Github リポジトリ から git pull して、 WordPress テーマを取得できるようにする必要があります。 ​ Wordpress が稼働するサーバーにて、 OpenSSH の 鍵をつくります。 ​ $ ssh-keygen -t rsa -f id_rsa_wordpress 生成された公開鍵を Github の Deploy Keys に設定します。 ​ ​ ​ 次に、 httpd サーバー の実行権限で、git pull できるようにします。 ​ これは、 Github Webhook で WordPress が動作する PHP 関数を呼び出しますが、 WordPress は、 apache ユーザーで動作しているため、 apache ユーザーで git pull できないと テーマを更新することができないからです。 ​ 今回は、以下の内容の /home/ apache /bin/git- ssh .sh をつくり、git pull 時に GIT_ SSH 環境変数 にて指定するようにしています。 ​ #!/bin/sh ssh -oStrictHostKeyChecking = no -oUserKnownHostsFile = /dev/null -i /home/ < user_name > /.ssh/id_rsa " $@ " ​ git- ssh .sh では、下記の傾向メッセージが表示されて、git pull が失敗しないように、StrictHostKeyChecking を off にするなどのオプションをつけています。 ​ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ​ ​ 以下のように、 wordpress theme が存在する git repository で、git pull できれば準備は完了です。 ​ $ sudo -u apache GIT_SSH =/home/apache/bin/git-ssh.sh git pull origin master From github.com: < theme repository > * branch master - > FETCH_HEAD Already up-to-date. ​ WordPress に action を追加 ​ ​ WordPress の wp_ ajax _nopriv を利用することで、 Github hook から任意の PHP 関数を実行することができます。 ​ wp_ajax_nopriv {$ REQUEST[‘action’]} | Hook | WordPress Developer Resources ​ 今回は、下記の関数を theme ディレクト リの functions. php に追加することで、 https://your-wordpress-site/wp-admin/admin-ajax.php?action=wp_ajax_nopriv_update_theme にアクセスすれば、git pull されるようにします。 ​ ​ /** * Add deploy hook endpoint */ add_action('wp_ajax_nopriv_update_theme', 'update_theme_from_github'); function update_theme_from_github() { $secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"; $posted = file_get_contents('php://input'); if ( empty ($posted)) { return; } $signature = 'sha1='.hash_hmac('sha1', $posted, $secret); if ($_SERVER['HTTP_X_HUB_SIGNATURE'] !== $signature) { header('HTTP/1.1 403 Forbidden'); error_log('Signatures didn\'t match... '); return; } $json = json_decode($posted); error_log(__FUNCTION__.": \n".var_export($json, true)); if ($json- > ref === 'refs/heads/master') { $git_root = dirname(__FILE__); exec("cd ${git_root} && GIT_SSH=/home/apache/bin/git-ssh.sh git pull origin master 2 > & 1", $out); error_log(join("\n", $out)); echo join("\n", $out); } else { error_log(__FUNCTION__.": nothing for ".$json- > ref); } } ​ このとき、 GitHub の Webhook の Secret 機能 を利用することで、 GitHub の Webhook からのアクセス時のみ git pull するようにしています。 デバッグ しやすいにように、 ​error_log で、webhook の payload を出力していますが、不要だったら消してください。 GitHub に Webhook を追加 ​ あとは、テーマの リポジトリ にて、Webhook を設定すれば、完成です。 ​ ​ ​ まとめ ​ これまで、更新の際は、ファイルを一つ一つアップロードしていたのが、 GitHub WebHook によって、git pull することで、PullRequest を Merge することで、 WordPress に反映されるようになりました。 ​ これによって、 WordPress の更新の手間が大幅に減ったため、LPO などの細かい改善がより進むようになりました。また、 GitHub Flow で WordPress テーマを更新するフローが確立したことで、個々の修正に対して、チームレビューが定着し、ノウハウの共有やミスの低減を達成することができました。 ​ ほんと、嬉しいことだらけです! ​ ただし、 httpd が動作する権限で、git pull をするため、 レンタルサーバ ーなどの社外含めた複数のアカウントが共存する環境では、セキュリティと Webhook による git pull は、共存できないかもしれません。その場合は、 WP Pusher などの プラグイン を入れましょう。 ​ ​ 参考にしたサイト ​ WordPress テーマを Git 管理して自動デプロイする - Qiita GitHubのWebhookをPHPで受け取る練習 - tanaka's Programming Memo GitHubのWebhookをPHPで自作する時に書く検証コード - Qiita WordPress Git deployments with WP Pusher
アバター
スタメンのデザイナーの ( @kiyoshifuwa )です。 先日 スタメンの登壇資料テンプレートを Keynote で作成し、全社員に展開を行いました。 作成の手順や気をつけたこと、展開後のフォローとして行ったことなどをご紹介します。 なぜ作ったか 最近、スタメン社員(主にエンジニア)が大きな セミ ナーやカンファレンスで登壇する機会が増えてきました。 これまでは登壇者が毎度0から作成していましたが、 トンマナの統一 会社としての統一感を持たせたい 安定したクオリティ 各々のデザインスキルに依存しないようにしたい 資料作成の 工数 削減 登壇者には資料よりも話す内容に注力してもらいたい 上記を達成するために、スタメン共通の登壇資料テンプレートを作成することにしました。 どのように作ったか どんなレイアウトのパターンが必要なのか把握したいので、洗い出しのために エンジニアが作った登壇資料のラフをもらい、全てのページに最適なデザインを適用させました。 その際、「これらの情報は並列の関係?」「箇条書きが適切か?最適なのはフローではないか?」など、詳細まで認識をすり合わせました。 実際に作ったスライドは こちら です。 そして、必ず使うものやよく使うものをテンプレートに落とし込みました。 できたものがこちら ▲テンプレートと使用例(一部抜粋) 用意したスタイルは以下の通りです。 表紙 自己紹介 アジェンダ 箇条書き 箇条書き(チェックリスト) 番号付きリスト フロー 画像+テキスト 画像+テキスト(説明付き) (補足) アジェンダ の数は3〜5つまで対応しています。 エンジニアは図やコードを見せることが多いので、縦や横に大きく使えるスタイルを用意しました。 スタメンのほとんどが Mac ユーザーなので、 Keynote ファイルのみ作成しました。 直近で必要な資料の要件に合わせ、4:3で作成しました。後々16:9も作成する予定です。 用意したデータの詳細 使用例とマスタースライドが入っている、「テンプレート.key」を用意しました。 登壇者は、以下の手順で登壇資料を作成します。 「テンプレート.key」を複製 ↓ 不要な使用例を削除 ↓ 使える使用例は書き換え またはマスタースライドから新規作成 また「カラーパレット. clr 」も用意しました。 これは登壇者のPCの「~/Library/Colors/」に追加します。 登壇資料作成時に色を変更したい場合は、用意したパレットから色を選んでもらいます。 気をつけたこと テンプレート使用のハードルを上げないよう、ルールを詳細まで固めすぎないように気をつけました。 「ガイドに沿って整列してね」とだけ伝えて、あとは自由に使ってもらっています。 資料作成後のデザ インチェ ックも必須ではなく、登壇者本人が必要とする場合のみ確認するようにしました。 展開後のフォローとアップデート テンプレートのデータを展開後、社内勉強会で登壇資料の作り方の詳細を説明しました。 デザ インチェ ックや Keynote の使い方説明は、要望があれば個別で行うようにしています。 また、現状のものは完成形でなく、随時アップデートをかけて改善し続けたいという旨を伝えました。 スタメン社員が「テンプレート.key」を使用して登壇資料を作った際は、改善点や新たに増やしたいマスタースライドなどをフィードバックしてもらっています。 実際にこのテンプレートを利用して、資料を作成したエンジニアからは下記のフィードバックをもらいました。 テンプレートをそのまま使って資料を作るだけで、デザイン的に整った資料になり楽だった。 デザインを意識する必要がないため、発表内容のブラッシュアップに集中できた。 意図したとおり、登壇者が発表内容に注力できたのは良かったので、改善点などももらってアップデートを続けていきます。 まとめ 以上、登壇資料テンプレートの作成から展開までに行ったことをご紹介しました。資料テンプレート作成をご検討の方の参考になれば幸いです。 株式会社スタメンでは一緒に働く仲間を募集しています。ぜひお気軽に話を聞きに来てください! Wantedly は こちら
アバター
スタメン エンジニアの松谷( @uuushiro )です。6/8に開催された 名古屋Ruby会議04 でRuby×AWS Lambdaの内容について発表してきました。発表時に口頭で補足していた内容も含めて今回記事にしました。こちらのスライドは図を多めに説明をしているので参考にしていただければ幸いです。 https://speakerdeck.com/uuushiro/ruby-x-aws-lambdade-sabaresufalsedao-ru-tunagfen-xi-ji-pan-falseshi-li-womotoni TL;DR スタメンが運営しているサービス「TUNAG」におけるデータ処理基盤にAWS Lambdaを導入することで、簡単に並列処理を実現しデータ処理時間を大幅に短縮することができ、サーバー運用コストから解放されました。今回はRuby × AWS Lambda × SAM を利用したアプリケーションの作成方法、自動テストについて共有します。 AWS Lambda活用の背景 スタメンが運営しているサービス、「TUNAG」は会社と社員・社員同士の
エンゲージメント(信頼関係)向上を
目的とした企業向けSNSです。TUNAGで発生したコミュニケーションなどのデータは、会社への関心や社員同士の関係性が反映されています。そのデータを分析することで、社内のエンゲージメント状況を定量的に測定することができます。このデータという根拠に基づいた社内活性化施策のPDCAを回すことができるというところが TUNAG の強みの1つです。そのため、データ処理基盤はこれからのTUNAGのビジネス上重要な鍵になっています。 しかし、エンゲージメントのような定性的なものを数値化するということはまだまだ不確実性が高いです。そのため頻繁に分析指標の見直しをすることがあるのですが、過去全期間分の指標の再集計ともなると既存のデータ処理基盤では8時間ほどの時間がかかってしまい、試行錯誤の回数に制限がありました。そこでAWS Lambdaのオートスケールを活用することで、複数のEC2インスタンスの管理をせずに並列処理を実現でき、集計時間を短縮することができました。また、スタメンではRubyに慣れているエンジニアが多いという理由から、AWS LambdaではRuby ランタイムを利用しました。この記事では、Ruby × AWS Lambda × SAM を利用したアプリケーションの作成及び自動テストの方法について共有します。 管理フレームワーク SAMを利用した開発 データ処理基盤のように一定の規模以上のシステムをサーバーレスで構築する場合、複数のAWS Lambda関数を管理する必要があり煩雑になりがちです。そしてRubyのコードをLambdaにデプロイする際にも、S3へファイルをアップロードしAWS Lambdaと紐付ける作業が必要で時間と手間がかかってしまいます。このような悩みを解決してくれるのが、AWS SAM(Serverless Application Model)です。 AWS SAM(Serverless Application Model) SAMは以下の特徴を持つ、サーバーレスアプリケーションを構築するためのフレームワークです。 * サーバーレスアプリケーションの管理フレームワーク * CloudFormationのサーバーレスリソース特化の拡張 * アプリケーションのビルド・パッケージング・デプロイコマンドを提供 * ローカルでAWS Lambdaの構築・テスト・デバッグが可能 このため、複数のLambda関数をCloudFormationのようにコードで管理することができ、またCLIでデプロイを簡単に実行できます。具体的なSAMの使い方を見ていきます。 SAMとは SAMの開発フロー 以下のように、 sam init --runtime ruby2.5 とすることでランタイムがRubyに指定されたアプリケーションのサンプルの雛形が作成されます。ここで生成された template.yml というファイルにはAWSのサーバーレスリソースが記述されており、以下の AWS::Serverless::Function はAWS Lambdaの関数を表しています。 以下のSAMコマンド package を実行すると、対象関数のフォルダ(CodeUriプロパティ)の .zip ファイルを作成しS3へアップロードし、さらにローカルの生成物への参照をアップロードしたS3のURLに置き換えた packaged.yamlというファイルが作成されます。 $ sam package --s3-bucket tunag-sam-temlate-store そして以下の deployコマンドを実行すると、テンプレートに記述されている通りのリソースがAWS上に構築されます。 $ sam deploy \ --template-file packaged.yaml \ --stack-name sample-functions \ --capabilities CAPABILITY_NAMED_IAM Gemの扱いについて RubyでAWS Lambdaのアプリケーションを作成する際にも、通常のRubyアプリケーション開発と同じように便利なGemで開発効率を上げたいです。下記のsam buildコマンドは、アプリケーション内の関数を繰り返し処理し、Gemfileをもとに依存関係を解決し、Lambdaにデプロイできる成果物を.aws-sam / buildフォルダーに書き込みます。 $ sam build Gemfileに応じて下記のコマンドが実行されるため、成果物のディレクトリ構成は以下のようになります。 $ bundle install --deployment --without development test AWS Lambdaでは、以下のように vendor/bundleがgemの探索ディレクトリ対象(GEM_PATH)であるため、sam build コマンドで生成された成果物をデプロイするだけでAWS LambdaからGemを利用することができます。 # GEM_PATH $LAMBDA_TASK_ROOT/vendor/bundle/ruby/2.5.0 /opt/ruby/gems/2.5.0 ネイティブ拡張があるGemの扱いについて ネイティブ拡張があるGemは、Lambdaの実行環境と同等環境で成果物を生成する必要があります。 例えば、nokogiri が依存している libxml2 と libxslt はLambdaのイメージに含まれているので、以下のコマンドでビルド可能です。 buildコマンドに --use-containerオプションをつけると、Lambdaの実行環境と同等環境のコンテナ内ででビルドを実行してくれます。 $ sam build --use-container それに対して、mysql2 が依存している mysql-devel はAWS Lambdaのイメージに含まれていないため、Lambdaイメージコンテナで必要なパッケージをインストールした上でbundle installをする必要があります。 $ docker run -v `pwd`:/var/task -it lambci/lambda:build-ruby2.5 \ /bin/bash -c "yum -y install mysql-devel; bundle install --path=ruby/gems;" このとき、必要な共有ライブラリ(libmysqlclient)は、 LD_LIBRARY_PATHに配置することでLambdaから読み込むことができます。 $ LD_LIBRARY_PATH: /var/task:/var/task/lib:/opt/lib AWS Lambda Layers 複数のAWS Lambdaの関数を組み合わせてシステムを構築していると、複数の関数の間で共通のロジックコードがでてきます。 AWS Lambdaでは、それぞれの関数に共通するコードをそれぞれの関数に対しアップロードする必要があります。また、ロジックを更新するたびに activesupport や mysql2 のGemのファイル群など、変更がないファイルをアップロードする必要があります。その結果、アップロードするファイルの容量が無駄に増え、容量の制限やデプロイ時間の遅延など問題が出てきます。そこで AWS Lambda Layers という機能が役に立ちます。AWS Lambda Layersは以下の特徴があります。 複数のAWS Lambdaでコードを共有できる仕組み Lambdaを呼び出すとLayersがコンテナの/opt配下にマウントされる sam build は現時点で非対応 AWS Lambda Layers は、共有コードを管理したり、コードの変更頻度が少ないライブラリやGemを配布したりする場合に特に便利です。Layersのパッケージを複数のLambda関数に添付して使用することができるため、AWS Lambda関数のビジネスロジックが単純化され、依存関係管理が容易になり、デプロイパッケージのサイズを小さくできます。 また、AWS Lambdaのデプロイパッケージの最大サイズは50 MBですが、最大5つのLayersをアタッチすることで、250MBまで上限を増やすことができます。また、Gemや共有コードなどの依存関係とビジネスロジックの間で、関心事の分離を強制できることがLayersを利用することの副次的なメリットでもあると思います。 AWS Lambda Layers 参考URL 共通コード用をLayersで扱う方法は、以下のように template.yamlに AWS::Serverless::LayerVersionというリソースを定義し、AWS::Serverless::FunctionのLayersプロパティから参照するだけです。 LayersにおけるRubyライブラリの探索パス(RUBY_LIB)は ruby/lib なので、以下のような構成でコードを配置すれば、LambdaからLayersを通して読み込むことができます。 LayersにおけるGemの探索パス(GEM_PATH)は ruby/gems/2.5.0 なので、以下のような構成でGemを配置すれば、LambdaからLayersを通して読み込むことができます。 img2lambdaを使ったLayersのデプロイ(おまけ) Layersを作成する際に、依存関係の管理などが多少煩雑になっていました。この管理をもう少し簡単にする方法は無いかと色々探していた中で、AWSが提供している img2lambda というライブラリがあったので紹介します。 これは、DockerイメージをLambdaやLayersに変換しデプロイできるツールで、これを利用することで依存関係をイメージに閉じ込め
変更があれば再ビルドという形で簡単に管理できるようになります。とても便利なので使ってみたかったのですが、後述するSAMのAWS Lambda Layersのローカル実行機能が利用できなくなってしまうので、今回は採用を見送りました。 img2lambda 自動テスト AWS Lambdaのコードについても普通のアプリケーションと同様に自動テストを書いていきます。ただ、AWS Lambdaという環境の特性を考慮し、以下のような方針で自動テストを行うことにしました。 クラウド上での検証はデプロイなどを含め時間が掛かり過ぎるのでなるべく ローカルでテスト したい AWS Lambdaは他のAWSサービスとの連携が多く統合テストが重要になる IAM権限などの、クラウドでしか検証できないものに限りクラウド上でテストを行う 今回はAWS Lambdaをローカルでテストをする方法についてフォーカスします。AWS Lambdaのローカルテストにおいて、以下3点のポイントを考慮しテストを作成しました。 AWS Lambda特有の依存を排除し単体テストを行いやすいコードにする AWSサービスに依存するロジックはスタブを利用する ローカルにLambdaのエンドポイントを起動し、AWS Lambda・Layersの組み合わせでテストする 実際に、TUNAGのデータ処理基盤で想定されるテストケースとして、Athenaへのクエリが失敗した場合を考えてみます。テストの手順として、以下の1~3をコードと共に示します。 ① Athenaへのクエリ実行APIを叩く ② クエリは非同期的に処理されるため
実行IDをもとに実行状況を問い合わせる ③ クエリの結果を取得し状態が
 “FAILED”なら例外を投げる まずここで1つ目のポイント、 AWS Lambda特有の依存を排除し単体テストを行いやすくする を考慮し、以下のようにLambdaに依存するハンドラーの引数などの箇所からロジックを切り離します。 そうすることで以下のように、Lambdaに依存しないコードになり、通常のRubyのクラスとしてテストが可能になりました。 続いて、Athenaのような他AWSサービスに依存するロジックをどのようにテストをすればいいでしょうか。ここで、2つめのポイントの AWSサービスに依存するロジックはスタブを利用 を実践します。AWSが提供する、AWS SDK for Ruby には 便利なスタブ機能があり、APIアクセスのレスポンス・エラーを簡単にスタブすることができ、AWS APIクライアントを実行するように振る舞います。 クライアントをスタブする方法は、以下のように、クライアントオブジェクト作成時に stub_responsesパラメータにtrueを設定します。そして、stub_dateメソッドでスタブデータを作成し、stub_responsesメソッドの引数に設定することで、特定のAPI呼び出しメソッドのレスポンスにスタブデータを設定することができます。 ここで作成したスタブオブジェクトを引数でテスト対象に渡すことで、ローカルでAWSサービス依存のテストができるようになりました。 AWS Lambdaのテストについては @t_wada さんの資料 Testable Lambda がとても参考になったので、興味のある方は是非見てみてください。この資料の中で利用されている LocalStackという、ローカルにAWSサービスと同等に振る舞うエンドポイントを提供してくれるサービスを利用してテストをしても良かったのですが、今回データ処理基盤に必要なAWS Athena・Glueは
非サポートだったため、AWS SDK for Ruby のスタブ機能を利用しました。 そして3つ目のポイント、 ローカルにLambdaのエンドポイントを起動し、AWS Lambda・Layersの組み合わせでテスト をします。ローカルでAWS Lambda・Layers を組み合わせてテストをする方法として、SAMにはローカルでLambdaのエンドポイントを起動するコマンドが用意されています。 $ sam local start-lambda 上記のコマンドを実行し、以下のようにエンドポイントをローカルに向け、テスト対象のLambdaをinvokeすることで、Layersを含めたAWS Lambdaのテストがローカルで実行可能になります。 まとめ 以上、Ruby × AWS Lambda × SAMにおけるアプリケーション作成・自動テストについて説明しました。通常のRubyアプリケーション開発と比べると考慮するべき事項が多く、決して簡単ではありませんでした。しかし、今回のデータ処理基盤において、AWS Lambdaの「オートスケール」、「従量課金性によるコストパフォーマンスの向上」、「サーバー運用コストからの解放」というメリットは、そのようなコストを優に上回ったのではないかと考えています。 また今回紹介した開発・自動テスト方法がベストだとは思いませんので例えば、SAMではなくServerless Frameworkならもっと簡単にできるよ、このツール便利だよ、とかありましたら是非フィードバックいただけると嬉しいです。 スタメンでは、Railsアプリケーションの開発及び、基盤システムをAWSで構築するエンジニアを大募集中です!興味をもたれた方は是非こちらの エンジニア採用サイト から気楽にご連絡ください。
アバター