TECH PLAY

株式会社スタメン

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

231

スタメン エンジニアの津田です。スタメンで運営しているサービス、「TUNAG」では、毎日、データベースの"その日の状態"を別々のデータベースとして残していました。こちらの運用を、 AWS のS3、Glue、Athenaを利用して置き換えたのですが、その中で利用した、 MySQL 互換Auroraから、S3上へのデータ抽出用 スクリプト の紹介をいたします。 TL;DR (概要) TUNAGでは、データベースとして、 Amazon Aurora ( MySQL 互換)を使用しています。サービス利用状況の分析などで、過去の特定時点でのデータベースが必要となるため、いままでは、日次でデータベースのコピーを作成し、一つのRDS インスタンス 内に追加していました。 この形式では、全てを使い慣れたデータベース上で完結できるため、機能の構築は素早く行えたのですが、データ総量が日々増え、処理時間が次第に増大していました。なんらかの原因で処理が失敗した場合の再試行にかかる時間も含めると許容できない長さになりつつあったため、以下のように置き換えています。 Auroraのクローン機能 を利用して特定時点でのDBクローンを作成した上で、 AWS Glueジョブで各テーブルの内容を parquet 形式でS3上に抽出し、データの利用は Athena 経由としています。 今回紹介するのは、図の黄色い部分で使用したGlueジョブの スクリプト です。 Glueを利用してS3にファイルを作成する AWS Glue は抽出、変換、ロード (ETL)を行うマネージド型のサービスです。今回は、Auroraのテーブルをそのままの形でファイルとして出力するだけなので、 AWS Consoleの「Add job」ボタンから、コードを一行も書かずに処理を作成することも可能です。 ただ、その場合、 まず、出力元のデータベースに対し クローラー を設定しデータカタログを作成 テーブル数だけGlueのジョブを作成 今後、テーブルの追加があった場合、手動で個別にジョブを追加 といった手順が必要となります。元になるデータベースへのテーブル追加はそれなりの頻度で発生するため、そのたびにジョブを作成するのは嬉しくありません。そこで、生成された スクリプト をベースに、 mysql のinformation_schemaを参照して、データベース内の全てのテーブルを調べて抽出するよう変更しました。 import sys import datetime from awsglue.transforms import * from awsglue.utils import getResolvedOptions from pyspark.context import SparkContext from awsglue.context import GlueContext from awsglue.job import Job # ジョブのパラメーターを取得 arg_keys = [ 'JOB_NAME' , 'jdbc_url' , 'db_user' , 'db_password' , 'bucket_root' , 'db_schema' ] args = getResolvedOptions(sys.argv, arg_keys) (job_name, jdbc_url, db_user, db_password, bucket_root, db_schema) = [args[k] for k in arg_keys] # Athenaで利用する際、パーティション分割をするため、年月日を取得しておく now = datetime.datetime.now() date = [now.year, now.month, now.day] sc = SparkContext() glueContext = GlueContext(sc) spark = glueContext.spark_session # 全テーブルを対象とするため、まず、information_schemaのテーブル情報を取得 df = glueContext.create_dynamic_frame_from_options( 'mysql' , connection_options={ "url" : jdbc_url + "information_schema" , "user" : db_user, "password" : db_password, "dbtable" : "tables" } ) # 抽出する必要がないテーブルはfilterで弾いておく。ar_internal_metadataはRailsが自動生成するテーブル target_tables = df.toDF().filter( "TABLE_SCHEMA = '{0}' AND TABLE_NAME <> 'ar_internal_metadata'" .format(db_schema)).collect() # 1テーブルずつ抽出 for row in target_tables: table_name = row[ 'TABLE_NAME' ] ds = glueContext.create_dynamic_frame_from_options( 'mysql' , connection_options={ "url" : jdbc_url + "dbname" , "user" : db_user, "password" : db_password, "dbtable" : table_name }) # S3上にdynamic frameを書き出す。パスには、パーティションを付加しておく glueContext.write_dynamic_frame.from_options( frame=ds, connection_type= "s3" , connection_options={ "path" : bucket_root + table_name + "/YEAR={0[0]:0>4}/MONTH={0[1]:0>2}/DAY={0[2]:0>2}" .format(date)}, format = "parquet" ) 実行数を調整する Glueのジョブには、Maximum capacityという項目があり、スケールアウトするために使用できるデータ処理単位(DPU)を指定できます。これはデフォルトでは10に設定されているのですが、 DPU の容量計画のモニタリング を参考にメトリクスを確認したところ、先述の スクリプト の書き方ではDPUを使い切れていなかったので、適当と思われる数字に減らして設定し直しました。 1 つの DPU はアプリケーションマスター用に予約されています。9 個の DPU はそれぞれ 2 つのエグゼキュターを実行し、1 つのエグゼキュターは Spark ドライバー用に予約されています。そのため、割り当てられるエグゼキュターの最大数は、2*9 - 1 = 17 です。 DPU の容量計画のモニタリング に以上のように書かれているので、エグゼキュターの数は、DPU x 2 - 3になると思われます。つまり、DPU2だと利用できるエグゼキュータ数は1ですが、DPU3だと3で、三倍になります。もし、エグゼキューター時間3で終わる処理があるとすると、DPU2では3/1で3時間、DPU3なら3/3で1時間で終わります。Glueの課金はDPU時間あたりなので、DPU3(3 x 1時間 = 3)であれば、DPU2(2 x 3時間 = 6)の半額で済む計算です。Maximum capacityは、必要とされるエグゼキュターの数を上回らない範囲で大きな値を設定すると、処理が早く、かつ、費用が抑えられることになると思われます。 まとめ Glueを利用してデータを抽出する部分を紹介させていただきましたが、生成されたファイル利用してをAthena経由で集計、結果を分析用データベースにロードするなど、後続の処理は AWS Lambdaで Ruby スクリプト を使って実装しています。こちらについては、 @uuushiro が 名古屋Ruby会議04 で発表しますので、よろしければお越しください。 また、 AWS Summit Tokyo内で行われる、 Startup Architecture of the Year 2019 にも出場させていただきますので、よろしくお願いします。
アバター
株式会社スタメンでWebフロントエンド・サーバーサイドの開発をしています、河井と申します。 本記事では react-chartjs-2 の使い方についての解説をします。React でグラフを書くことになったけど始め方が分からないなどといったときに参考にしていただければ幸いです。 TL;DR (概要) グラフ描画のためのライブラリである Chart.js を React の コンポーネント として利用できるようになるライブラリ、react-chartjs-2 の解説記事です。グラフに表示するデータを インタラクティブ に切り替えたり、図を綺麗に見せるために Plugin を用いた実装を解説します。 グラフの描画 前提として、紹介するライブラリは既にインストールされているものとします。 また、簡単のため コンポーネント は分割せずに1つの コンポーネント 内で完結するようにしています。 まずは基本形です。Line や Bar といった コンポーネント に対し、data と options(必要であれば) を渡します。 import React, { Component } from 'react' ; import { Line } from 'react-chartjs-2' ; class SampleChart extends Component { render { const data = { labels: [ 'April' , 'May' , 'June' , 'July' , 'August' , 'September' ] , datasets: [ { data: [ 67, 79, 52, 41, 66, 43 ] , // 省略 } , ] , } ; const options = { // 省略 } ; return ( <Line data= { data } options= { options } /> ); } } 以下のようなグラフが表示されます。 使用できる コンポーネント の種類は react-chartjs-2 のレポジトリ を、 data と options の形式については、 本家 chart.js のドキュメント を参照してください。 データの取得とグラフの更新 data や labels を動的に取得したい場面のほうが多いかと思うので、それらを state として管理します。 実際の利用シーンとして、とあるグラフが日付を起点としてデータを取得しているとき、その起点となる日付を指定してグラフを更新したい、といった状況を想定してみます。 まず、ページの表示時には componentDidMount() 内でデータを取得します。 何らかの方法で日付を更新したとして、その日付が state であれば コンポーネント が再描画されます。その直後に呼び出されるライフサイクルメソッドとして componentDidUpdate() があり、ここでその日付に応じてデータを再取得します。 以下がその実装例です。DatePicker は、カレンダーから日付を選択するような コンポーネント を想定しています。 import React, { Component, Fragment } from 'react' ; import moment from 'moment' ; class SampleChart extends Component { constructor(props) { super (props) this .state = { date: moment() } ; } componentDidMount() { this .getData(); } componentDidUpdate(prevProps, prevState) { if (prevState.date !== this .state.date) { this .getData(); } } getData() { const { data, labels } = fetchData( this .state.date); //日付に基づいた何らかのデータ取得処理 this .setState( { data: data, labels: labels } ); } render { // 省略 各種変数の定義等 return ( <Fragment> <DatePicker date= { date } onDateChange= { (newDate) => { return this .setState( { date: newDate } ); }} /> //何らかの日付変更処理 <Line data= { chartData } options= { chartOptions } /> </Fragment> ); } } if (prevState.date !== this.state.date) { ... } の部分で更新前の日付と更新後の日付を比較していて、異なっていればデータが再取得されます。 クリックイベントの追加 グラフ要素をクリックして、クリックされたラベル(日付など)に応じて処理をしたいときのために、クリックイベントを追加することができます。 以下は、クリックしたグラフ要素に対応するラベルのインデックスをコンソールに出力するサンプルです。インデックスは elements[0]._index で取得することができます。 import React, { Component } from 'react' ; import { Bar } from 'react-chartjs-2' ; class SampleChart extends Component { handleClick(elements) { if (elements.length === 0) return ; console.log(elements [ 0 ] ._index); } render { return ( <Bar data= { data } options= { options } onElementsClick= { (elements) => this .handleClick(elements) } /> ); } } 拡張機能 の利用 Chart.js はそれだけでもなかなか 表現の自由 度が高いものですが、さらにその幅を広げるための 拡張機能 (extensions)が用意されています。 extensions を用いると追加のグラフパターンが使えるようになったり、より凝った表示オプションを設定できるようになります。今回はその中の chartjs-plugin-datalabels を紹介します。 chartjs-plugin-datalabels はその名の通りラベルに関する拡張で、どのような種類のグラフに対しても自由な位置にラベルを表示できるようになります。 数値差が大きくなると小さい方の数値が把握しにくくなるので、パッと見てわかるようにしたいです。この プラグイン を使って棒グラフの終端に数字を表示してみたいと思います。 ラベルの表示に関するパラメータはいろいろありますが、今回の実装のためには anchor 、 align が関係してきます。 具体的には、anchor でグラフの先頭、中心、終端のどこにラベルを置くかの基準点を指定し、align で anchor で指定した基準点からどの方向にラベルを設置するかを指定します。 これらを組み合わせて、 anchor: 'end', align: 'right' とすることで、終端の右側に表示します。 実装は以下のようになります。 import 'chartjs-plugin-datalabels' ; const chartOptions = { plugins: { datalabels: { display: true , anchor: 'end' , align: 'right' , formatter(value) { if (value === null || value === 0) { return '' ; } return `$ { value } %` } , } , layout: { padding: { right: 50, } , } , // 省略 } ; ラベルを表示する余白をが必要がほしかったので右側に padding を入れています。 また、表示する値に単位を付ける、3桁ごとにカンマで区切るなどの表示形式は、 formatter の中で記述することができます。 今回は横向き棒グラフを使いたいので、 HorizontalBar コンポーネント を使います。 render ( <HorizontalBar data= { chartData } options= { chartOptions } /> ) これを実際に表示すると次のようになります。 まとめ 本記事では、react-chartjs-2 の使い方を、基礎から応用まで紹介しました。基本的な機能がデフォルトで揃っていながらも、非常に柔軟なライブラリでもあるので、グラフを実装する際はぜひカスタマイズして使ってみてください。 また、株式会社スタメンでは一緒に働く仲間を募集しています。お気軽にお話を聞きに来ていただければと思っています! 名古屋から世界へ!HRtechサービスの自社開発エンジニアと話しませんか? 参考 Chart.js react-chartjs-2 Github Chart.js popular extensions
アバター
こんにちは!スタメンで TUNAG の iOS / Android アプリ開発 を担当している @temoki です。 CTOの小林が スタメンのエンジニアが作っている『TUNAG』の技術的な解説 で TUNAG 全体のテク ノロ ジー スタックについてお話していますが、今回は iOS アプリにフォーカスを当ててお伝えしようと思います。 言語 TUNAG の iOS アプリはすべて Swift で書かれています。Swift のバージョンアップにもすぐに対応しているため、現時点で最新の Swift 5 を使用しています。Swift は Optional 型により Null Safety を実現しており、TUNAG iOS アプリでは今のところ Null Pointer Exception によるクラッシュレポートは届いたことがありません(素晴らしい)。 Swift は初期のバージョン1〜3あたりまではバグやバージョンアップごとに破壊的に仕様が変わっていくのに苦労しましたが、バージョン4以降は仕様も安定し、安心して利用できるようになりましたね。 サービス TUNAG は AWS + Ruby on Rails で構築されていますが、mBaas (mobile backend as a service) の Firebase も利用しています。特に今年の2月に正式リリースされたばかりの Cloud Firestore のおかげで、非常に即時性の高いチャット機能を実現することができました。また、Cloud Firestore はオフライン利用のために取得済みのデータを自動的にローカルストレージにキャッシュしてくれますし、データがどこかを意識することなくアクセスできる点もとても便利です。 Cloud Firestore だけでなく、TUNAG と連携したユーザー認証のために Authentication 、クラッシュレポーティングのために Crashlyitics など、Firebase を広く活用しています。 また、アプリへのプッシュ通知配信には NIFTY CLOUD mobile backend を現在利用していますが、これも Firebase の Cloud Messaging への移行を検討中です。 (2020/2/5 追記) アプリへのプッシュ通知配信は、2019年9月に Firebase の Cloud Messaging に移行し、配信速度が格段に向上しました。 ライブラリ iOS アプリでは、HTTPネットワーキングは Almofire を中心とし、画像の読み込みに AlamofireImage 、Web API クライアントの実装のために Almofire のラッパーである Moya を使用しています。アプリからは様々な Web API を使用しますが、Moya はその API 仕様に沿って宣言的に実装していけるところがお気に入りです。 また、先日 TUNAG iOSアプリのチャット機能をVIPERアーキテクチャで開発した話 にも書きましたが、データレイヤーで Cloud Firestore から受け取るリアルタイムアップデートに対してリアクティブに処理するために Reactive X の Swift 実装である RxSwift を使用しています。 その他には Swiftコード の静的解析の SwiftLint 、画像やStoryboardのようなリソースをタイプセーフに扱うために SwiftGen などの開発支援のためのツールも活用し、コード品質を確保しています。 これらのライブラリは基本的に Carthage でインストールすることで開発中のビルド時間の短縮していますが、Carthage に非対応のライブラリのみ Cocoapods を利用しています。 ベータテスト TUNAG はスタメンのメンバー自身がヘビーユーザーです。そのためアプリの新バージョンは常に社内メンバーでのテストを実施しています。 開発中のステージング環境で動作するアプリは、 Fabric の Beta というサービスにより開発メンバーの iOS デ バイス にデプロイしています。この Fabric は Twitter 社が提供していたモバイルアプリの開発支援サービスですが、 Google 社に売却されて現在 Firebase への統合が進められている状態 です。Crashlytics も Fabric が提供するサービスの一つでしたが、Firebase への統合もほぼ完了しています。Beta も Firebase App Distribution として統合される予定です。そのため Beta による ベータテスト 中にアプリがクラッシュした場合も、Crashlytics によりちゃんとレポートされるので助かります。 (2020/2/5 追記) Firebase App Distribution がベータリリースされたため、2019年12月に Fabric Beta からの移行しました。 新バージョンの開発の終盤では、開発メンバーだけではなく社内のたくさんのメンバーにて普段利用してフィードバックしてもらうために、プロダクション環境で動作するアプリを Apple 公式の Test Flight で社内メンバーに配信しています。 そしてテストやリリースのためのアプリのビルドには Fastlane を使用しています。Fastlane には Beta や Testflight へのアプリのアップロードや、クラッシュ解析用のシンボルファイルのアップロードなどの機能が揃っているため、ビルドからデプロイまでの一連の流れをコードで定義して自動実行できてしまいます。 さいごに いかがでしたでしょうか。TUNAG の iOS アプリの開発はこのようなモダンな環境で行なっています。これは(実は)この半年間くらいで整備してきたもので、今後は Android アプリの方も徹底改善していく予定です。 このようなモダンな環境での iOS アプリ開発 や、今後の Android アプリ開発 環境がどのように改善されていくのかに興味を持たれた方は、 こちら から気軽にご連絡ください!
アバター
概要 こんにちは、スタメンの小林( @lifework_tech )です。 スタメンの名古屋オフィスは、 東海道新幹線 の高架下にある倉庫のような広い空間に、 ものすごく大きな机 をみんなでシェアして使っています。 机が広くて、横幅と奥行きがあるため、エンジニアやデザイナーが在籍するプロダクト部のみんなはそれぞれの好みの環境にして快適に仕事をしています。 今回、何人かに、こだわりの環境についてインタビューしましたので、ご紹介させてください。 標準的な机 まずは、スタメン標準環境。 スタメンのエンジニア or デザイナーは、 MacBook Pro 15インチ + LG 27インチ 4Kディスプレイ + Apple Magic Keybord もしくは、Magic Trackpad という環境が標準です。キーボードやマウスは持ち込んでも良く、あとで紹介しているように自分でカスタマイズしている人もいます。 キーボード師匠 (ネイティブチームのエンジニア) ネイティブエンジニアのKさんのデスクです。 Kさんによって、デュアルキーボードの良さが布教されたため、勝手にキーボード師匠と思っています。 タイピングによる肩甲骨の痛みを解消するために、 日本語配列 の貴重な分割キーボード MiSTEL BAROCCO MD600 を使用しています。日本語印字がなく、オレンジのアクセントカラーがお洒落です。 このキーボードはそもそも Mac 用に最適化されているわけではなく、よく使用するカーソルキーや英数・かなキーがないため、キーボード本体のカスタマイズ機能と Karabiner Elements を駆使して Mac の 日本語配列 に極力近づけています。 パームレスト は高さがぴったりなので FILCO の分割キーボード用のもの をチョイス。 普段は Macbook っぽく真ん中に配置した Magic Trackpad で操作、資料作成など必要に応じて右側の Magic Mouse を使います。 CTOの小林 ドラえもん のような便利な道具をだせるエンジニアでありたいと思っていて、机にはいつも ドラえもん がいます。 この ドラえもん は、小芥子( こけし ) なんです。社員旅行で行った 馬籠宿 の民芸店で購入しました。 マウスを真ん中に置くと、手を伸ばさなくても操作できるので、右腕の疲れ具合が減るんです。 腕を前に伸ばすと、腕を支えるために筋力を使うので疲れやすく、体の近くにマウスを置くと疲れにくいと、ジムのトレーナーにアド バイス 頂いてからこうしています。 肩を開いた方が疲れにくいとのキーボード師匠の薦めで、Magic Keybord を2台を Karabiner-Elements で連動させています。 マウスパッドは、steeleseries の ゲーミングマウスパッドで、かなり表面が固くてマウスがよく滑るため、長年使っています。 打ち合わせ等で席を離れることも多く、ケーブルの抜き差しが楽になるように、 Belkin Thunderbolt 3 Express Dock を使っています。 デザイナー Mさん マウスをたくさん動かせるよう、広いマウスパッドをおいています。 時々 iPad Proや、ラフを書くためのノート、印刷物を置くので なるべくスッキリさせておきます。 SREチームのエンジニア Tさん Ubuntu を使用していることもあって、HHKB JP配列に親指 トラックボール という標準的な構成です 手前の リストレスト は肘が痛くならないようにおいています トラックボール を Logicool M570 から ELECOM EX-G に替えた以外は、7年ほどこの構成です ボールはM570の方が好みなので、ボールのみM570のに入れ替えて使っています 余ったボールは娘(3歳)のおもちゃとして活躍しています まとめ エンジニアにとって、開発環境の快適さは非常に大切で、特に一番触れることの多いキーボードやその配置は皆さんこだわりがありますよね。 ご紹介した以外にも、 ThinkPad トラックポイント・キーボード を使っている人がいたり、それぞれ快適な環境を整えています。 スタメン名古屋オフィスは、まさに ベンチャー という感じの 新幹線の高架下にあるガレージのようなオフィス で、各自の机以外にも、 ペアプロ や勉強会にも使えるながーいダイニングテーブル(下図)があったりと、働きやすい工夫が詰まったオフィスです。 どんなオフィスか見てみたいなと思ってくださったら、ぜひ @lifework_tech へお気軽にご連絡ください。オフィスをご案内します!
アバター
はじめに こんにちは、スタメンで iOS/Android アプリのエンジニアをしている @temoki です。 昨年の10月にスタメンにジョインしてからの私の最初のミッションは、 TUNAG iOS アプリのチャット機能の開発プロジェクトでした。本記事ではこのチャット機能開発プロジェクトにおいて採用した VIPER というアーキテクチャについて紹介し、チャット機能の初版リリースからいくつかの機能改善を経た現在における所感をお伝えしたいと思います。 VIPERの概要 VIPERとは? 私が VIPER を知ったのは objc.io の Architecting iOS Apps with VIPER という記事です。これを読めば VIPER について一通り理解することができますが、端的に説明するために次の文を引用します。 VIPER is an application of Clean Architecture to iOS apps. The word VIPER is a backronym for View, Interactor, Presenter, Entity, and Routing. Clean Architecture divides an app’s logical structure into distinct layers of responsibility. This makes it easier to isolate dependencies (e.g. your database) and to test the interactions at the boundaries between layers: つまり次のとおりです(日本語に翻訳しただけですが)。 VIPER は iOS アプリケーションに Clean Architecture を適用したもの VIPERという言葉は、 V iew、 I nteractor、 P resenter、 E ntitiy および R outing を表す バクロニム アプリケーションの論理構造を責任の異なるレイヤーに分割することで(データベース等の)依存関係を分離し、レイヤー間の境界で相互作用をテストすることが簡単になる VIPERのメインパーツ VIPER は次の5つのパーツに分類され、それぞれが明確な責務を持ちます。 V iew Presenter からの指示を表示する ユーザー入力を Presenter に伝える I nteractor アプリケーションの中の一つのユースケース(画面単位など)を表現する データ操作とビジネスロジックを含む P resenter Interactor から受けとったデータを表示するための準備を行う ユーザー入力に反応して Interactor に新しいデータを要求する このようなビューロジックを含むが、iOS の UIKit には依存しない E ntity Interactor のみに使用される単純なモデルオブジェクト R outer 画面がどのように表示されるかの画面遷移ロジック その表示する画面の生成(依存性注入) これは Passive View 方式の MVP (Model View Presenter) アーキテクチャがベースになっており、大きくなりがちな Presenter から画面遷移と生成の責務を Router に、そして画面固有のユースケースを Interactor に分離しているのが特徴的です。 それぞれのパーツのつながりは下図のようになります( objc.io の記事からの引用)。 TUNAG アプリへの適用 ここからは TUNAG iOS アプリへの VIPER 適用についてお話していきます。 なぜ VIPER を採用したのか? TUNAG のチャットは複数の画面で構成されますが、メインとなるチャットルーム画面のスクリーンショットがこちらです。 メンション、絵文字、スタンプ、写真投稿、ファイル添付などたくさんの機能があります。TUNAG のチャットは簡易的なものではなく、メンバー間の日常コミュニケーションやビジネス用途としても利用されるものですので、この1つの画面だけでもとても多くの表示パターンと入力パターンがあります。そして TUNAG という事業は長く継続していくものですので、今後の変更などのメンテナンスのことを考えると最初から適切にモジュールを分割していく必要があります。 VIPER は特定のプラットフォームやライブラリに依存しているものではなく、オブジェクト指向でアプリケーションを適切に設計していくためのガイドラインのようなものです。そのため、開発チーム内でモジュール分割の方針を共通認識として開発を進めていけると感じたため、VIPER を採用することにしました。 また、開発チームには開発経験の少ない若手メンバーもおり、VIPER での構築を実践していくことでメンバーの設計力向上につながるのではないかという期待もありました。 TUNAG アプリの VIPERによる構成 最終的に、TUNAG アプリの構成は下図のようになりました。各パーツで <~> と記述しているものは、そのパーツが準拠する Protocol です。それぞれのパーツは、その Protocol により接続されることになります。 View, Presenter, Interactor, Router は基本的に画面単位に用意します。画面を開発するときは、その画面における各パーツの役割を Protocol で定義し、それらを Contract (契約) としてまとめたファイルを作るようにしました。こうすることでこの画面の機能がまとまったカタログができます。以下が、タスクリスト画面を作るとした時の Contract の例となります。そして、Router がそれぞれの依存性を注入し、まとめて一つの画面モジュールとして組み立てます。 /// タスクリスト画面 View protocol TaskListView : class { // Dependency var presenter : TaskListPresentation! { get } /// タスクリストの読み込み中の表示をする func showLoadingState () /// タスクリストを表示する func showTaskList (_ taskList : [ TaskCellModel ] ) /// タスクリストが一つもない状態を表示する func showEmptyState () } /// タスクリスト画面 Presentation protocol TaskListPresentation : Presentation { // Dependency var view : TaskListView? { get } var interactor : TaskListUseCase! { get } var router : TaskListRouter! { get } /// タスクセルがタップされた func taskCellDidTap (taskId : Int ) /// タスク追加ボタンがタップされた func addTaskButtonDidTap () } /// タスクリスト画面 UseCase protocol TaskListUseCase : class { // Dependency var output : TaskListInteractorOutput? { get } /// すべてのタスクリストを取得する func fetchAllTaskList () } /// タスクリスト画面 InteractorOutput protocol TaskListInteractorOutput : class { /// すべてのタスクリストを出力する func outputAllTaskList (_ taskList : [ TaskModel ] ) } /// タスクリスト画面 Wireframe protocol TaskListWireframe { /// タスクリスト画面のビューコントローラー var viewController : UIViewController? { get } /// タスク作成画面を表示する func presentCreateTaskView () } VIPER による基本構成に加え、データレイヤーには Repository パターンを適用しています。TUNAG のチャット機能におけるデータソースは、 Firebase の Cloud Firestore、TUNAG の REST API、ローカルストレージと多岐にわたるため、Repository パターンによりそれらを隠蔽しています。Repository は特定のユースケースに依存せず、例えばユーザー情報やチャットメッセージ情報のようなシンプルなデータの CRUD のみを責務とするため、複数の画面から使用されます。 また、Cloud Firestore にあるユーザー情報やチャットメッセージ情報などのリアルタイムアップデートを受け取り、それらをマージして Interactor に流していくために RxSwift を使用しています。MVVM アーキテクチャーを採用しているわけではないですし、OS 標準ではない特定のライブラリにアプケーション全体が依存することを避けるため、RxSwift は Interactor より先のプレゼンテーション層では使用しないようにしています。 良かった点/悪かった点 このような構成で2018年10月からチャットの開発をはじめ、2019年1月初に初版をリリースしました。その後細かいアップデートをしつつ、この4月にはチャットメッセージの検索のような大きな機能追加の開発を行なっています。VIPER を採用した開発を始めて半年経った現時点における、個人的に良かった・悪かったと感じている点を箇条書きでまとめます。 良かった点 画面実装時に前述の Contract ファイルを用意することで、どのパーツは何に関与して、何に関与しないのか?と常に考えながら実装していくことになり、自分もチームメンバーもオブジェクト指向での設計力が格段に上がったという実感があった(よくよく考えてみると SOLIDの原則 を忠実に実践していくことになっていた) 各パーツの責務が明確で、それぞれの間が Protocol により疎結合になっていることで、ユニットテストがとても書きやすい チャットの開発は最低限の機能を実装し、徐々に肉付けしていくスタイルで行なっていったが、足し算が非常にやりやすい 機能追加後のコードの Diff も何の機能追加や変更を行なったかがとてもわかりやすかったため、PR のレビューもしやすい 初版リリースの前にQAチームによるテストや社内でのβテストを実施したが、機能のボリュームのわりには不具合検出数が非常に少なかった 悪かった点 1つの画面を作るのに多くのクラスのファイルが必要であるのはやはり面倒だった( 例えば Swift-VIPER-Module のような自動生成ツールを試すべきだった) これは VIPER の問題ではなく私の問題だが、初版リリース時に部分的にしかユニットテストを書くことができなかった(ただし、その後徐々にテストを追加していっているが、実装コードを変更せずともテストを書けていけているのは VIPER のメリット) コード量で見る成果 チャット機能の初版リリースの直前に、VIPER 適用の振り返りの意味もこめて、開発着手から初版リリースの間に書いた Swift コード量を集計してみたました。結果は次のとおりです。 新規ファイル数: 約200 (≒クラス数) 追加/変更LOC: 約10,000 単純に計算すると、1ファイルあたり 50 LOC くらいになっています。VIPER で他のパーツの Mediator として働く Presenter は比較的にコード量が多くなりがちなのですが、チャット機能でもっとも複雑なチャットルーム画面(「なぜ VIPER を採用したのか?」でお見せしたスクリーンショットの画面)の Presenter においても 500 LOC 程度で収めることができていました。 これも VIPER を採用したことで設計がうまくいった成果だと思っています。 おわりに スタメンでの私の最初のミッションはまさかのチャット機能でした。とても大きな機能なのでやりきれるかという不安をなくすため、最初にこのような VIPER による設計方針を固め、それにしたがって1画面作り終えた頃に VIPER の良さを実感し、それ以降は特に不安なく淡々と機能開発を進めていくことができました。この開発期間は私のエンジニア人生の中でも特に濃密なものになりましたので、その振り返りをしながらこの記事を書いています。 TUNAG iOS アプリのチャット開発は一旦落ち着きましたが、TUNAG の iOS/Android アプリとしてやるべきことはまだまだ多く、引き続きより良いアプリにしていくために一緒になって開発してくれるエンジニアが必要です。もし興味がありましたら こちら からご連絡ください。一緒に濃密なエンジニア人生を送りましょう!
アバター
こんにちは! スタメンで iOS / Android アプリエンジニアとして インターン をしているカーキ @khaki_lit です! スタメン開発ブログに登場するのは初めてになります.現在は主にTUNAGの Android 版を開発していますが,以前は iOS 版のTUNAGチャットの開発にも少し関わっていました. try! Swift とはTUNAGの iOS アプリでも使用されているSwiftという言語の国際的なカンファレンスとなります.私は2日目(26日)の方に参加してきました.初日のレポートは @temoki が こちら の記事で書いています. 近くて遠い世界 自分はtry! Swiftのような国際的なカンファレンスに参加するのは初めてだったのですが,今回感じたのが 世界の近さと遠さ です. 世界の近さ 自分たちと同じようにSwiftという言語を使って iOS アプリを開発しているエンジニアが世界中にたくさんいるんだと認識し,世界の近さを感じました.私は普段 OSS などに参加していないため,アプリを開発していて世界を感じることはありませんでしたが,Swiftのコミュニティは世界中に広がっていて,自分もその一員なんだと今回参加して実感しました.言葉も国籍も違う人々と『Swift』という共通言語を通じて話せることに感動しました! 世界の遠さ その一方で世界への遠さも同時に痛感しました. 世界で活躍するエンジニアの発表は本当にどれもすごくて,今の自分には理解できないようなレベルの話もあり,自分の未熟さを突きつけられました. 個人的に衝撃を受けたスピーカーが18歳で発表時高校を卒業したばかりという @kateinoigakun さんです. 自分は大学生で若いしまだまだこれからだとばかり思っていましたが,自分より若い世代が世界的なカンファレンスで英語で堂々と発表しているのを生で見たことで自分自身もっと力をつけてこのようなカンファレンスに立ちたい!と強く思いました. 印象深い発表 どれも素晴らしかったのですが,特に印象に残っているのが Mayuko Inoue さんの発表です.彼女はSwiftの技術的な話ではなく iOS デベロッパ ーの多様性 の話をされていました. iOS デベロッパ ーの多様性?と思われるかもしれませんが,事実 iOS アプリは Mac でしか開発することができず, デベロッパ ー登録にも多額なお金が必要な iOS 開発者は欧米とアジアにしかほとんどいないそうです.格差問題は個人の力ではどうしようもできませんが,彼女は自らのエンジニアとしての生活を YouTube を通じて紹介することで エンジニアの生態 を知ってもらう取り組みをしています. シリコンバレー のエンジニアの1日を紹介した こちらの動画 など 自分たちを紹介することで知らない人にとっては謎に満ちたエンジニアという職業を少しでも知ってもらい,エンジニアに対するハードルを下げる取り組みです. スタメン自身もこの技術ブログを通じてスタメンのプロダクト部及びエンジニアの日常や使っている技術などを紹介し,少しでもスタメンに興味を持ってもらえたらと考えているためリンクする部分がありました.そしてこれから個人でも発信していきたいと改めて感じました. 最後に 私が参加したのは2日目と3日目のワークショップでしたが普段名古屋では得られないような刺激をたくさん受けました.2日目のアフターパー ティー や3日目のワークショップで東京や海外のエンジニアの方とお話しできたのもエキサイティングな経験でした!
アバター
はじめに 4/18〜4/20に開催されたRubyKaigiに、スタメンエンジニア @mmoto99299415 (写真左) と @uuushiro (写真右)の2名で参加してきました。そのレポート記事になります。 セッション いくつか気になったセッションを紹介します。 1日目 Building Serverless Applications in Ruby with AWS Lambda AWS SDK for Ruby チーム の@alexwwoodさんによるセッションでした。 柔軟性・スケーラビリティ・高可用性・セキュア・従量課金制といったサーバーレスの利点の説明から、実際に AWS SAM CLI ・ AWS SDK for Ruby ・ aws -record を用いたサーバーレスアプリケーションのライブデモの実施まで丁寧に説明してくれました。スタメンでも分析基盤の コンポーネント の一つに Ruby を実行する AWS Lambdaがあります。別の機会にテックブログで紹介したいと思います。 https://speakerdeck.com/awood45/building-serverless-applications-in-ruby-with-aws-lambda GraphQL Migration: A Proper Use Case for Metaprogramming? Square Incのエンジニアである@gao_shawneeさんによるセッション。 GraphQLを扱うにあたり、サーバーサイド側では、モデルに対応したType・Fieldを定義する必要があります。加えて、Queryによりデータを取得するため、QueryType内に必要となるFieldと、それに対応したメソッドを定義します。対象とするモデルが増えると、Type・Field・メソッドの定義も一緒に増加し、だんだんFatになっていきます。 メタプログラミング を利用することで、動的にType・Field・メソッドを定義してこの問題を防ぐという内容のセッションでした。 スタメンではGraphQLを採用したサービスはまだありませんが、今後導入する場合は同じ問題に直面するので、とても参考になりました。 https://speakerdeck.com/shawneegao/ruby-kaigi-slides 2日目 Yabeda: Monitoring monogatari eBaymag.com を運用しているエンジニアの@Envekさんによるセッションでした。 eBaymagでは大規模なSidekiqの運用をしており、そこで学んだ経験についての トーク でした。例えば、Sidekiqのジョブが遅くなったとき、それがなぜ遅くなったのか、どこから考えるべきか、原因を見つけるにあたって必要な理論はなにか?といったような現実世界での課題に対して具体的な方法を知ることができました。スタメンではSidekiqの数値の監視にはSidekiq標準の ダッシュ ボードしか利用していませんでしたが、このセッションでは、prometheus-client という gem を利用してアプリケーションに簡単に Prometheus で監視するためのメトリックを追加し、Grafanaによってクラフを作成し可視化するということを実践していました。アプリケーションが成長に伴って、監視すべきメトリクスも増えていくことは今後の参考になりそうでした。 https://docs.google.com/presentation/d/1i8N_OcnQJ9SE6wdqzqV-vp_IYRxQRpEtJ0tpluYtCvo/edit#slide=id.g5675f30aae_1_230 3日目 Cleaning up a huge ruby application Cookpad Incのエンジニアである@riseshiaさんのセッション。 不要なコードを削除することで、コードの可読性が高まり、アプリケーションの読み込み時間も改善できたという内容でした。 具体的には次のような取り組みが紹介されていました。 - コードを削除するための仕組みづくり(どのようにメンバーに アサイ ンするか) - ソースコード が実行されているかどうかの調査方法 → logの取得 - 詳細なlogの取得方法(InstructionSequense, oneshot coverage) コードを削除することは、成果がすぐに現れないため、どうしても優先順位が後ろになります。でもその中で、仕組み化し、実践していく方法は参考になりました。 1年間で実際に数万行のコードを削除できた。という成果をみて、毎日の積み重ねの重要さを感じました。 https://speakerdeck.com/riseshia/cleaning-up-a-huge-ruby-application ブース 各企業のブースを見て回りました。 普段、RubyMineを使って開発しているのですが、そのエディタを提供しているJetBrainsもブースを出していました。様々な ノベルティ が有り、面白かったです。 同じ名古屋からは エイチーム がブースを出しており、はるばる福岡で話をすることができて嬉しかったです。 他にもブースを回ったのですが、エンジニアの方とゆっくり話ができて、有意義な時間が過ごせました。 感想 松谷(@uuushiro) 初参加のRubyKaigiとても楽しかったです。特に印象に残ったのは、3日目の「 Ruby Committers vs the World」という、 Ruby コミッタの方たちがステージ上でこれからの Ruby について様々な議論を繰り広げるといったコーナーです。そのコーナーの中では、コミッタの皆さんの Ruby を良くしたいという思いと、絶え間ない努力を重ねてきたという過程を垣間見ることができ、改めて支えてくださっている方々に感謝の気持ちが湧きました。 コミッタの方同士の掛け合いが日本語で交わされるのも日本発祥の プログラミング言語 ならではだと思いましたが、グローバルな仲間たちとコミュニケーションをとり、RubyKaigiをもっと楽しめるように英語の壁を乗り越えて来年のRubyKaigiを迎えたいです。また、 Ruby を支えている側と利用している側の間に大きい技術的な壁を感じました。私自身、 Ruby を支えている方々と同じ目線で Ruby の未来を考えられるように、エンジニアとしてもっと成長したいと思いました。 スタメンの創業事業「TUNAG」は、 Ruby を利用し成長してきました。スタメンとしても Ruby にお世話になるだけではなく、恩返しをしてきたいと思います。 来年のRubyKaigiは 松本市 ということで、名古屋から近いのでスタメンのエンジニアみんなで参加します。 RubyKaigiでお世話になった皆さん、本当にありがとうございました。来年もよろしくお願いします。 ミツモト(@mmoto99299415) RubyKaigiに参加し、充実した時間を過ごすことができました。各セッションを通じて、普段の開発では知ることのできない内容に触れ、深く掘り下げるきっかけが得られました。(ただ、日本語通訳が無いのは辛かった汗) 最も心に残ったセッションは、自分も松谷と同じ「 Ruby Committers vs the World」です。 互換性を捨ててでもアグレッシブに仕様を変えていくべきか?という議題や、version upで追加したい新しい機能(右代入、イテレートのブロックパラメーター)において、様々な意見が交わされていました。 Ruby を開発する上での議論を目の当たりにして、1Rubyistとして興味深かったです。 これまで、 Ruby という言語で当たり前のように開発していましたが、その裏側にいるコミッタ、安定版のversionを提供するメンテナーの方々のおかげで、普段の開発が成り立っていることを肌で感じることができました。 加えて、RubyKaigiを通じて、他のエンジニアと交流を持てたこともすごく嬉しかったです。アプリケーションを作るにあたり、 アーキテクチャ 、技術選定、開発体制など、一緒に議論できる仲間が増えたのはすごく良かったです。 おまけ スタメンはちょうど4月に福岡支社を立ち上げたので、福岡支社のメンバーとめんたい重を食べてきました! 福岡でもスタメンをよろしくお願いします。 おわりに 今回、RubyKaigiには業務として参加しており、宿泊費と交通費を会社に負担してもらいました。 スタメンでは Ruby を書く機会がたくさんあります!絶賛 Rubyist を募集中です!!興味がある方は Wantedly よりご連絡ください。
アバター
こんにちは、Web アプリケーションエンジニアのミツモトです。 普段は TUNAG という、企業やコミュニティを対象としたサービスの開発しています。 今回のブログでは、TUNAGのユーザー登録を実装するときに採用した、Rails の FormObject を取り上げます。 目次 はじめに FormObject 採用例 ActiveModel::Model おわりに はじめに ユーザー登録にあたり、ユーザーだけでなく、その付属情報を扱う Model (所属など)も同時に保存する必要がありました。 それらを各々で処理すると、同じような処理を Controller で繰り返し書くことになり、見通しが悪くなります。 Rails の FormObject を採用することで、複数の Model を一緒に保存でき、 Controller の肥大化を防ぐことができました。 この記事では FormObject とその採用例をご紹介させていただきます。 FormObject 単一のフォーム送信で複数の ActiveRecord モデルを更新したい場合に、その永続化ロジックをカプセル化できるデザインパターンです。 ActiveModel::Model というモジュールを include することで利用できます。 メリット ビジネスロジックが Controller に出ないため、各レイヤーの可読性が良くなる 種類の異なる複数 Model を1つの Model として扱える validation がかけられる 渡ってきた parameter を FormObject のクラス内で parse できる DBに依存しないインスタンスでも、Active Recordと同じインターフェースで扱える(通知など) 採用例 採用する前 ユーザーのみを新規作成する場合、Controller は以下になります。 # ユーザーの新規作成フォームを表示 def new @user = User .new end # ユーザーを作成 def create @user = User .new(user_params) if @user .valid? @user .save end end 扱う Model が増えると... # ユーザー、所属、住所の新規作成フォームを表示 def new @user = User .new @department = Department .new @address = Address .new . . . end # ユーザーを作成 def create @user = User .new(user_params) @department = Department .new(deparment_params) @address = Address .new(address_params) . . . if @user .valid? && @department .valid? && @address .valid? ... @user .save @department .save @address .save . . . end end 同じような処理が増えて、Controller が見辛くなります。 採用した後 User, Department, Address...をまとめるため、Form::Registration という FormObject のクラスを作ります。 Controller の処理 # 登録の新規作成フォーム def new @registration = Form :: Registration .new end # 登録の実行 def create @registration = Form :: Registration .new(registration_params) if @registration .valid? @registration .save # User と Department と Address が create される end end Form::RegistrationのFormObjectクラス class Form :: Registration include ActiveModel :: Model attr_accessor :user , :department , :address validate :validate_user validate :validate_department validate :validate_address def initialize ( params : {}) @user = User .new(params[ :user_params ]) @department = Department .new(params[ :department_params ]) @address = Address .new(params[ :address_params ]) end def save @user .save @department .save @address .save end . . . end まとめてvalidation をかけたり、parameter を FormObject のクラス内で parse できます。 また、各 Model の attributes ではないけど、保存するかどうかの判定に必要な parameter もここで受け取り、 Form::Registeration の validation として追加することができます。 このように FormObject を使うには、Form::Registration で include している ActiveModel::Model が必要です。 ActiveModel::Model ActiveModel::Model を include することで、自分で定義したクラスを FormObject として Model のように扱うことができます。 中を見ると、 module ActiveModel module Model extend ActiveSupport :: Concern include ActiveModel :: AttributeAssignment include ActiveModel :: Validations include ActiveModel :: Conversion included do extend ActiveModel :: Naming extend ActiveModel :: Translation end . . . end end ActiveModel 関連のモジュールが include , extend されています。 errors, valid? などのインスタンスメソッドがを扱える ActiveModel::Validations や、 エラーメッセージを翻訳できる ActiveModel::Translation が定義されています。 Model のようにインスタンスメソッドやクラスメソッドを呼ぶことができます。 おわりに Rails の FormObject を取り上げました。 MVC アーキテクチャだけでは綺麗にコーディングできない部分を、別のレイヤーに切り出すことで、可読性を高めることができます。もし機会があれば、試しに使ってみてください。 最後まで読んでいただき、ありがとうございました。 スタメンでは エンジニア を募集しています。興味がある方はぜひご連絡ください。
アバター
こんにちは、スタメンエンジニアの松谷です。 組織のエンゲージメントを高めるプロダクト TUNAG(ツナグ) を開発しています。 開発・運用に関わる中で日々思うのは、アプリケーションの管理をよりシンプルにし、セキュアで信頼性が高く、スケーラブルに運用することを容易にしたいということです。 AWS Systems Manager には、これらのニーズを満たす多くの機能があることを知り実際に導入してみました。簡単に導入することができ、運用上大きな効果を得ることができたので共有させていただきます。 TL;DR (概要) スタメンのSystems Managerを活用することで、 SSH レスで安全なオペレーションを実現し、監査可能な操作ログを保存し、現状のサーバーの状態を簡単に把握できるようになりました。具体的な活用事例とともに紹介します。 目次 スタメンにおける活用事例 セッションマネージャーでSSHアクセスの廃止 オートメーションで日常タスクを1クリックで自動化 イベントリでインスタンスの状態を簡単に把握 まとめ スタメンにおける活用事例 AWS Systems Managerが持つ機能のうち、スタメンで活用している機能とその運用について説明します。 セッションマネージャーで SSH アクセスの廃止 セッションマネージャーの機能の説明は以下の公式ドキュメントをご確認ください。 セッションマネージャー を使用して、 インタラクティブ なワンクリックブラウザベースのシェル、または AWS CLI を介して Amazon EC2 インスタンス を管理できます。セッションマネージャー は、インバウンドポートを開いたり、踏み台ホストを維持したり、 SSH キーを管理したりすることなく、安全で監査可能な インスタンス の管理を提供します。セッションマネージャー は、 Amazon EC2 インスタンス への簡単なワンクリックの クロスプラットフォーム アクセスをエンドユーザーに提供しつつ、 インスタンス への制御されたアクセス、厳格なセキュリティプ ラク ティス、 インスタンス アクセスの詳細を含む、完全に監査可能なログを必要とする企業ポリシーに準拠することを容易にします。 AWS Systems Manager Session Manager - AWS Systems Manager 簡単に説明すると、 SSH 接続せずに、 AWS マネジメントコンソール(もしくはAWSCLI)上でEC2 インスタンス に対してオペレーションができる機能です。 適切なロールがEC2にアタッチされていて、SSMエージェントがインストールされている インスタンス が以下のように、一覧で表示されます。 インスタンス を選択して、セッションを開始をクリックすると、セッションが開始され以下のようにシェルライクな画面がブラウザで表示されます。 任意の操作が完了した後、終了をクリックするとセッションが終了します。 そして、セッション履歴のタブをクリックすると、過去に起動したセッションの履歴を確認することができ、ここでは、いつ、だれが、どの インスタンス のセッションを起動したのか、そして、そのセッションでどのようなオペレーションを実行していたのかは、設定画面で「S3 バケット ストレージの有効化」または、「CloudWatch Logs ストリームの有効化」をONにしていれば確認することができます。ローカルのターミナルでオペレーションをしていた頃は、問題が起きた時に過去のログを遡ることができない場合があり、困ったことがありましたがこれで安心です。 また、新しいセッションが始まった時点で SNS 通知をすることも可能です。 セッションマネージャーの導入により、各開発者からの SSH アクセスが不要になり、踏み台サーバーを廃止することができました。セキュアになるだけでなく、踏み台サーバ代も節約できて良いこと尽くしです。 オートメーションで日常タスクを1クリックで自動化 オートメーションの機能の説明は以下の公式ドキュメントをご確認ください。 Systems Manager オートメーションを使用して、一般的なメンテナンスとデプロイのタスクを自動化します。オートメーションを使用すると、 Amazon Machine Image の作成と更新、ドライバーとエージェントの更新プログラムの適用、 Windows インスタンス でのパスワードのリセット、 Linux インスタンス での SSH キーのリセット、および OS パッチまたはアプリケーション更新プログラムの適用が可能になります。 オートメーションドキュメントは、Systems Manager がマネージド インスタンス および AWS リソースで実行するアクションを定義します。ドキュメントは JavaScript Object Notation ( JSON ) や YAML を使用し、これにはユーザーが指定するパラメータおよびステップが含まれます。ステップは順番に実行されます。 AWS Systems Manager オートメーション - AWS Systems Manager Systems Managerで実行する各種操作はドキュメントとして定義されています。 AWS が定義済みのドキュメントや、自分で作成するカスタムドキュメントがあります。 スタメンでのオートメーションの活用例の一つに、ステージング環境へのデプロイがあります。以下は、特定 インスタンス に対して、デプロイ スクリプト を実行する、カスタムドキュメントです。 --- description: "deploy staging" schemaVersion: "0.3" parameters: BranchName: type: "String" description: "(Required) Deploy target Branch" InstanceId: type: "String" description: "(Required) InstanceId to run command" default : "instance id" S3BucketName: type: "String" description: "(Required) S3BucketName" default : "tunag-systems-manager" S3KeyPrefix: type: "String" description: "(Required) S3KeyPrefix" default : "staging/deploy" mainSteps: - name: "deployStaging" action: "aws:runCommand" inputs: DocumentName: "AWS-RunShellScript" InstanceIds: ["{{ InstanceId }}"] OutputS3BucketName: "{{ S3BucketName }}" OutputS3KeyPrefix: "{{ S3KeyPrefix }}" Parameters: commands: - su operator -c "source ~/.zshrc; cd /home/app; ./bin/deploy_staging.sh {{ BranchName }}" これは、ステップ数が1つのシンプルなドキュメントです。parametersで、パラメータ化したいキー・バリューを指定しています。 aws :runCommandは、指定されたドキュメントを実行するアクションです。 aws :runCommandでは、1つ以上の管理対象 インスタンス でコマンドを実行するためのオプションが指定可能です。DocumentNameで、Systems Manager が実行するドキュメント( AWS -RunShellScript)を指定しています。 なお、commandsオプションで実行されるコマンドリストは、1つ1つssm-userというユーザーで実行されます。弊社のデプロイフローでは、特定 Linux ユーザーに限定してデプロイをしていたので、suコマンドのcオプションを利用することで、ユーザーを切り替えた状態でコマンドの実行をしています。 また、オートメーションの実行リンクを、チームに共有すれば簡単に特定タスクの実行画面にアクセスすることができ便利です。下図を例にすると、デプロイしたいブランチ名を入力パラメータに入れて実行をクリックするだけでデプロイを開始することができます。 今までアプリケーションエンジニアは、デプロイを実行したいだけなのに SSH 鍵の登録が必要になっていました。今回、オートメーションを使うことで、アプリケーションエンジニアに特定リソースへの SSH アクセス権限を付与する必要なく、特定のリソースの特定のタスクに対するアクセス権限をIAMによって提供できるようになりました。これはセキュリティ的に大きな前進でした。 また、Systems Managerによるドキュメント化によって、チーム内で属人化していた運用のベストプ ラク ティスを リージョンやIAMグループ間で簡単に共有ができるようになりました。また、ドキュメントが受け入れる許可パラメータ値のバリデーションも行うことができ、ミスオペレーションを防ぐことができます。 今回はとてもシンプルなタスクをSystems Manager上で管理できるようにしましたが、Systems Managerは、 インスタンス だけでなく周辺のサービスも含めて自動化できる フレームワーク なので、積極的に複雑なタスクを簡素化・自動化していきたいと思います。 オートメーションには数多くのアクションがあるのでぜひ公式ドキュメントをご確認ください。 Systems Manager Automation アクションのリファレンス - AWS Systems Manager イベントリで インスタンス の状態を簡単に把握 イベントリの機能の説明は以下の公式ドキュメントをご確認ください。 インベントリを使用して、 Amazon EC2 インスタンス およびオンプレミスサーバー、またはハイブリッド環境の 仮想マシン ( VM ) から、 オペレーティングシステム (OS)、アプリケーション、 インスタンス の メタデータ を収集できます。 メタデータ を照会すると、ソフトウェアポリシーに従ってソフトウェアと設定を実行している インスタンス と、更新が必要な インスタンス をすばやく把握できます。 AWS Systems Manager インベントリ - AWS Systems Manager インベントリ情報を取得することで、 インスタンス の全ての構成要素(OS, ミドルウェア , ライブラリ)の一覧とそのバージョンを簡単に管理することができます。OSのセキュリティ設定に問題はないか、不要な ミドルウェア は存在しないかなど、各 インスタンス の状況を把握することができます。また、取得した情報は、以下のように ダッシュ ボード形式で表示することができます。 まとめ まだまだSystems Managerの一部機能しか使いこなしていませんが、この時点で既に感じているメリットは以下の4つです。 1. 権限の可視性と制御性の向上 IAMロールで制御可能になるので、誰がどのサーバーにどのアクセス権限をもっているのかが AWS 上で簡単に管理が可能 2. セキュリティと コンプライアンス の向上 踏み台サーバーが不要 SSH 用のインバウンド用ポート開放が不要 SSH キーペアの管理不要 サーバーのインベントリ情報の管理が簡単に アクセス履歴がS3やCloudTrailで可能になり、サーバーアクセスなど監査可能なログを必要とする企業ポリシーに準拠が可能 3. 運用業務の自動化と定型化 繰り返し作業の自動化と定型化ができ、コスト削減とミスオペレーションの削減が可能 4. 無料で利用可能 AWS Systems Manager自体は無料 Systems Managerによって管理される AWS サービスを使用している場合、そのサービスの料金は発生 サービスを運営するにあたって必要なセキュリティ要件も、 AWS のサービスを利用することで、ほとんど 工数 を掛けず、サービス全体のセキュリティを高めることができました。これからも適切な AWS サービスを選択し、運用コストを下げていこうと思います。 スタメンでは一緒にシステムの信頼性を向上させていく仲間を募集しています。ぜひ こちら からご連絡ください。
アバター
こんにちは、スタメンで iOS / Android アプリのエンジニアをしている @temoki です。 TUNAG の iOS アプリはすべて Apple 発の プログラミング言語 Swift で書かれていますが、この3月にその Swift の国際カンファレンス try! Swift 2019 Tokyo が開催されました。 アプリ開発 の情報収集やスキル向上のために、昨年からスタメンのエンジニアもこのカンファレンスに参加しており、今回は私が参加してきましたので、その様子をお伝えしたいと思います。 国際色豊かな会場 開催場所は ベルサール 渋谷ファーストです。到着すると緑色のTシャツを着たたくさんのスタッフと、try! Swift キャ ラク ターの Riko が出迎えてくれ、地下の会場の方に案内していただきました。 こちらがセッション会場です(1日目最後の写真撮影の時のものですが)。国内外からなんと900人の参加があったようです。海外からの参加者も非常に多く、国内でこれだけ国際色豊かな プログラミング言語 に関するカンファレンスは希なのではないかと思います。 スピーカーによる素晴らしい発表の数々 このカンファレンスは主に招待スピーカーによる発表と、CFP の募集から選ばれたスピーカーによるライトニング トーク が中心となります。司会進行は日本語と英語両方で行われ、発表も同時通訳が入るなどオーディエンスへの配慮も万全でした。特に同時通訳の質が高く、よくこれだけ専門的な内容をすぐに変換できるものだなと驚きました。おかげで英語が得意ではない私も発表内容に専念することができました。 個人的に刺さった発表の1つは @1024jp さんの native macOS application、またはAppKitの世界 です。以前に渡邊恵太さんの 融けるデザイン という書籍に感銘を受けたのですが、その内容が macOS のアプリケーションという世界で具体化されたような素晴らしい発表でした。@1024jp さんが開発されている CotEditor は masOS に最適化されてとても使いやすく、今も愛用しています。 そしてもう一つ、最も興味深かったのが @terhechte さんの Introduction to Swift Keypaths です。Swift の KeyPath 機能については私の知識は Swift 3 時代のもので止まってしまっていたので、型情報が保持される点など KeyPath が非常に強力なものになっていることに驚きましたし、それを ジェネリクス や プロトコル とは異なる抽象化の手段として用いてしまった点は 目から鱗 でした。 個人スポンサーがとてもオススメです これは個人的な活動となりますが、このような良いコミュニティが今後も継続していくことに少しでも貢献できればと思い、個人スポンサーとして登録していました。すると、この写真のように開会時に紹介していただいたり、公式ウェブサイト、公式アプリ、そして会場の入り口のスポンサーボードなど様々なところでアイコンを掲載していただきました。スポンサーボードには皆さんサインされていたので私も書いてきましたよ。また、これによって他の参加者との会話のきっかけにもなりましたので、try! Swift の個人スポンサーはオススメです! 多種多様なスポンサーブース try! Swift にはたくさんのスポンサーがついており、各社工夫をこらしたブースを出されています。写真は Cyber Agent さんの投票コーナーです。Application Architecture のところで私は VIPER に最初の一票を入れてきました。TUNAG の iOS アプリは VIPER アーキテクチャ ーを採用しています。このあたりはまた今度このブログで詳細を書きたいと思います。 このようなスポンサーブースで各社のサービスについて聞いたり、それを支えるエンジニアなどとも交流することができました。その中のトレタさんのブースで説明されていた、今月にリリースしたばかりの トレタnow という飲食店を当日予約で最短10分後に入れるというサービスが素晴らしかったです。飲食店等の予約管理をしているトレタさんだからこそできることですね。この日は参加者10人くらいで夕食に行くことになったのですが、早速このトレタnowで予約して、本当にすぐにお店に入れてしまいました。二次会会場探しなどにすごく役立ちそうですし、店舗としては空きを極力減らせて良いサービスだなと思いました。 最後に 私は都合により1日目だけの参加となりましたが、この1日だけで非常に多くの刺激を受けましたし、たくさんの素晴らしい発表によって多くの知見も得ることができました。スタメンはまだ創業期であるにもかかわらず、こういったカンファレンスにエンジニアを積極的に送り出していただけるのはとてもありがたいですね。 スタメンではこのような自己研鑽を奨励する文化があり、それをサポートする環境も整っているため、エンジニアが成長し、それがプロダクト開発に活かされるという良いサイクルが生まれていると思います!こんな環境の中で一緒に切磋琢磨してみたいと思っていただけたら、ぜひ こちら からご連絡ください。
アバター
こんにちは、スタメンのエンジニア、津田です。以前、弊ブログでも「 TUNAGの全文検索を支える Elasticsearch × Rails 」として紹介させていただいたように、TUNAGでは検索機能の実装にElasticsearchを利用しています。検索クエリとしては主にMulti Matchを利用しているのですが、RDBに登録されているレコードを利用しやすい形でElasticsearchのドキュメント化する方法について試行錯誤したため、共有させていただきます。 TL;DR (概要) Elasticseachでは、 Multi Match Query を使って、複数のフィールドを検索対象とするクエリを発行できます。検索時に対象とするフィールドを指定することができるため、一つのインデックスを、検索対象フィールドの異なる複数の用途で共用することができます。ただし、一つのインデックスに含められるフィールド数には制限があるため、注意が必要です。 実現したかったこと Elasticsearchを検索用途で導入した場合、RDBには1:Nで格納されている情報を、Elasticsearchには1つのドキュメントとして登録するケースがあります。たとえば、「1つの商品 : N個の商品情報」、を、「1つの商品情報」ドキュメントにまとめてから、Elasticsearchに登録するような場合です。「N個の商品情報」を「1つの商品情報」に折りたたむ際には、「検索時にどのような情報を検索対象とするか」を考える必要があります。 検索の用途として、 店舗利用者が、購入時に参照可能な商品情報のみを検索する 店舗担当者が、店舗が参照可能な商品情報のみを検索する システムの管理者が、すべての商品情報を検索する という3つのユースケースがあったとします。やり方として、二通り考えられます。 ドキュメント"登録時"に必要な情報を予めまとめてしまう ドキュメント"検索時"に必要なフィールドのみを検索する 順に説明します。 ドキュメント登録時に必要な情報を予めまとめてしまう この場合は、たとえば、以下のように3つのフィールドに情報をまとめる、具体的には必要な文言を結合して登録することで、情報を折りたたむことが可能です。 商品情報 店舗利用者用フィールド: 「商品名、商品の特徴、保証について、etc...」 システム管理者検索用フィールド : 店舗利用者用フィールド +「販売時の注意点、店舗での管理方法、etc...」 店舗担当者用フィールド : システム管理者用フィールド + 「システム管理上必要な情報、etc...」 このようにドキュメントを登録した場合、検索時には1つのフィールドを対象とすればいいので、 Match Query で検索することができます。 ただし、この方法では、検索時により細かい調整をすることができません。たとえば、「このページでの検索は、商品名、特徴のみから検索しよう」となった場合、利用できるフィールドがないため、ドキュメントの再登録をしない限り対応できません。 ドキュメント検索時に必要なフィールドのみを検索する そこで、以下のようにドキュメントを登録します。 商品情報 商品名 商品の特徴 保証について 販売時の注意点 etc... このように構築したインデックスに対して Multi Match Query を使えば、検索時に対象とするフィールドを変更して対応することが可能です。 Multi Match Query では、複数のフィールドに対してMatchクエリを実行するようなクエリです。以下のタイプがあり、タイプによって挙動が異なります。 best_fields most_fields cross_fields phrase phrase_prefix 今回のような用途では、cross_fiedsを使います。 cross_fields タイプの検索では、「指定されたフィールドを大きな一つのフィールドとして扱い、与えられたクエリと一致しているものを検索する」ような動きをします。 たとえば、「金槌 丈夫」で検索した場合、マッチする情報を持つのは「商品名」フィールドと、「商品の特徴」フィールドです。cross_fieldsタイプのクエリであればこれはうまくマッチしますが、たとえば「best_fields」ではヒットしません。「best_fields」も複数のフィールドを対象として検索するのですが、ヒットするためには、同じフィールドに「金槌 丈夫」が含まれている必要があるからです。 cross_fieldsを指定したmulti_matchクエリは、以下のような形になります。 { " query ": { " multi_match " : { " query ": " 金槌 丈夫 ", " type ": " cross_fields ", " fields ": [ " 商品名 ", " 商品の特徴 ", etc ... ] , " operator ": " and " } } } 問題点 このように便利なcross_fieldsですが、今回は上記のやり方では使いませんでした。 今回、登録するドキュメントは、インデックス単位で見ると、「商品の特徴」がN個あり動的に増えるものでした。さらに複数のアナライザ(ngramとkuromoji)でフィールドを分けていたため、 商品情報 商品特徴1_kuromoji 商品特徴1_ngram 商品特徴2_kuromoji 商品特徴2_ngram ... となっており、フィールド数の上限を設定するのが難しかったからです。 Elasticsearchには「1つのフィールドに含まれるフィールド数の上限(デフォルトでは1,000)」が存在しています。これは、インデックス作成時に、 index.mapping.total_fields.limit を設定することで変更可能ではあります。 しかし、そもそもこの制限は、 Add limit to total number of fields in mapping にあるように、クエリ実行時に Mapping Explosion が発生するのを避けるためのものです。 実際に、動的にフィールドが増加する形でドキュメントを登録する場合は、事前に予想されるフィールド数上限と、上限に付近に達した状態での動作検証が必要と思われます。 まとめ Elasticsearchでの検索は非常に高速で、クエリも高機能なものがありますが、それを活かすためにはINDEXの設計からきちんと行っていかなければいけないものだと感じました。 スタメンではエンジニアを募集しています。もし興味がありましたら こちら からご連絡ください。
アバター
こんにちは、スタメンCTOの小林です。 2019年3月1日に、スタメンのエンジニア全員で合宿を行い、スタメンのエンジニアチームが目指している姿(VISION) と 個々のエンジニアの価値観や行動指針(VALUE) を全員で話し合って決めました。 スタメンのエンジニアが作っている『TUNAG』の技術的な解説 と合わせると、スタメン開発チームの現在と将来をご理解いただけると思います。 スタメンの開発チームに興味をもってくださったら、ぜひ @lifework_tech に気軽にご連絡ください。オフィス紹介や詳細をお伝えさせてください。 なぜ合宿で決めたのか 現在、スタメンには、10名のエンジニアが在籍しています(入社予定者含む)。この10名は、プログラミングが好きで、成長意欲に溢れ、スタメンで大きな仕事をしたいと思っているなど、多くの共通点があります。 とはいえ、各自のスタメンに入社するまでの経歴は様々ですし、それぞれ異なったすばらしい個性もあります。現在スタメンで担当している役割・領域も異なります。 今後、TUNAGの大規模化により技術的な難易度は上がり、新規事業では一層スピードが求められ、エンジニアメンバーが増加することで多様性が増していく、など様々な課題が待っています。 これらの課題に対応するためには、各エンジニアは一層の成長を求められ、開発チームとしてさらに結束する必要があります。そこで、開発チームとして目指す姿と、個々のメンバーに求められる考えや方針を固め、団結したチームで非連続な成長をしていきたい。こんな背景で VISION と VALUE を定めることにしました。 また、下記の理由で、創業期から現在に至るまでスタメンを支えてきたエンジニア全員が集まり、合宿という形でしっかり議論して決めることにしました。 上意下達のチームではなくて、当事者意識に溢れたチームでありたい。 チームの規模的に、個々の強さに加え、チームの強さで勝負する時期に来ている。 アイデアをみんなから募ることで、少しでも良いアウトプットにしたい。 議論に加わることで、今後加わるメンバーに自分の言葉で伝えていってほしい。 前提となるスタメンの企業理念 と Star Way 株式会社スタメンの企業理念は『 一人でも多くの人に感動を届け、幸せを広める。 』です。 また、すべての部門の全員が大切にしている下記の5つの行動指針「 Star Way 」があります。 Finish Promise ・・・ 約束を守り、最後までやりきる Work Bravely ・・・ 大胆に攻め、挑戦や失敗を讃える Take Ownership ・・・ 当事者意識を持って、自らが率先する Say Niceplay ・・・ 素直に認め合い、強みでチームを引き上げる Enjoy Together ・・・ ワクワクを大事に、一体感を楽しむ 今回、合宿で決めた VISION と VALUE は、上位概念として この企業理念 と Star Way が存在し、エンジニアに合わせてさらに具体的にしたものになります。 スタメン開発チームのVISION (目指している姿) エンジニアリングで事業の成長を牽引する 経営理念に掲げた『一人でも多くの人に感動を届け、幸せを広める。』を実現するために、目指している開発チームの姿がこの VISION です。 エンジニアが事業を作り、事業によって多くの人に感動を届け、幸せを広めたい。また、開発チームが提供する「ものづくり力」が事業の成長を牽引する存在でありたい。 エンジニアのみんなで話しましたが、目指している姿にずれはなく、シンプルで当たり前な内容になりました。 スタメン開発チームのVALUE (価値観と行動指針) VISION を実現するためには、高い技術力と強いチームワークを持った開発チームが必要で、メンバーに求められる価値観や行動指針を定めた内容が 以下の VALUE になります。 ユーザー目線で考える 失敗に向き合う 本音で伝える 誇りに思えないなら十分でない わくわくを心に 順に説明させてください。 ユーザー目線で考える 多くの人に感動を届け、幸せを広めるためには、ユーザーが抱くプロダクト(事業)に対しての期待値を超える必要があります。 そのためには、プロダクトに作り手としての意思を込めつつも、常にユーザー目線で考える必要があります。合宿で話した全員が「ユーザーファースト」な考えを、価値観として最優先していました。 失敗に向き合う エンジニアの仕事には、不具合や障害など失敗はつきものです。でも、その失敗から目をそらしてはだめで、正面から向き合わなくてはなりません。 できる限り失敗しないよう、根性ではなくエンジニアとして創意工夫する。それでも失敗したときには、真摯に失敗に向き合って対応し、失敗から学びを得られるエンジニアでありたい。 本音で伝える どれだけ優秀なエンジニアであっても一人では限界があります。エンジニアリングで事業の成長を牽引するためには、チームで結束する必要があり、スタメンの開発チームでは非常にチームワークを重んじています。 チームとしての結束を強くするために何をVALUEに加えるべきか。議論した結果が「本音で伝える」でした。 レビューや議論など様々な場面において、遠慮して本心を伝えないチームよりも、厳しいことも本音で伝え合えるチームでありたい。そのためには、お互いに一人のエンジニアとして対等に向き合い、リスペクトし合うことが大切と考えています。 誇りに思えないなら十分でない エンジニアとして、プロフェッショナルな仕事をしたい。そのためにも、常に高いレベルの仕事を目指し、一つ一つの成果物に対して誇りを持てる仕事をしたい。「誇りに思えるか」と問われたときに躊躇するようであれば、それは十分な仕事ではないと考えています。 また、成果物に限らず、チーム、プロダクト、実績など、何に対しても誇りに思えるぐらいの仕事をしたいと考えています。 わくわくを心に エンジニアリングは本当に楽しい仕事です。技術で困難な問題を解決したり、プロダクトを多くのユーザに使ってもらえることは、本当にわくわくします。わくわくを常に感じ、エンジニアという仕事を楽しみたい。 わくわくなエンジニアが集まって、わくわくなチームになる。そんな現場でありたいです。 合宿の風景 記念に写真を撮ったので、合宿の風景をご紹介します。 各自事前に考えてきた、VISION と VALUE の案とその考えをそれぞれ発表し、全員でカテゴライズや優先順位を付けて集約していきました。 いつもどおり和やかな雰囲気で始まりましたが、思いのこもった発表と議論は真剣でした。 合宿後は懇親会でした。本音で語り合った後で、みなさん良い笑顔です。 最後に 今回は、スタメンのエンジニアチームの VISION (目指している姿) と VALUE (価値観や行動指針) をご紹介させていただきました。 スタメンのエンジニアが作っている『TUNAG』の技術的な解説 と合わせて、スタメン開発チームを、少しでも知って、ご理解いただけたら嬉しいです。 VISON や VALUE は、掲げただけでは意味がありません。経営理念に掲げた『一人でも多くの人に感動を届け、幸せを広める。』を実現するためには、常に VISION や VALUE を意識して、日々の仕事の中で実践していきたいと思います。 スタメン開発チームは、世界中に誇れる素晴らしいチームです。本当に頼もしい仲間たちが集ってくれました。でも、まだ10名です。もっと頼もしい仲間を集めて、エンジニアリングで世の中に感動と幸せを広めたいと思っています。 こちら から、募集中の職種が確認できます。 こんな環境でいっしょに働きたいなと思っていただけましたら、 @lifework_tech か Wantedly から、ぜひご連絡いただけないでしょうか。
アバター
こんにちは、スタメンCTOの小林です。 最近、面接や勉強会などで社外のエンジニアの方と話した際に、スタメンのエンジニアチームの詳細について、思ったより面白そう、まともそう、やりがいがありそうとの感想をいただくことが続き、スタメンの中の人たちの詳細が外の人たちに伝わっていないと感じることがありました。 ちょうど先月の1/29、株式会社スタメンは、設立3周年を迎えて4年目に入ったこともあり、4年目を迎えたスタメン開発チームの現在を紹介する記事を書いてみます。 まずは、創業以来スタメンのエンジニアチームが作り続けている TUNAG について、技術的な側面から解説します。 スタメンのエンジニアチームの VISION (目指している姿) と VALUE (価値観や行動指針) と合わせて読んでいただけると、スタメン開発チームをより理解していただけると思っています。 興味をもってくださった方は、 こちら からご連絡いただき、気軽にスタメンのオフィスに遊びにきてください。詳細をお伝えさせていただきます! TUNAG について スタメンは、 TUNAG(ツナグ) という、エンゲージメント経営を推進する社内SNSを運営しており、私を含めてエンジニアが在籍しているプロダクト部は、TUNAG の 企画、開発 を担当しています。 TUNAG が生まれた背景 TUNAG ミッションは、エンゲージメントの向上によって、明るく強い組織作りの支援を通じて、クライアントの成長を牽引することです。 スポーツにおいてメンバーが相互に信頼しあっているチームは強いのと同じように、ビジネスにおいても経営陣と従業員、従業員間で信頼関係を築けているチームは強く、様々な業界において競合他社に勝り、継続的な成長を遂げている企業があります。 この、企業においての経営陣と従業員、従業員間で信頼関係の度合いを「エンゲージメント」と呼び、スタメンは「エンゲージメント」を重視した経営を行っている企業に対し、TUNAG による社内制度を軸としたコミュニケーションによって、エンゲージメントの醸成を支援しています。 TUNAG が生まれた背景の詳細や思いについて、代表の加藤が ビジョン に書いています。また、 導入事例 にて TUNAG を導入いただいている企業様を紹介しています。スタメンのエンジニアたちが、世の中にどのように影響しているかを知ることができます。ぜひ読んでいただけないでしょうか。 TUNAG の主な機能 TUNAG は、Ruby on Rails で実装され、Amazon Web Services で運用された SaaS(サース、Software as a Service) です。また、iOS/Android 向け アプリ と Chrome などの PC のブラウザで利用でき、導入頂いている各企業の社内コミュニケーションを担っています。 TUNAG は、規模や業界の異なる様々な企業の社内制度の運営に対応できるように、非常に多くの機能を備えていますが、各機能は4つのグループに大別されます。 SNS SNS については、Facebook に代表されるような、タイムライン、プロフィール、効果的なコミュニケーションに必要な 絵文字、スタンプ、画像・動画の投稿に加え、ビジネスで利用できるチャットを備え、フル機能をもつ社内SNSサービスになっています。 社内制度の作成と利用 社内制度の作成と運用については、Google Form や SurveyMonkey に代表されるような 任意のフォーム を作成できます。制度は、特定の部署や役職のみに利用を制限したい場合がありますし、書籍の購入補助など利用回数に制限(例:月に3回)を設けるなど、利用に際して様々な条件を設定することができます。 書籍の購入補助など管理部門による確認が必要な制度もありますし、投稿前に上長の確認が必要な制度もあります。よって、TUNAGでは、承認経路の設定や承認状態の管理といったワークフロー機能を備えています。 さらに、ワークフローを提供するためには各ユーザーの所属や上長といった組織及び役職の情報が必要となります。TUNAG では、会社全体の各部門の組織図、各ユーザの所属と役職といった、人事マスターを管理することができ、制度の利用設定や、投稿の公開範囲、チャットのルーム設定などを組織単位で設定することができるようになっています。 共通基盤 TUNAG の目的は、エンゲージメント経営の推進です。よって、各企業の運営担当者にとって、それぞれの社内制度の利用状況や、各部署やユーザのログイン状況など、TUNAG の利用状況を把握することは非常に大切で、管理者画面には、グラフ等のダッシュボード機能や、各種CSVエクスポート機能が提供されています。 また、TUNAG 全体を横断的に検索できる 全文検索機能を Elasticsearch を用いて構築しています。 外部連携 他要素認証などの強固なセキュリティ や アカウントの一元管理などを目的に、Google G-Suite や Microsoft Azure Active Directory のアカウントを用いた Single Sign-on を希望されるお客様もいます。よって、TUNAG では、SAML 2.0 に対応しています。 また、PCにダウンロードすることなく、データをエクスポートできるように Google SpredSheet と連携していたり、 ネイティブアプリへPUSH通知を送るために、ニフクラ mobile backend と連携したりと、様々な外部サービスを連携する機能を実装しています。 TUNAG の テクノロジースタック 前述したように多くの機能を実装した TUNAG は、コード量も多く、また、日々データが蓄積されサイト規模が拡大しているため、開発効率、保守性、運用性を重視して技術選定しています。 創業時こそ、CTOの小林がまとめて技術選定しましたが、チームが大きくなった現在では、各分野で専門的な知見を持ったエンジニアが存在し、各々が新しい技術やライブラリをチームに提案して、TUNAG に取り入れています。 技術選定の考えとしては、各分野/技術のベストプラクティスを積極的に導入することで開発/運用を効率化し、確保した時間でTUNAGのコア部分の実装に注力する方針をとっています。 2019年2月時点の主なテクノロジースタックとしては、下記の図のようになります。順に解説させてください。 言語・フレームワーク TUNAGの サーバーサイドは、Ruby on Rails で実装されたモノリシックなWebアプリケーションとなっています。TUNAG のコード量がさらに大きくなり、開発チームも複数になった場合は、機能単位でマイクロサービス化をするかもしれませんが、現時点では開発効率を重視してモノリシックに作っています。 また、タイムライン や チャット など 複雑なUI は React を用いて実装しています。ネイティブアプリは、iOS は Swift で実装されており、Android は Java から Kotlin へ移行中です。 開発支援 エンジニアの日々は、コーディング、デバッグ、テストが大部分を占めます。よって、日々の開発効率は非常に重要です。そのため、スタメンのエンジニアは、全員 RubyMine を用いて開発しており、快適に Rails や React を書いています。 また、GitHub Flow で開発を行っており、GitHub で PullRequest を作成し、リリースに際しては、チームメンバーがレビューを必須にしています。リリース前に必ずレビューを行うことで、バグを未然に防ぐことはもちろん、設計方針やコーディングテクニックなどの知識の共有なども積極的に行っています。 自動化も積極的に行っており、テスト や PullRequest がマージされた際の Deploy などは、CircleCI により自動化されています。Sider は、rubocop によるコーディング規約のチェックや Rails Best Practices による自動レビューに用いています。 なお、開発に用いているハードウェアは、MacBook Pro 15-inch, Apple Magic Keyboard, Magic Trackpad もしくは Magic Mouse に、27インチ 4Kモニターとなっています。スタメンのオフィスは机が広く、肩こり対策でキーボードを2台並べているエンジニアが多く、やたらキーボードがたくさんあるオフィスになっています(笑 プロジェクト管理 ベストプラクティスを積極採用するスタメンですが、社内コミュニケーションにおいては少し違います。 スタメンでは、多くのITベンチャーが利用している Slack を利用しておらず、TUNAG の チャット機能で日々のコミュニケーションを行っています。 プロジェクト管理は、Trello によるカンバン方式を2週間単位のスクラムで行っており、 DocBase に仕様やマイルストーンなどを記載して社内共有と知識の蓄積を行っています。 Amazon Web Services (AWS) AWS の使い方は、Webアプリケーションのテンプレート的な構成となっています。EC2 上の Amazon Linux で nginx + puma の上で rails を動かしていますが、より効率的な運用を目指して夏にかけて AWS ECS を用いた構成に変更する予定です。 最近は、ログの蓄積と分析の基盤を準備中で、Kinesis Firehose、Glue、Athena といったサービスを用いて構築中です。これらのデータは、BI Tool の Metabase で分析したり、機械学習を用いたタイムライン等の最適化などに用いる予定です。 その他のサービス TUNAG では、主要な部分は AWS で運営されていますが、いくつかの分野ではより高機能なクラウドサービスを利用しています。 アプリケーションの監視とパフォーマンス・チューニングは CloudWatch に加えて、NewRelic を用いています。 Bugsnag も多様しています。Rails や JavaScript で 例外が発生した場合は、Bugsnag によって通知されますが、例外発生時 の 環境変数 や Stacktrace などの実行環境が保存されるため効率的なデバッグに欠かせないツールとなっています。 また、メールの配信には SendGrid、画像の配信には imgix を用いています。これらのサービスは、メールのバウンス処理や、画像のリアルタイム変換など便利な機能備え、開発リソースの節約につながるだけでなく、費用面でも効率が良く助かっています。 最後に Cloud Firestore ですが、TUNAG のチャット機能のネイティブアプリ向けのバックエンドとして利用しています。チャットは、利用頻度も高くスケーラビリティ及びパフォーマンスが非常に大切となります。FireStore を用いることで、AWS の S3 のように パフォーマンスとスケーラビリティをすぐに獲得でき、また、Cloud Firestore のネイティブ向けSDKに含まれるローカルキャッシュ機能により、データ通信量の削減やレスポンス向上にも寄与しており、今後 Cloud Firestore の利用頻度は向上しそうです。 まとめ ここまで読んでくださってありがとうございます! 今回、スタメンのエンジニアたちが作っている TUNAG について、技術的な側面からご紹介させていただきました。 TUNAG は、SNS、チャット、ワークフロー、制度管理(フォームビルダー) といった機能を備えた 大規模 Webアプリケーションで、Rails の他にも、React を用いたフロントエンド開発、Swift/Kotlinでの 向けネイティブアプリに加え、AWSを中心とした SRE にも取り組んでいます。 UI/UX、パフォーマンス、安定性、スケーラビリティ と 課題は山盛りで、当面飽きそうにありません。 また、TUNAG の導入社数は増え続けており、利用いただいているユーザ企業様からは多くの要望と期待を頂いています。ユーザーの皆さんの会社を良くすることに貢献できるのは、やりがいも社会的意義も感じます。 このように、スタメンの開発現場は、頼もしい仲間と、歯ごたえのある課題に向き合い、多くのユーザに使われる、大規模なアプリケーション開発に、没頭できる環境です。 これだけでも、それなりに幸せなことなのですが、もっと技術的なチャレンジをしてみたいし、もっと良いサービスにして、もっと多くの人に使っていただきたいと思ってます。何よりも、たくさんの頼もしい仲間を集めて、さらにすばらしい開発チームを作っていきたいと思っています。 ちょっとでも興味をもってくださったら、 株式会社スタメンの募集一覧 ご覧いただき、ぜひご応募いただけないでしょうか。
アバター
1. はじめに 明けましておめでとうございます。エンジニアのミツモトです。 年末は皆さんいかがお過ごしでしたでしょうか? 年末、私は家でゆっくりしながら、リーダブルコードを読んでいました。 リーダブルコード この本は私がスタメンに入ってから最初に勧められて読んだ本です。 スタメンがリーダブルコードを推奨している理由 スタメンはチームでの開発が前提になるため、誰もが読みやすいようにコーディングすることを大事にしています。そのため、人によって読みやすさの基準に差がでないよう、リーダブルコードを参考にしています。 あわせてRubocopも採用し、コーディングスタイルを統一しています。 リーダブルコードを読みながら、Webアプリケーションエンジニアとしての1年目を振り返ったので、今回は、この1年でコードの書き方がどう変わったかを紹介します。 2. 読みやすいコードを書く 変数・メソッドの 命名 に気を付ける 変数・メソッド名で処理の中身をわかりやすくするため、次のことをしています。 品詞の扱いを気をつける メソッド名で何が起きるのか/できるのかをわかりやすくするため 他でどういう 命名 がされてるか調べる プロダクト/チーム全体で用語を統一する Facebook など類似のプロダクトのURLや CSS の変数を参考にする 最初の頃は品詞を意識していなかったため、名詞・形容詞が入り混じっていました。 (最近でもどの単語にするか迷う) 昨年、社内で話題になったのは ghkw という コマンドライン ツールです。 github 上のキーワードを検索し、利用件数を知ることができます。複数のキーワードを同時に検索できるため、比較するのに便利です。 実装の長いメソッドを分ける メソッドが長くなると、コードが読みづらくなり、テストも複雑になります。 ユニットテスト が書きやすい粒度にコード量を保つことで、可読性が増え、不具合も減ります。 メソッドを分けるため、具体的には次のことをしています。 どういう処理を行っているか箇条書きにする。 箇条書きにした処理の中で、別メソッドとして切り出せるものは分ける。 別メソッドにすることが難しければ、メソッドの中で段落に分ける。 こうすることで、1つのメソッドの読みやすさが向上します。 それでも分かりづらい場合はコメントをつけます。本来は「コードのみで処理がわかる」状態が一番良いですが、他の人や未来の自分からすると、「一体何をしてるんだ?」となり得るものはコメントを付けるようにしています。 コメントを付ける際は、背景やメンテナンス時の注意事項など、あとで読む人に伝えたいことをシンプルに書くように気をつけています。 条件式がネストしていたら早めに return する 条件式がネストしていると、コードが読みづらくなります。 if post.present? if post.image? puts ' hoge1 ' else puts ' hoge2 ' end else puts ' hoge3 ' end 階層の深い条件式はスタックが多くなり、「最初の条件って何だっけ?」となります。 他の人にとっても読みづらくなるので、早めにreturnするのはオススメです。 return puts ' hoge3 ' if post.blank? if post.image? puts ' hoge1 ' else puts ' hoge2 ' end 3. Rails での設計の考え方 Model に対しての ロジックを View と Controller に持たせない スタメンはバックエンドに Ruby on Rails を採用しており、 MVC の アーキテクチャ がベースになります。 昔のコードを見返すと Model にあるべきロジックが Controller や View に出ていました。 Modelにロジックが定義されていないと、 Controller や View でコードが重複する可能性があり、DRY ( Don't repeat yourselfの略。コードの重複を防ぐプログラミングの原則 )から遠ざかります。 View でよくあるのが Model の状態に関連した条件式です。 if @post .present? && @post .image? && ( @post .image.width > 1024 || @post .image.height > 1024 ) # サムネイル表示する end こういう場合は Model の インスタンス メソッドにします。View は スマホ 版とPC版、メールなど、同じような内容で複数のテンプレートを実装することも多く、Modelにメソッドとして実装することでDRYになります。 class Post def has_large_image? image.present? && (image.width > 1024 || image.height > 1024 ) end end Controller でよくあるのが、 scope を使っていないパターンです。 @posts = Post .where( user : current_user, type : %i(image video) ) というような where 句を 昔は Controller に直書きしていました。 こういう時は Model に scopeを定義することで、他のController や View でも呼び出すことができます。 View と Controller は、 Model に定義したメソッドを最低限呼び出すだけにしましょう。(ただし、最近 FatModel になりつつあるので、 Model 内の整理も必要) private メソッドで カプセル化 する 昔は深く考えずに全て public メソッドで定義していたのですが、外部から参照してほしくないメソッドは private メソッドとして定義するようになりました。これによって外部から呼ばれないことが保証され、 リファクタリング がしやすくなります。 private メソッドは、それを用いる public メソッドのテストで動作が確認されるため、private にすることでテストを省くことができます。 validation でエラーを返す インスタンス として無効なのであれば、 インスタンス メソッド内でエラーハンドリングするのではなく、 validation としてエラーを返します。エラーメッセージも扱いやすいし、 validation をかけるタイミングも設定できます。 ActiveModel::Validator を継承した custom validator を使えば、 複数のモデルに共通した validation を DRY に実装することができます。 4. メンテナンス性を高める YAGNI だけど、拡張しやすく YAGNI とは、"You ain't gonna need it"の略で、「機能は実際に必要となるまでは追加しないのがよい」という エクストリーム・プログラミング における原則です。 新しい機能を実装するとき、「こういう機能も付加した方が良いのでは?」という話が出ます。要望に沿って付加機能を実装したとしても、使われなければ意味がありません。また、gemや他サービスでそれを補うものが出てくる可能性もあります。(実際に google drive の gem を扱ったとき、作り過ぎなくてよかった!ということがありました。) 作り過ぎは禁物ですが、予見できる要望を後で作りやすくするために設計段階に考慮しておくのは大事です。 DB のテーブル構成を考える時など、あとで修正する時にコストが高くつくものは拡張性を意識します。 コードの共 通化 既存の機能と似たものを作るとき、そのままコピーして部分的に変更するだけで、実装できるものがあります。ですが、これをするとロジックの修正が必要になった際に修正箇所が多くなり、メンテナンス性は大きく下がります。 スタメンに入ってからも何度かそういう場面がありました。最初の頃はコピーして実装することが多かったですが(スタメンの皆、すみません...。)、レビュー等で指摘を受け昨年の終わり頃から共 通化 を意識するようになりました。 Model と Controller は共通部分を Module に切り出すことで、次に機能を追加する時に楽になります。 View についても template を共 通化 します。 その瞬間は面倒だと思いますが、後で全部つくり直すより今やった方が良いです。 5. おわりに エンジニアになったばかりの人の参考になればと思い、今回の記事を書きました。 こうやって書き並べると、まだ十分にできてない部分があるため、今後もより良いコードを書けるよう頑張ろうと思います。2019年は Rails の基礎を固めつつ、フロントエンドにも幅を広げていきます。 スタメンではエンジニアを募集しています。もし興味がありましたら こちら からご連絡ください。 オフィスで、話ができるのを楽しみにお待ちしています。
アバター
こんにちは。桑原です。 2度目の投稿となります。 今回は TUNAG のフィードに埋め込まれている Markdown の仕組みについて解説します。 ※ ソースコード は部分的な公開になるため、ご了承ください。 前置き Markdown とは、テキストで HTML を表現するための マークアップ 言語です。 下記の画像のように、タイトルやプログラムコード、画像の埋め込みなど、多彩な表現が可能になります。 ところがこの Markdown 、一般的な記法はあれど、標準化・規格化されていないことをご存知でしょうか? エンジニアの間では、 Markdown への対応はまさに 地獄 と評されてきました。 何故 地獄 と言われているのか、 Markdown の仕組みと散々苦しんだ私の愚痴話をしたいと思います。 網羅するべき多くの記法 ざっとあげると下記の通りです。 文字の強調、イタリック、もしくはその両方 Strongタグ 取り消し線 ヘッダー(h5〜h1) 区切り線(4種類) HR区切り線(2種類) Codeタグ Blockquoteタグ 点リスト 数値リスト チェックボックス (チェック入り、無し) 絵文字 画像 リンク(URLそのまま、文字列にHrefのアタッチ) メール and 電話番号 改行 テキストに 正規表現 の走査・走査・走査... TUNAG ではフロントエンドの実装に React を採用しています。 Markdown での アルゴリズム では、テキストを 正規表現 で記法を検知し、 Markdown 用の React Component に置き換える、と言う方法で実装しています。 これを行う MarkdownParser の動きは下記の通りです。 Markdown の記法には大きく分けて下記の5パターン存在します。 太字のようなそれ単体で成立するもの Hタグのようにその記法の中でさらに別の記法が成立するもの Codeタグのように行をまたいで成立し、かつタグの中で別の記法が成立するもの 改行 1〜4のどれにも属さない文字列 これらのパターンに分けて、走査の アルゴリズム について説明します。 1. Delタグのようなそれ単体で成立するもの これは最も単純なパターンです。 該当する記法としては下記の通りです。 文字の装飾や取り消し線 HR区切り線 絵文字 画像 該当する文字列が存在する場合、下記の コンポーネント が実行されます。 decoratedText には該当した文字列が引数として与えられるため、この文字列を del タグでくくって DOM を return するだけです。 const Del = ( { decoratedText } ) => { const textRegExp = new RegExp (`(?: \\ ~) { 2,2 } ( [ ^ \\ x00 ] +?)(?: \\ ~) { 2,2 } `); const textMatch = decoratedText.match(textRegExp); if (!textMatch) { return null ; } return ( <del className= "markdown__del" > { textMatch [ 1 ] ? textMatch [ 1 ] : null } </del> ); } ; 2. Hタグのようにその記法の中でさらに別の記法が成立するもの このパターンの場合は 1. の応用で実現します。 decoratedText が該当する文字列になるため、これを更に 正規表現 (二重が許される記法)で走査する、 replaceToComponentsメソッド を利用しています。 replaceToComponentsメソッド により、該当する 正規表現 があれば コンポーネント に置き換わった戻り値が得られます。 const H1 = ( { decoratedText } ) => { const textRegExp = new RegExp (`# ( [ ^ \\ x00 ] *)`); const textMatch = decoratedText.match(textRegExp); if (!textMatch) { return null ; } return ( <del className= "markdown__h1" > { textMatch [ 1 ] ? replaceToComponents(textMatch [ 1 ] ) : null } </del> ); } ; 3. Codeタグのように行をまたいで成立し、かつタグの中で別の記法が成立するもの Markdown では画像のような行をまたいだ記法が存在します。 これに対応するため、改行コードを UTF-8 の \x00 に変換して、テキストすべてを1行の文字列に整えてから 正規表現 の走査を行います。 ※ \x00 を利用するのは、ユーザーのキーボード入力では入力ができないためです ※ 一般的に利用される 文字コード ではバグの原因となります 下記の 正規表現 は1行に変換された文字列から Blockquote の記法を検知するためのものです。 これを1行となったテキストに実行することで、 Blockquote の Component に置き換えます。 '> Blockquoteだよ> 二行目> > 段落1> > 段落2'.match(new RegExp(`((^|\\x00)(\\> )+(.*?)(\\x00|$)){1,}`)); しかしこのままでは Blockquote は一行の文字列になってしまうため、先ほど変換した \x00 を使って改行を行います。 Blockquote の場合は、 \x00 ごとに div タグでくくる処理を加えます。 そしてCodeタグの中でさらに別の記法が成立するため、2. と同様に二重で走査を行います。 4. 改行 マークダウンは複数改行を行なっても、一行に変換されるものが一般的ですが、 TUNAG では複数改行をそのまま有効にするように作られています。 これは単に走査された \x00 を コンポーネント に置き換えるだけの処理になります。 const Line = () => { return ( <span className= "markdown__line" /> ); } ; 5. 1〜4のどれにも属さない文字列 1〜4 のどれにも属さない文字列はそのまま span タグでくくっただけの コンポーネント に置き換えます。 const String = ( { decoratedText } ) => { return ( <span className= "markdown__str" > { decoratedText } </span> ); } ; Markdown だけじゃ足りない、更なる要望 マークダウンの実装だけではなく、下記のような要望を社内やユーザーさんから頂いたため、機能の拡張を行いました。 「...もっと見る」によるフィードの省略 「ナイスプレーしたメンバー」など、マークダウンでないパーツの埋め込み 複数改行をしたい まだこれだけでなく、たくさんの要望が積もっているため、随時対応していきたいと思います。 最後に いかがだったでしょうか。本当にざっくりでしたが、 Markdown の仕組みを解説しました。 しかし実際にはこの説明ほど単純ではなく、更に地獄が待ち受けているのが現実です。 👻 もし挑まれるなら心して掛かるように... 💀  
アバター
はじめに こんにちは! スタメンのシュール ( @shule517 ) です。 RuboCop って便利ですよね! 僕は Ruby を書き始めた頃に、「君の書いたコードは Ruby っぽくない。Rubocopを入れると良いよ!」とアド バイス をもらってから、ずっと愛用しています。Rubocopは Ruby の一般的な書き方を教えてくれたり、もっと効率の良い書き方を教えてくれるので、 Ruby を学ぶ中でとても頼りになりました。 そんな僕の Ruby の師匠(?)でもある Rubocop について紹介します! Rubocopとは? Rubocop は、 Ruby のコードをチェックしてくれるGemです。 上の例だとロケットハッシュ { :key => 1234 } は Ruby の古い構文なので、書き換えてね! と指摘されています。 Ruby のコーディングスタイルで有名な「 Ruby Style Guide 」を基準に、ルール違反した書き方をしていると指摘してくれます。また、「 Rails Style Guide 」を元に Rails に特化したルールまでチェックしてくれます。チェックしてくれるルールは、400以上あります。詳しくは 公式マニュアル で! Ruby を始めたばかりの人には、 Ruby の書き方を勉強するためのヒントに。プロジェクトで Ruby を使ってる人には、コーディング規約をプロジェクトに浸透させるために。 個人で開発している人にも、プロジェクトで使う人にも、おすすめです! Rubocopを導入してみる Gemをインストールする ターミナルで gem install rubocop を実行する。 Rubocopを実行してみる rubocop と実行すると、現在 ディレクト リ配下のコードをチェックしてくれます。 ハッシュの書き方がルールと違うよ!と4箇所指摘されています。 この方法だとコマンドを実行するのが手間なので、次はRubyMineと連携してみましょう! RubocopをRubyMineと連携しよう! RubyMineと連携すると、コードを書いた瞬間にRubocopの指摘が表示されるようになります。コマンドを手動で実行しなくても良く、その場でコードを修正できるので、とても便利です! RubyMineでRuboCopを有効にする RubyMineのPreferencesメニューを開きます。[Editor → Inspections] の Rubocop を有効にしてください。 RubyMineのエディタにコードを書くと、リアルタイムでRubocopの指摘が表示されるようになりました。 連携完了です! どんどんコードを書いていきましょう! Rubocopの指摘を自動修正しよう! AutoCorrect機能を使うと、指摘内容を自動で修正してくれます。この機能がかなり強力で、大半の指摘は自動修正が可能です。Rubocopを導入した時点で大量のエラーがあると、手動で修正するのはとても大変なので、自動修正していきましょう! rubocop -a ファイル名 で、指定したファイルの指摘内容を自動修正できます。 ※-a は、 --auto-correct の略です。 いろんな書き方のハッシュが全て同じ書き方に統一されました! ←が修正前 →が修正後のコードです。 さいごに 最後まで読んでいただいて、ありがとうございました! 次は、実際のプロジェクトにRubocopを導入した時の体験談について書こうと思います! プロジェクトにRubocopを導入した時点では、大量のエラーが出てビビリました。。。 プロジェクトにRubocopを浸透させていくまでの方針や、工夫。 Sider の導入について書こうと思います! 次回もお楽しみに〜!
アバター
1. はじめに こんにちは。エンジニアのミツモトです。 普段は TUNAG という 社内SNS サービスのバックエンドを担当しています。 フロントエンドの業務も行いますが、触れる機会が少なく、たまには動くものを作ってみたい!と思い、今回は SVG でloaderを作りました。 2. つくったもの See the Pen svg loader by mitsumoto ( @mmoto ) on CodePen . 3. SVG とは Scalable Vector Graphicsの略 ベクター 画像( JPEG や PNG などのラスター画像と異なり、伸縮しても画質が劣化しない)を XML で記述するための言語 W3C ( World Wide Web Consortium)により勧告 図形(ex. 直線・曲線・円)、画像、 テキストを扱い、それらのオブジェクトをグループにまとめたり、スタイリングできる onmouseover や onclick などの多彩な イベントハンドラ を、任意の SVG オブジェクトにあてることができる Scalable Vector Graphics (SVG) 1.1 (第2版)概要 Can I Use どのブラウザでも、最新のversionであれば SVG に対応しています。 4. 図形を描画する 4-1. SVG タグ svg タグは、他の XML である XHTML やMathMLタグとの名前の衝突を避けるため、 xmlns="http://www.w3.org/2000/svg" と記述します。 これにより 名前空間 を指定し、自分と子要素が svg であることを示します。( HTML5 では省略できます。) SVG プロパティ 説明 width 描画領域の幅 height 描画領域の高さ viewBox "x y width height" x:表示領域の左上角 x座標 y:表示領域の左上角 y座標 width:(描画領域に対する)表示領域の相対的な幅 height:(描画領域に対する)表示領域の相対的な高さ viewBoxの設定は分かりづらいため、今回は表示領域の位置を(x,y)=(0,0)とし、描画領域と表示領域のサイズを合わせます。 数値に単位がない場合、 SVG ではpxになります。 4-2. 線の描画 線の起点と終点を座標で指定し、描画します。 See the Pen line by mitsumoto ( @mmoto ) on CodePen . lineプロパティ 説明 x1 起点のx座標 y1 起点のy座標 x2 終点のx座標 y2 終点のy座標 stroke 線色の指定 4-3. 矩形の描画 矩形の幅や高さ、座標、角の丸みを指定して描画します。 See the Pen rect by mitsumoto ( @mmoto ) on CodePen . rectプロパティ 説明 width 幅 height 高さ x 左上角 x座標 y 左上角 y座標 rx 角のx方向丸み ry 角のy方向丸み fill 塗りつぶし色の指定 4-4. 円の描画 See the Pen circle by mitsumoto ( @mmoto ) on CodePen . circleプロパティ 説明 cx 円中心 x座標 cy 円中心 y座標 rx 円の半径 4-5. パスの描画 指定した座標を順番に線で結び、描画します。 See the Pen path by mitsumoto ( @mmoto ) on CodePen . pathコマンド 説明 M x,y パスの起点 座標 h x 水平方向にx座標まで直線を描画 v x 鉛直方向にy座標まで直線を描画 l x,y x,y座標まで直線を描画 c x1,y1 x2,y2 x,y 3次 ベジェ曲線 を描画 x1,y1:曲線の起点に対する制御点の座標 x2,y2:曲線の終点に対する制御点の座標 x,y:終点の座標 コマンドは大文字だと絶対座標、小文字だと1つ前の座標からの相対座標になります。 上記以外にも、 ベジェ曲線 を描く q , t , sコマンドや、円弧を描く a といったコマンドがあります。 5. loaderをつくる 「2. つくったもの」の内、右側のドミノ倒しのようなloaderのつくり方を説明します。 はじめに矩形を描画します。 See the Pen domino1 by mitsumoto ( @mmoto ) on CodePen . これにアニメーションをつけていきます。 まず水平方向に移動させます。 animate attributeName="x" values="10;100" dur="1.2s" begin="0.8s" repeatCount="indefinite" animateプロパティ 説明 attributeName アニメーションの対象属性 values 変化させる属性値( ; で区切ります) dur アニメーションの1ループにかける時間 begin アニメーションの開始時間 repeatCount 繰り返し回数(indefinite:無限に繰り返し) 0.8sec経過後にアニメーションが始まり、1.2secかけて水平方向にx=10から100へ移動するアニメーションが繰り返されます。 See the Pen domino2 by mitsumoto ( @mmoto ) on CodePen . 次に矩形を回転させ、不透明度を変化させます。 animateTransform attributeName="transform" type="rotate" from="0 10 70" to="-60 100 70" dur="1.2s" begin="0.8s" repeatCount="indefinite" attributeName="opacity" values="0;1;0" dur="1.2s" begin="0.8s" repeatCount="indefinite" animateTransformプロパティ 説明 type 変化の種類 from アニメーションの開始時の属性値 type:rotateの属性値(d, x, y) d: 回転角 x: 回転の中心点x座標 y: 回転の中心点y座標 to アニメーションの終了時の属性値 不透明度はattributeNameプロパティを"opacity"とし、valuesを0〜1の間で指定します。 See the Pen domino3 by mitsumoto ( @mmoto ) on CodePen . 最後にアニメーションの開始時間をずらして複数表示させることで、ドミノが倒れているようなアニメーションにします。 See the Pen domino4 by mitsumoto ( @mmoto ) on CodePen . 6. おわりに SVG を用いてloaderをつくりました。細かな部分へのこだわりがサービスへの愛着に繋がると思うので、デザインには一層気を配りたいと思います。スタメンではエンジニア、デザイナーを募集しています。興味がある方は こちら をご覧ください。
アバター
こんにちは。スタメンの 小林 ( @lifework_tech )です。 スタメンの社内勉強会で障害対応について話しましたので、資料を公開させていただきます。 障害対応への考え方と備え どんなに対策を行っていても障害は必ず発生します。 地震 のようにいつ発生するのかはわかりません。でも、予め障害発生を想定して、準備しておくと、影響の低減、復旧までの時間短縮など多くのメリットがあります。 障害に向き合い再発防止策を積み重ねることで、システムもチームも強くなることを目指し、普段意識していることをまとめてみました。 障害の検知から対応まで 障害を把握したら まずは落ち着く 障害を知ったらまずは深呼吸して落ち着きましょう。慌てて対応してもいいことは何もありません。 役割を分担する 原因調査をしつつ、応急処置を行い、社内に連絡するなど、障害対応は同時並行で進みます。また、普段の デバッグ と一緒で、一人よりも、誰かと話しながら対応すると、スムーズに進むものです。 まずはチームメンバーに連絡を取り障害対応を分担できるようにしましょう。 原因を把握する アラートや社内からの報告など、障害を把握することになった情報があるはずです。まずは、その情報を起点として何が起きているかを把握することが大事です。 このときアラート、ログを精査することが多いと思いますが、「原因」と「現象」を意識することが大事です。 例えば、 AWS の CloudWatch ALARM を用いて監視を行っている場合、Elastic Load Balancing (ELB) の HTTP応答の Latency が しきい値 を超えたというアラートによって、障害発生を知ることがあります。この場合、何らかの原因があって、ELB の遅延が発生しているのであって、現象としての負荷に対応しようとして、サーバーを増設しても「原因」は解消されません。 また、 MySQL への接続数が上限に達したときに発生する Too many connections の場合、 Rails 等の クライアント から 新たに接続できないのは「現象」であって、滞留しているクエリなど接続数を増やす「原因」は別にあります。 なので、「この現象を起こしている要因は何か?」と考えながら、ログやアラートを見ると良いです。 また、それぞれのログには非常に重要な情報が含まれています。なんとなく流し読みするのではなく、一つ一つメッセージ (エラー) の意味を理解しながら調査してください。 加えて、時系列にメモを取りながら調査すると、状況の整理、チームへの共有、障害報告への記載などに役立ちます。 テキストエディタ でも良いですし、Slack 等のチャットへ貼っていくのでも良いでしょう。 こういうときに、 MySQL や bash / zsh の プロンプトに現在時刻が入っていると、出力結果をコピーするだけで時刻も記載されるため便利です。また、iTerm2 等 の Terminal Scrollback Buffer を 大きな数字にしておくと、スクロールすれば履歴を追えるため便利です。 社内と顧客への共有 障害対応に集中するあまり、社内やユーザーへの共有が疎かになることがあります。 障害対応中は、常にユーザーへの悪影響が発生します。何が起きているかわからない状況ではストレスは強くなります。障害が続くようであればユーザー向けの告知を検討しましょう。 このとき、社内のサポートチームへ告知をお願いできると、技術的な障害対応に専念できます。エンジニア以外でも告知を掲載できるツールを用意しておくと良いです。 社内への共有について、顧客対応に加えて、 Google や Facebook 等へ広告を出稿している場合は、広告出稿を止めることで余計な出費を抑えることができます。 たまたま、障害発生 と Google の クローラー の巡回が重なって、一時的に検索順位がさがることもあるかもしれません。エンジニアが想定していない影響がある場合もあります。社内共有はしっかり行いましょう。 復旧作業 障害の要因がわかり、障害が継続している(しそうな)場合は、復旧作業をすることになります。原因よって対応は異なりますので、ここでは個別ケースでの対応方法は割愛し、注意すべき点について記載します。 復旧作業をする際には、障害によってデータの不整合が発生していないか、または、応急処置によって不整合が新たに発生しないかを必ず確認してください。特に、 MySQL 等のDBで、クエリを 中断(Kill) するとき、 Sidekiq/DelayedJob Jobを中断するときは、そのオペレーションによって何が起きるのかを把握してから実行すべきです。 不用意な中断によって、database の破損 や データ不整合などが起きることはよくあります。障害時に焦ってオペレーションしたばかりに、さらに大きな障害を招いた例は、たくさん経験してきました。 障害対応時のオペレーションは、普段よりもミスしやすいです。まずは落ち着いて、誰かに目視で確認してもらったり、できる限り実行前にバックアップを取るようにしてください。 障害収束後にすること 顧客向け障害報告の掲載 ユーザーからのお問い合わせへの返信、サイトへの告知の掲載など、お客様に対して障害の報告とお詫びを掲載します。どれくらい詳細に記載するかは、障害の範囲、時間、影響など多くの要素が絡みます。ユーザー向けサポートを担当している部門と連携して文案を作成して掲載しましょう。 この際、過去の掲載内容が履歴として残っているとスムーズです。 社内向け障害報告の提出 障害から時間が経つと記憶も曖昧になるし、危機感も薄れます。障害報告は、当日か遅くても翌日には提出するようにしましょう。 社内の過去の障害報告を雛形にして記載すると良いです。可能ならドキュメントツールのテンプレートにしておきましょう。 障害報告を記載する際は、誰に対して報告するのかを明確にしましょう。誰に報告するかで、影響範囲や原因などの説明内容が変わります。以前勤めていた会社で、エンジニアしかわからない専門用語ばかりの障害報告を全社向けにメールして、叱られたこともありました。 スタメンの障害報告フォーマットは下記です。 スタメンでは、障害発生時から対応完了まで 社内のチャット で状況を随時共有しながら障害対応をしているため、障害報告は、エンジニアチーム内での履歴として作成しており、社内向けには「概要」に記載する程度にしています。 ## 報告 [年月日] [報告者]  (誰がいつ報告したか) ## 概要 (発生した障害についての概要。エンジニア以外にもわかりやすく簡潔に) ## 対応履歴 (障害発生、検知、対応、収束までの履歴を時系列に箇条書きで) ## 顧客対応 (顧客への連絡 や 影響に応じた対応など) ## 発生原因 (障害が発生した原因について) ## 再発防止 (再発防止策とタスクリスト) ## 関連コミット (障害対応時のコミット、再発防止策のコミットへのリンク) ## 参考資料 (障害対応や再発防止実施時に参考にしたドキュメント等へのリンク) 障害報告で重要なのは、再発防止 です。 障害の原因となったことへの対策はもちろんですが、間接的な要因となった事象についても再発防止を行うこと大切です。 直接的な原因への対策 不具合の理由となったコードを修正する、 アクセス障害の原因となったサーバーを入れ替えるなど、発生原因に対しての対応です。 間接的な原因への対策 不具合であれば、なぜテストで検知・予防できなかったのか。過負荷によるシステムダウンであれば、設計上の課題や、ステージング環境での検証で事前に把握できなかったのか? など。 また、再発防止策として悪い例としては、下記があります。 気をつけます。 ダブルチェックします。 マニュアルに記載します。 エンジニアは、コードによって、再現性、確実性のある再発防止策を実施できるのが強みです。 気をつけます という気持ちも大事ですし、ダブルチェック もときには必要ですが、どちらも再現性も、確実性はありませんし、マニュアルについては読まれるとは限りません (たいてい忘れ去られる)。 事前の準備 障害は突然やってきますし、素早い判断や対応を求められるため、事前に準備(備え)ておくことが大切です。 システム構成の把握 今からホワイトボードにシステム構成図をかけますか? ログの場所は把握していますか? CloudWatch 等での監視 異常を検知できるような仕組みになっていますか? 異常が起きる前の状態も監視していますか? データのバックアップ 大切なデータが失われないように定期的にバックアップが取得されていますか? DB はもちろん、画像など失われるデータはないでしょうか? コマンドライン に慣れておく mysql ps, top, tail, grep などの代表的なコマンドとそのオプションを把握しておく。 連絡先の把握 休日や夜間に、助けを求められるように連絡先をしっていますか? Slack や LINE だと夜間通知が抑制されるときがあるため、電話できると良いです。 まとめ はじめから障害対応できる人はいません。みなさん、小さな経験を積み上げて、 スキルアップ していくものです。ですので、障害に遭遇したらいい経験が詰めるチャンスだと思って、飛び込んでみてください。 先輩がテキパキ障害対応しているときに、飛び込む勇気がないときがあるかもしれません。そのときは「できることないですか?」と聞いても良いでしょう。 過去に、集中して対応しているときに社内調整や告知掲載を引き受けてくれて助かったこともありますし、ランチ前に障害が起きて対応しているときにおにぎりの差し入れをもらって一息つけたこともありました。 私が新人だったころ、障害対応後に喫煙ルームにいって一服している上司を追いかけて、何を考えながら対応していたのか質問していました。また、上司の shell の history を見て、 トラブルシューティング 方法を学んでいました。 今晩、一人アラートに気づくかもしれません。そのときに、何かしらの対応ができるように今日から準備していきましょう。
アバター
こんにちは、スタメンの松谷( @uuushiro )です。 「 Rails スタートアップがやってよかったこと」 というタイトルで、 銀座Rails#1 で発表してきました。その時の資料を公開します。 発表内容 https://speakerdeck.com/uuushiro/yin-zuo-rails-number-1-uuushiro スタメンが創業してから約2年間のやってよかったことをまとめているので、以下のような内容に興味のある方はぜひスライドをご参照ください。 入れてよかったgem aasm (オブジェクトの状態遷移管理が簡単にできる) awesome _nested_tree ( ActiveRecord でツリー構造を扱える) elasticsearch-model / elasticsearch- dsl (Elasticsearchを Rails で簡単に扱える) xray- rails (ブラウザ上でviewの構造を可視化してくれる) delayed_job / sidekiq (非同期処理) 使ってよかったツール・サービス imgix (画像専用 CDN ) Bugsnag (クラッシュレポート管理ツール) SendGrid (メールデリバリーサービス) Mailtrap (ダミー SMTP サーバー) Metabase (BIツール) 取り入れてよかった開発スタイル 定期bundle update レビューの自動化 発表の背景 今回、私が発表しようと思った理由としては2つあります。 1つ目の理由は、TUNAGの開発には Rails やその他多くのgem使っていてますが、単に使うだけでなくフィードバックをして、 Rails コミュニティの発展に少しでも貢献したかったからです。スタメンが創業した2016年に、「 Rails ベストプ ラク ティス」のような内容の資料を参考にさせて頂き、とても助かりました。今回公開する発表資料で、どなたかの2018年版の Rails 技術選定の参考になれば幸いです。 2つ目の理由は、技術的な知見を発表することで、名古屋に閉じがちな弊社も、社外の多くの Rails エンジニアからフィードバックを得ることができ、それをもとに技術力の向上に繋がるのではないかと考えたからです。実際、発表中にも Twitter の ハッシュタグ で沢山のフィードバックを頂きました。ありがとうございました。懇親会でも多くの Rails エンジニアの方々と技術ノウハウの交換ができ、名古屋ではなかなかできない充実した時間を過ごせました。 まとめ スタメンとして初めて東京の技術イベントで発表させていただいたのですが、想像以上にその場の空気や熱、そこで出会う人たちとの交流が魅力的で、本当に楽しかったです。これからどんどん東京のイベントにも参加したいと思います。このようなきっかけを作ってくださった運営スタッフの皆様、ありがとうございました。
アバター
こんにちは。スタメンでデザイナー兼フロントエンドエンジニアをしているナガキです。 弊社が提供している「TUNAG」 には(オプションで)ビジネスチャット機能があります。 App版チャット PC版チャット ご覧のように、 スマホ アプリ版は非常にシンプルな画面構成ですが、PC版はひとつの画面内に様々な情報を表示する必要があり、どうしても複雑になりがちです。 そんな複雑で大量の情報を CSS の Flexbox を利用してレイアウトしています。 今回は「TUNAG」PC版チャットを例に、 Flexbox の便利な使い方をご紹介します。 `Flexbox` とは Flexible Box Layout Module のことで、 Flex Container と呼ばれる親要素と Flex Item と呼ばれる子要素を使って、柔軟なレイアウトが実現できます。 CSS Flexible Box Layout - MDN web docs 仕様が何度も変わったり、ブラウザが対応していないなどの問題もありましたが、現在ではほとんどすべてのモダンブラウザで安心して利用することができます。 Can I use 最近徐々に利用が広がっている Grid Layout は2次元のレイアウトを定義しますが、 Flexbox は1次元、つまり横か縦、どちらか一方向だけのレイアウト(並びや余白)を定義します。 これだけ聞くと Grid Layout のほうが便利なように思えますが、 Flexbox をうまく活用することで、その名のとおりフレキシブルなレイアウトが実現できます。 それでは早速、実際のチャット画面を参考にしながら、どのように Flexbox を利用しているか説明していきましょう。 固定幅 + 可変幅の横方向カラムレイアウト PC版チャットのベースレイアウトは 位置 コンテンツ 横幅 ウィンドウサイズによる拡縮 左 ルーム一覧 固定 なし 中央 選択中ルームのメッセージ一覧 可変 あり 右 選択中ルームに関する情報 固定 なし となっており、左右のカラムが固定幅、中央のメッセージ一覧部分だけがウィンドウ幅にあわせて変わります。 これを CSS で実装しようとすると以下のようになります。 See the Pen flexbox sample 1 by ngk works ( @ngk-works ) on CodePen . ` flex ` プロパティでカラムの拡縮をコン トロール する ポイントとなるのは子要素の flex プロパティ。これでカラム幅の拡縮の有無( flex-grow , flex-shrink )や基準幅( flex-basis )をまとめて設定しています。 flex: flex-grow flex-shrink flex-basis; サンプルでは左右のカラムを 100px の拡縮しない要素とすることで、中央のカラム幅だけがウィンドウ幅に応じて変わるようになります。 このように Flexbox を利用することで、複数カラムのレイアウトが簡単に実現できました。 固定幅 + 可変幅の縦方向カラムレイアウト Flexbox は横方向だけでなく、縦方向にも利用できます。 例えば、左カラムにあるチャットルーム一覧部分のレイアウトを見てみると、 コンテンツ 高さ ウィンドウサイズによる拡縮 スクロール ヘッダ(タイトル) 固定 なし なし 検索フォーム 可変(なりゆき) なし なし ルーム一覧 可変(なりゆき) あり あり となっています。 先程のベースレイアウトと比べると、 コンテンツが並ぶ方向が縦 拡縮はしないが、コンテンツの内容によって幅(高さ)が決まるコンテンツがある ウィンドウ幅やコンテンツの内容によって幅(高さ)が変わる という違いがありますが、これも Flexbox であれば簡単に実装できます。 See the Pen flexbox sample 2 by ngk works ( @ngk-works ) on CodePen . ` flex -direction` で並べる方向を指定する flex-direction を column に指定するだけで Flex Item を縦方向に並べることができます。 また、幅をコンテンツ内容に合わせたい場合は flex-basis に auto を指定します。 .flex-container--column { display : flex; flex-flow: column nowrap ; // 縦方向に並べる } .component-header { flex: 0 0 60px ; // 拡縮なし / 固定幅 } .search-form { flex: 0 0 auto ; // 拡縮なし / コンテンツに応じた幅 } 縦方向に並べる場合に気をつけなければならないのは、ウィンドウの高さとコンテンツ量の関係です。 コンテンツ量がそれほど多くなく、ウィンドウ内に収まる場合は問題ありませんが、ウィンドウの高さを超える量のコンテンツがある場合は注意が必要です。 「TUNAG」のチャットでは1画面内にすべての情報を収める必要があるため、チャットルーム一覧部分が画面外に広がっていかないよう .room-list { overflow-y : auto ; } を指定することで、状況に応じてリスト部分だけをスクロールさせています。 サンプルのウィンドウサイズを変えたり、リストアイテムの数を増減させたりして挙動を確認してみてください。 `Flexbox` を利用して、思い通りのレイアウトを実現する 右カラムにはチャットルームの様々な情報が表示されています。 コンテンツ 高さ ウィンドウサイズによる拡縮 スクロール 配置 ルームアイコン 固定 なし なし 上 メンバー一覧 可変(なりゆき) あり あり 上 ファイル一覧 可変(なりゆき) なし なし 下 (※ 各コンテンツには、指定幅のヘッダ(タイトル)と実コンテンツが含まれます) チャットルーム一覧とは 下寄せで配置される コンポーネント がある 大きさの違う複数個の コンポーネント が混在する という違いがあります。 flex プロパティを利用してカラム位置をコン トロール する コンポーネント の下寄せ配置は、拡縮可能なコンテンツを利用することで実現します。 See the Pen flexbox sample 3 by ngk works ( @ngk-works ) on CodePen . 上記のサンプルでは .members { flex: 1 1 auto ; } により、中央のメンバー一覧部分がウィンドウ領域を埋めるように拡縮するため、ファイル一覧が最低限の高さを確保した状態で、最下部に押しやられます。 `margin` プロパティを利用してカラム位置をコン トロール する また別の手段として、 margin を利用する方法もあります。 例えば、下記のようなメンバー一覧部分が拡縮しない場合であっても、 .members { flex: 0 0 auto ; } margin を利用することで、下寄せ配置が実現できます。 .files { flex: 0 0 auto ; margin-top : auto ; } Flex Container をネストすることでスクロールをコン トロール する 「TUNAG」PC版チャットでは、コンテンツの高さがウィンドウの高さを超えた場合にメンバー一覧だけをスクロールさせる必要がありました。 See the Pen flexbox sample 4 by ngk works ( @ngk-works ) on CodePen . ポイントとなるのは、 .members が .flex-container--column の Flex Item であり、 .member-list の Flex Container でもある点です( .files も同様)。 Flexbox は Flex Container をネスト( 入れ子 構造)することができるため、うまく利用すると、 拡縮の可否 スクロールの有無 配置(寄せ) などを自由自在に組み合わせることができます。 サンプルでは、メンバー一覧のリスト部分だけを拡縮・スクロール可能な要素とすることで、1画面に収めつつ、状況に応じてスクロールバーを表示しています。 `Flexbox` で柔軟なレイアウトを 冒頭に `Grid Layout` のほうが便利なように思えますが、 `Flexbox` をうまく活用することで、その名のとおりフレキシブルなレイアウトが実現できます といった理由が、前項の 自由自在な組み合わせ にあります。 Grid Layout は ピクセル や割り合いを事前に定義する必要があり、(縦/横)幅が異なるコンテナが混在する場合はレイアウトが難しくなってしまいますが、 Flexbox であれば、 ピクセル や割り合いを事前に定義することはもちろん、コンテンツに応じた大きさにもできます。 それらを柔軟に組み合わせることができるため、非常に自由度が高いレイアウトが実現できます。 See the Pen flexbox sample 5 by ngk works ( @ngk-works ) on CodePen . レイアウトの実装に困ったら、ぜひ Flexbox を使ってみてください。
アバター