株式会社RevCommのブログ - TECH PLAY

TECH PLAY

株式会社RevComm

株式会社RevComm の技術ブログ

185

この記事は、RevComm Advent Calender 19 日目の記事です。 はじめに TL;DR CDK for Terraform とは 特徴 技術比較 実践 前提条件 環境構築 resources 作成 Deploy GCP認証 deploy内容の確認 終わりに はじめに こんにちは、株式会社 RevComm でフロントエンドチームでエンジニアをしている高橋( @katakana_33 )と申します。 今回は、私が RevComm に入って初めて見たソースコードが Terraform だった事と関連して、CDK for Terraform と GCP の連携について、実践した使用感を書きました。 RevComm の良さの1つとして、GitHub Organization 内の他プロダクトのリポジトリを見れる事でプロダクト全体の理解を深め、自身の専門職種外の技術スキルにも目を向けられる点があります。 RevCommのオンボーディング期間のその日のオンボードが終了した後 、私が初めて見たソースコードが担当するプロダクトの Terraform でした。未経験の技術だったこともあり、そこで今回は、CDK for TerraformとGCPの連携について調査と実践をしました。 TL;DR 0 から IaC を導入する場合は、メリットの方が大きい。 移行コストと導入必要性を考慮した際、移行しない事も選択肢の 1 つ。 CDK for Terraform とは CDK for Terraform by Hashicorp AWS CDKチームとHashicorp 社が共同開発 をしたツールで、 AWS CDK と Terraform の良い所を合わせたものです。 AWS CDK の開発者 kit を使用して、インフラリソースの定義とデプロイを行えます。 公式より以下概念図 特徴 HCL の学習を必要とせず、普段使用しているメジャーな言語で IaC を記述できる。以下がサポートされているプログラミング言語。 TypeScript、Python、Java、C#、Go。(2022 年 12 月時点) Terraform エコシステム全体にアクセスできて、各機能のテストを行える。 マルチプロバイダが可能な為、ベンダーロックインを回避できる。 技術比較 Terraform CDK CDK for Terraform HCLの学習 必要 不要 不要 マルチプロバイダ 可能 不可能(AWSのみ) 可能 参考文献(有志の記事を含む) 多い 多い 少ない(有志の記事が少ない、イレギュラーケースがまだ明るみになっていない可能性が高い) 実践 今回 は、web サービスでよく使いそうな GCP の以下の resources(本記事ではcloud infrastructure resources を指します。) を作成し deploy します。 Compute Network Compute SubNetwork Compute Disk IP address 前提条件 あらかじめ以下の環境を用意してください Terraform CLI Node.jsおよび npm v16 gcloud ( install 方法 ) GCP project( project の作成方法 ) Typescript 実行環境 環境構築 上記の環境を構築したら、CDK for Terraform 独自の project を構築します npm install --global cdktf-cli@latest // cdktf package install cdktf help // check install mkdir learn-cdktf && cd learn-cdktf // create workingDirectory cdktf init --template=typescript --local // create project(入力すると以下質問が出る) ? Project Name // 任意の名前 ? Project Description // 任意の説明 ? Do you want to start from an existing Terraform project? (デフォルトNo) ? Do you want to send crash reports to the CDKTF team? (デフォルトYes) ? What providers do you want to use? google 質問に答えると、以下の構成ファイル群が生成されます。 . ├── __tests__ ├── cdktf.json ├── help ├── jest.config.js ├── main.ts ├── package-lock.json ├── package.json ├── setup.js └── tsconfig.json GCP provider を使えるようにするには、このファイル群があるディレクトリにて以下のcommand を実行します。(GCP 以外の各種 provider が欲しい際は公式を参照ください。) npm install @cdktf/provider-google これで CDK for Terraform × Typescript × GCP の環境が構築できました。 resources 作成 構築する provider resources は、ルートディレクトリ直下の main.ts の Stack( Hashicorp:Stacks )に記述します。 以下は初期状態です。 TerraformStack を継承した MyStack が自動生成されています。 // Copyright (c) HashiCorp, Inc // SPDX-License-Identifier: MPL-2.0 import { Construct } from "constructs" ; import { App , TerraformStack } from "cdktf" ; class MyStack extends TerraformStack { constructor( scope: Construct , id: string ) { super( scope , id ); // define resources here } } const app = new App (); new MyStack ( app , "project-name" ); // 環境構築時に質問で回答したproject名がここに入る app.synth (); MyStack の constructor の中で provider の instance を生成します。 config 情報の project の value には、実存する GCP project 名を記入ください。 class MyStack extends TerraformStack { constructor( scope: Construct , id: string ) { super( scope , id ); new GoogleProvider ( this , "google" , { project: "project-name" , } ) // define resources here } } project 名を正確に書かずに deploy をした場合は以下の error がでます。 (project 名を hoge として deploy した結果) │ Error: Error creating Disk: googleapi: Error 400: Consumer 'projects/hoge' is invalid: Resource projects/hoge could not be found.. provider instance を作成したら、任意の resources を書きます。 今回は以下ソースコードの resources を作成し deploy します。 resources は constructor 内に定義します。 使用するGCP resourcesの使用方法は、公式リファレンスには殆ど記載がないので、 以下の GitHub のソースコードから使用する resources を import して、各 resources に必要な key と value を渡します。 GitHub: cdktf-provider-google class MyStack extends TerraformStack { constructor( scope: Construct , id: string ) { super( scope , id ); // define resources here const ZONE = "asia-northeast1-b" ; const PROJECT_ID = "katakana-id" ; // 任意のID 各resourcesに付与する const PROJECT_NAME = "katakana-tr" ; // GCP projectの名前 const PROJECT_PREFIX = "example" ; const PROJECT_PREFIX_NAME = ` ${ PROJECT_PREFIX } - ${ PROJECT_NAME } ` ; const PROJECT_PREFIX_ID = ` ${ PROJECT_PREFIX } - ${ PROJECT_ID } ` ; new GoogleProvider ( this , "google" , { project: PROJECT_NAME , } ) const network = new ComputeNetwork ( this , ` ${ PROJECT_PREFIX_ID } -network` , { name: ` ${ PROJECT_PREFIX_NAME } -network` , routingMode: "REGIONAL" , autoCreateSubnetworks: false , } ) const subnetwork = new ComputeSubnetwork ( this , ` ${ PROJECT_PREFIX_ID } -subnetwork` , { name: ` ${ PROJECT_PREFIX_NAME } -subnetwork` , network: network.selfLink , ipCidrRange: "10.10.0.0/16" , region: "asia-northeast1" , } ); const address = new ComputeAddress ( this , ` ${ PROJECT_PREFIX_ID } -address` , { name: ` ${ PROJECT_PREFIX_NAME } -address` , subnetwork: subnetwork.selfLink , region: subnetwork.region , addressType: "INTERNAL" , } ); const disk = new ComputeDisk ( this , ` ${ PROJECT_PREFIX_ID } -disk` , { name: ` ${ PROJECT_PREFIX_NAME } -disk` , zone: ZONE , type : "pd-standard" , size: 10 , image: "ubuntu-os-cloud/ubuntu-2204-lts" , } ); new ComputeInstance ( this , ` ${ PROJECT_PREFIX_ID } -instance` , { name: ` ${ PROJECT_PREFIX_NAME } -instance` , zone: ZONE , machineType: "e2-medium" , bootDisk: { autoDelete: false , source: disk.selfLink } , networkInterface: [ { networkIp: address.address , subnetwork: subnetwork.selfLink , } ] , canIpForward: false , tags: [ "iap" ] } ); } } const app = new App (); new MyStack ( app , "dev" ); new MyStack ( app , "stg" ); new MyStack ( app , "prod" ); app.synth (); Stack を 環境毎に初期化することで、同じ宣言内容で個別の deploy が可能となります。 Deploy 最後に deploy です。 手順は以下のとおりです。 GCP 認証・deploy 内容の確認 対象を指定して deploy GCP認証 以下のコマンドで GCP への認証をします。 gcloud auth application-default login GoogleProviderClass の config という引数の credentials という入力用変数でも認証を行うことができますが、今回は楽に認証が行えるコマンド認証を使っています。 deploy内容の確認 cdktf plan コマンドで確認します。出力内容を全てを載せると量が多くなる為、 以下は、google_compute_instance の deploy 内容です。 cdktf plan create-disk # google_compute_instance.example-katakana-id-instance (example-katakana-id-instance) will be created + resource "google_compute_instance" "example-katakana-id-instance" { + can_ip_forward = false + cpu_platform = (known after apply) + current_status = (known after apply) + deletion_protection = false + guest_accelerator = (known after apply) + id = (known after apply) + instance_id = (known after apply) + label_fingerprint = (known after apply) + machine_type = "e2-medium" + metadata_fingerprint = (known after apply) + min_cpu_platform = (known after apply) + name = "example-katakana-tr-instance" + project = (known after apply) + self_link = (known after apply) + tags = [ + "iap", ] + tags_fingerprint = (known after apply) + zone 省略 対象を指定し deploy 複数の stack がある場合は、stack 名を指定しないと以下の error になります。 cdktf plan (stack 名) Usage Error: Found more than one stack, please specify a target stack. Run cdktf deploy <stack> with one of these stacks: (stack名), (stack名2) deploy 対象に間違いなければ deploy します。 この場合も、複数 stack がある場合は、指定します。 deploy 時、以下の3つの選択肢があります、Approve をすると deploy されます。 cdktf deploy (stack名) Approve Dismiss Stop error が無ければ、cdktf plan で出力された内容が出力され、 最後に以下の出力が表示されて、deploy 完了です。 Apply complete! Resources: 5 added, 0 changed, 0 destroyed. 画像は、私の個人環境にて、今回 deploy された resources になります。 これで、GCP の Network、SubNetwork、IP address、 Disk が作成できました。 GCP deploy 終わりに 今回の実践にあたり、 「CDK for Terraform × Typescript × GCP」で IaC と deploy を行った使用感は以下となります。 Terraform の利点を享受できたこと。(環境構築、diff、deploy のしやすさ。) Typescript フレンドリーに package の import、provider resources の実装ができる。 各 provider で使用する resources class を把握すれば、ESLint 等の Lint を入れてる人は恩恵がわかりやすく、言語補完が効くのでスラスラ書ける。 変数や関数の再利用が書きやすく、読みやすい。 私が個人開発をする際は、CDK for Terraform を使うと思います。 RevComm では一緒に働く仲間を募集しています。 このブログを読んで興味を持ってくれた方、そうでない方も、 ぜひ採用サイトをチェックしてみてください! www.revcomm.co.jp
この記事は RevComm Advent Calendar 2022 の 17 日目の記事です。 こんにちは大谷( @sara_ohtani_mt2 )です。 今年 10 月に RevComm に入社してバックエンドエンジニアをしています。 今年もあっという間にアドベントカレンダーの季節がきてしまいましたね。 不確実性と闘う皆さん、今年も一年お疲れさまでした! 『THE 有頂天ホテル』という大晦日のホテルを舞台にした映画の中の「年が変われば、いいこともあるわ」という台詞が大好きです。年末になるといつも思い出します。 新年を迎えるというのはただ年がインクリメントされるだけのことのようにも考えられますが、自分の頭と心を整理整頓してまた新しく始めるには、やはりぴったりの機会です。 そこで今日はセルフメンテナンスの基本と 2022 年最新ニュース、そして私がセルフメンテナンスのために行っているオリジナルフレームワークについてご紹介したいと思います。 INDEX 『エンジニアリングマネージャーのしごと』に学ぶセルフメンテナンスの基本 セルフメンテナンス関連キーワードと 2022 年最新ニュースのピックアップ セルフメンテナンスのための振り返り 振り返りフレームワーク「GNT」のススメ(Notion テンプレート付き) 不調からの復活をより再現性のあるものにする RevComm のセルフトークのカルチャー 参考 『エンジニアリングマネージャーのしごと』に学ぶセルフメンテナンスの基本 今年 8 月に発売された書籍『エンジニアリングマネージャーのしごと』を読みました。今後、組織課題に関心がある人への定番本となることは間違いないと感じさせる内容でした。 印象的だったのは、部下や他者に対するアプローチに関することだけでなく、セルフマネジメントや自分のメンタルをクリーンな状態に維持する方法についてもかなりページが割かれていたことです。 本の中では繰り返し「まずは自分をマネジメントしなければいけない」と書かれています。これは、自分の中が整頓されていないと仕事でも頭が回らず力が発揮できないから、ということ以外にも、さらにもう 1 つ意味があると思っています。 組織というのは、人の集まりであり、人数が多くなるほど複雑になっていきます。 組織の形で一番小規模なのが、メンターとメンティの 1 対 1 の関係ですが、さらにその前のレベルが、セルフメンテナンスです。 つまり、自分自身をメンティとして、コーチングやケアをするのです。 それによって組織問題解決のためにある様々な手法を試行して勘所や効果を探ることができます。 『エンジニアリングマネージャーのしごと』のセルフメンテナンスに関連するところを切り取ると、例えばこんなことが書いてあります。 チェックリスト形式にしてみました。いくつできているでしょうか? ツールを活用して情報を整理整頓している? 人脈を広げ、職場に友人ができて支えられていると感じられる状態にしている? 上司と語らい、同僚と語らい、家族や友人と語らう時間をもっている? 外に出て楽しいことをするようにしている? 嫌な感情をもったときには自分と向き合い、なぜそんな感情を持つのか探るようにしている? 周囲の面倒を見ることに全力を尽くすのと同じように自分の面倒も見ている? 何もしない時間枠を確保している? 緊急事態や割り込み、他の人のフォローに備えて、仕事の量を自分のキャパシティの 85% に押さえている? 8 時間の良質な睡眠をとっている? 1 時間に 5 分は体を動かしている? マインドフルネスやっている?(できれば年単位で) 自分のこの先 10 年のビジョンと計画、スキルバックログを書き出して適宜更新している? Checked: 個 / 12 個 並べてみるとあまりに基本的なことだと思う人もいるかもしれません。 しかし基本的なことにもかかわらず、ついつい忙しくなってくると優先度を下げてしまうことではないでしょうか。 忙しいときほど、これらの基本ができているか自分自身を見直す必要があります。 セルフメンテナンス関連キーワードと 2022 年最新ニュースのピックアップ 先程のチェックリストの中で「マインドフルネス」が出てきましたが、正直にいうと私自身はマインドフルネスをこまめには行えていません。 やることはシンプルですが、効果を実感するのは難しいと感じてしまって継続的に実施できずにいます。 ジョン・カバット・ジン(マサチューセッツ大学医学大学院教授・同大マインドフルネスセンターの創設所長)がマインドフルネスについて「何年かやってみれば、どうなるかわかるよ」と言っていたと『エンジニアリングマネージャーのしごと』内で書かれていますが、その境地にはなかなかたどり着けなさそうです。 IT 界隈でマインドフルネスといえば Google ですが、Google がマインドフルネスについて研究開発し始めたのが 2007 年頃とすると、それから 15 年経っていることになります。 *1 2022 年現在のトレンドはどうなっているのでしょうか。 近年でマインドフルネスと関連するキーワードとしては、「ウェルビーイング」が浸透しつつあるようです。 「ウェルビーイング」とは「幸福」「健康」などと訳されるワードです。 SDGs にも登場しており、そこでは「GOOD HEALTH AND WELL BEING(すべての人に健康と福祉を)」という形で掲げられています。 *2 また 2021 年には、内閣府の「成長戦略実行計画」に、「国民が Well-being を実感できる社会の実現」という記載があり、政府の KPI でもウェルビーイングに関するものが設定されています。 *3 *4 新型コロナウイルスの影響や生産性・創造性の向上効果への期待から、特に 2021 年後半以降からウェルビーイングのためのメンタルヘルステックへの市場の関心は引き続き高まっているようです。 *5 *6 emol 社による国内メンタルヘルステックカオスマップの 2022 年版で企業向けアプリがかなり増えていることからもそれは伺えます。 *7 引用元より転載 個人が日々のセルフメンテナンスのために利用するアプリの中で、私が広告をよく見かけるのは「Calm」や「Meditopia」というマインドフルネスアプリです。 *8 *9 そして同じくらいよく見かける「 Awarefy 」というアプリが Google Play ベスト オブ 2022「隠れた名作部門」で大賞を受賞したとのことでした。 *10 隠れた名作部門とは、まだ知られていないけれど、イノベーティブでファンを増やしているアプリを Google Play が選出したものです。 Awarefy ではマインドフルネスも行えますが、より広い枠組みである「認知行動療法」をメインコンテンツにしています。 認知行動療法とは、比較的働きかけやすい自分の認知(考え)と行動にアプローチすることで、本来変化させづらい感情や身体反応にもアプローチするという手法です。 例えばネガティブな感情やストレスによる影響の暴走を抑えるトレーニングになります。 アプリでは現在の状態とありたい姿、そしてそれを踏まえた一日の振り返りを記録することができます。 私も実際に Awarefy を使ってみましたが、認知行動療法の振り返りはアジャイル開発の振り返りのワークにも通ずるところがあり、KPT などの振り返りフレームワークを行ったことがある人には特に感覚がつかみやすいように思いました。 セルフメンテナンスのための振り返り 組織関連の仕事に関わっていると表に出せない話も多いと思います。 例えば 1on1 でした話は基本的に他言できないし、評価のことも採用のことも…。 そんな中でモヤモヤがたまってしまうことがあったらどうしたらよいでしょう?(そしてたいてい、ほぼ確実にそれはたまります) 正直になんでも自由に話せる場は本来限られています。 もしどんなことでも話せる心理的安全性が特別高い場を、どこに行っても常に確保できるとしたら、それは自分の強力な武器になりそうです。 そこで、定期的に一人で振り返りフレームワークを行うことをおすすめします。 自分自身をメンティ・メンターとして一人で振り返りを行うぶんには、話してはいけないことなどありません。 そして転職して会社が変わったとしても絶対についてきてくれるメンターを手に入れることができます。 自分を味方につけるということはセルフメンテナンスにおいてとても重要なことです。 振り返りフレームワーク「GNT」のススメ(Notion テンプレート付き) 自分自身を整理整頓するときには、振り返りと向き直りのセットが必要だと考えています。 振り返りは過去をもとに良かったところや問題を検証してより良くしていくワークで、向き直りは「そもそもどこへ向かいたいんだっけ?」ということを改めて整理してそこを目指すためのアクションを考えるワークです。 私はセルフメンテナンスのために、一人で行う振り返りを 2019 年頃から行っています。 最初は KPT をただ一人で行っているというスタイルでした。 KPT は Keep(今後も続けたいこと)、Problem(今後の課題)、Try(次にやってみること) を書き出すシンプルな振り返りフレームワークです。 しかし向き直りとセットにしていなかったことで、目の前のことにばかり囚われすぎて Problem の落とし込みがうまくできていませんでした。 そのぼやけた Problem から出した Try を実行してもどこかズレを感じて、改善サイクルを回しているはずなのに少しずつストレスになっていました。 そこで、「セルフメンテナンスのために一人で行う振り返り」というコンセプトで KPT をアレンジして、GNT (Good / Noise / Try) というフレームワークを作りました。 GNT 形式にアップデートしてから 2 年半ほど経ちましたが、今はこの形で落ち着いています。 GNT実施例サンプル、Vision の列が Noise と Try の間にある 進め方は下記の通りです。 事前準備として、任意のタイミングで Vision(ありたい姿)を書き出しておく Good(よかったこと)を書き出す Noise(モヤモヤしてること)を書き出す 特に気になる Noise をピックアップして深堀りし、Problem を見つける Try を書き出す 実行するTry を決める KPT との主な違いはこの 2 点です。 Vision を視界に入れながらワークを行う とにかく吐き出す Noise パートと必要に応じて深堀りする Problem パートに分ける Noise から Problem を見つける例 細かいやり方と Notion テンプレートはこちらです。 note.com 「最近、漠然と調子が悪いな」と思ったときは特に、ぜひ今日の『エンジニアリングマネージャーのしごと』からつくったチェックリストの内容ができているかも合わせて振り返ってみましょう。 不調からの復活をより再現性のあるものにする 闘う人にとって一番重要なリソースは何でしょうか? 資金力?時間?権力? いえいえ自分の気力でしょう!と私は思います。 「元気があればなんでもできる!」ではないですが、気力がないと問題を前にしても踏ん張れずに受け流してしまったりするものです。 しかし、気力が常にほとばしっていて尽きない!という人ばかりではないと思います。 少なくとも私はそうではありません。 ただ、気力が尽きてしまっても、できるだけ早く切り替えて立ち直ることならできるかもしれない、と思うのです。 そのために自分の不調に早めに気づいてケアをし、そして過去にモチベーションをあげるのに役立った Try や Good を記録しておくことで、不調からの復活をより再現性のあるものにしていっています。 日々、不確実性と闘う皆さんの中の気力が尽きてしまいそうな人の参考になれば幸いです。 RevComm のセルフトークのカルチャー RevComm には社内向けのカルチャーブック「RCCB ( R ev C omm C ulture B ook)」があり、その中にセルフトークについての項目があります。 自分自身を磨き続けよう ・セルフトークの時間を設け、プライベートとビジネス両面について自分自身と対話しよう。 ・人と比較して勝つのでなく、昨日の自分に勝とう。昨日の自分と比べて今日はどれだけ成長できるかに励もう。 ・自分自身の経験だけで学ぶのではなく、他者から一つでも多く学ぶことで、より自分の価値を高めよう。 ・成功するためには苦労が必要ですが、成功しているときも常に失敗が待っています。成功しているときも怠けることなく全力で取り組もう。 ・自分の身に起こることより、それに対してどう反応したかで決まります。思考は言葉に、言葉は行動に、行動は習慣に、習慣は性格に、性格は運命になります。 (RCCB 一部抜粋) 主要プロダクトである MiiTel も、営業電話のやり取りを解析、可視化したデータをもとに振り返り・セルフコーチングしていただくことで生産性向上につなげるというコンセプトがあります。 10 月に入社したばかりですが、自分と向き合い、こまめにメンテナンスすることを大切にしている会社なのだと感じています。 振り返り好きな方、ぜひご連絡ください! www.revcomm.co.jp それではよいお年を! 参考 エンジニアリングマネージャーのしごと ―チームが必要とするマネージャーになる方法 サーチ・インサイド・ユアセルフ――仕事と人生を飛躍させるグーグルのマインドフルネス実践法 *1 : あなたも実践できる、Google発のマインドフル・メソッドとは? https://careercompass.doda-x.jp/article/335/ *2 : ウェルビーイングとは? 注目される理由と、SDGsや経営の視点からみた重要性|SDGsにまつわる重要キーワード解説 https://sdgs.kodansha.co.jp/news/knowledge/40247/#section-1-3 *3 : 内閣府ホームページより 成長戦略実行計画案 令和3年6月18日 資料3 https://www5.cao.go.jp/keizai-shimon/kaigi/minutes/2021/0618/shiryo_03.pdf *4 : Well-beingに関する関係省庁の連携 https://www5.cao.go.jp/keizai2/wellbeing/action/index.html *5 : メンタルヘルステック投資額、1~3月6割減 前四半期比 https://www.nikkei.com/article/DGXZQOUC236LT0T20C22A6000000/ *6 : ウェルビーイング、市場も注目 社員の幸せ実現する会社 https://www.nikkei.com/article/DGXZQOCD0113Q0R01C22A0000000/ *7 : 「国内メンタルヘルステックカオスマップ 2022年版」を公開! https://prtimes.jp/main/html/rd/p/000000013.000043787.html *8 : カリフォルニア発 睡眠・瞑想・リラクゼーションの世界No.1アプリ『Calm』がついに日本上陸 https://prtimes.jp/main/html/rd/p/000000002.000071029.html *9 : トルコ発、Z世代向けマインドフルネスアプリが日本参入 https://project.nikkeibp.co.jp/behealth/atcl/feature/00004/120600327/ *10 : AwarefyがGoogle Play ベスト オブ 2022「隠れた名作部門」で大賞を受賞! https://prtimes.jp/main/html/rd/p/000000039.000057374.html
この記事は、 RevComm Advent Calender 16 日目の記事です。 フロントエンドチームに所属する関口です。フロントエンドエンジニアとして活動するかたわら、MiiTel の一部の製品のプロジェクトマネージャーを兼任しています。 なぜこのタイミングで Chrome 拡張機能がテーマなのかというと、最近 Manifest V3 への対応 を弊社の MiiTel Phone Chrome 拡張機能 に対して行い、知見ができたことがその理由です。各 Chrome 拡張機能の機能定義を行うフォーマットが Manifest ファイルです。このフォーマットの V2 から V3 への移行が Google から促されており、その 移行猶予期間が来年の 1 月まで となっています。 この記事について この記事のゴール Chrome 拡張機能の開発手順を最低限確認した後、TypeScript が使えるシンプルな開発環境を作るチュートリアルを提供します。 この記事で説明しないこと Chrome 拡張機能の配布方法 Node.js やパッケージのバージョンについて Lint やユニットテスト、CI について バージョン管理ツールの設定や操作 対象読者 TypeScript 開発経験のあるソフトウェアエンジニア 導入 Chrome 拡張機能は何ができるか ブラウザが備える JavaScript API に加え、Chrome ブラウザのローカルな機能にアクセスするための Chrome API が利用できます 。Chrome API を通じてブックマークのデータにアクセスしたり、ブラウザのコンテキストメニューを編集したり、といったことが可能になります。 Chrome 拡張機能のファイル構成 Chrome 拡張機能は Manifest、Service worker、Content scripts、HTML ページといったファイルで構成されています。 オフィシャルドキュメントによるそれぞれの定義 を見ていきます。 The manifest ファイル名は manifest.json で、その拡張機能のルートディレクトリに置く必要がある。拡張機能の定義(利用したい API や構成するファイルなど)をここに書く。 The service worker ブラウザのイベントを拾うイベントハンドラを定義する。 Chrome API を利用できるが、Web ページのコンテンツには直接アクセスできない。 Content scripts Chrome で閲覧中の Web ページにインジェクトされる。DOM にアクセスできることに加え、Chrome API の一部の機能が使える。拡張機能の Service worker とメッセージを送受信してやりとりができる。 The popup and other pages popup (拡張機能アイコンをクリックした時に表示するコンテンツ)、 option page (拡張機能の設定ページ)、 その他任意の HTML ページ を追加できる。Chrome API にアクセス可能。 基本的な開発方法についてはここでは説明しません。公式ドキュメントの Development Basics に簡潔に説明されているので、そちらをご参照ください。 モチベーションと技術選定方針 TypeScript を使って簡単な Chrome 拡張機能を作りたい。 依存パッケージを最小限にし、ラーニングコストがかからない開発環境にしたい。 とにかくシンプルにしたいので、React や Vue といった JavaScript フレームワークも使わず、HTML も CSS も生で書くことにします。ここでは扱いませんが、もし React や Vue を使いたい場合、 CRXJS を使って環境構築するとよさそうです。 チュートリアル Hello World Development Basics の Hello World チュートリアルをベースに、簡単な拡張機能を作ってみましょう。まず拡張機能のために空のディレクトリを用意して、package.json を作ります。 $ mkdir extension-directory $ cd extension-directory $ npm init -y 空の manifest.json ファイルを作り、 $ touch manifest.json エディタで開いて以下のように書き込みます。 { " manifest_version ": 3 , " name ": " Hello Extensions ", " description ": " Base Level Extension ", " version ": " 1.0 ", " action ": { " default_popup ": " hello.html ", " default_icon ": " hello_extensions.png " } } hello_extensions.png はこちらからダウンロード して、同じディレクトリに置きます。これがツールバーに表示される拡張機能のアイコンになります。 同ディレクトリに hello.html ファイルを作り、以下のように書き込みます。 < html > < body > < h1 > Hello Extensions </ h1 > </ body > </ html > そして、Chrome の拡張機能ページを開き画面右上にある「デベロッパーモード」をオンにして表示される「パッケージ化されていない拡張機能を読み込む」ボタンをクリックすると、そのディレクトリの必要なファイルを読み込んで拡張機能として実行することができます。 パッケージ化されていない拡張機能を読み込む Chrome のツールバーに Hi アイコンが表示されるはずですが、これをクリックすると、Hello Extensions というポップアップが表示されます。 ここまでは Development Basics のチュートリアル の内容です。 Popup の JavaScript では、この hello.html に JavaScript を追加します。 < html > < body > < h1 > Hello Extensions </ h1 > < script src = "popup.js" ></ script > </ body > </ html > popup.js ファイルの中身は以下のようにしましょう。 const $body = document .querySelector( 'body' ); const $p = document .createElement( 'p' ); $p.innerHTML = 'Hello Popup' ; if ($body) { $body.appendChild($p); } 拡張機能ページのリロードボタンをクリックすると 拡張機能ページのリロードボタン 拡張機能はこのように更新されるはずです。 この popup.js を少し最適化してみます。 + import { HELLO } from './constants.js'; const $body = document.querySelector('body'); const $p = document.createElement('p'); - $p.innerHTML = 'Hello Popup'; + $p.innerHTML = `${HELLO} Popup`; if ($body) { $body.appendChild($p); } constants.js という新しいファイルを作り、 export const HELLO = 'Hello' ; と書き込みます。 このように、一部のテキストを constants.js に定数として切り出しました。更新ボタンをクリックしてから拡張機能を表示すると、該当のテキストが表示されていません。拡張機能管理ページを確認すると「エラー」ボタンが表示されています。 拡張機能管理ページのエラー表示 エラーボタンをクリックすると、エラーの内容を見ることができます。 エラーの詳細 Uncaught SyntaxError: Cannot use import statement outside a module と表示されていますね。 import 構文を使うには script タグに type="module" 属性が必要です。 hello.html を修正します。 <html> <body> <h1>Hello Extensions</h1> - <script src="popup.js"></script> + <script type="module" src="popup.js"></script> </body> </html> 拡張機能をリロードして確認してみましょう。今度は該当のテキストが表示されるはずです。 また、拡張機能管理ページのエラーボタンはそのまま表示されているはずです。いつの時点のエラーなのかを区別するため、エラー内容ページに遷移し、現時点でのエラー報告はすべて削除しておくとよいでしょう。 Content scripts を追加する さらに、Webページにインジェクトされる Content scripts も書いてみます。content_scripts.js ファイルと content_styles.css を追加し、以下のように manifest.json を修正します。 { "manifest_version": 3, "name": "Hello Extensions", "description": "Base Level Extension", "version": "1.0", "action": { "default_popup": "hello.html", "default_icon": "hello_extensions.png" }, + "content_scripts": [ + { + "matches": [ + "http://*/*", + "https://*/*" + ], + "css": [ "content_styles.css" ], + "js": [ "content_scripts.js" ] + } + ] } content_scripts.js const $body = document .querySelector( 'body' ); const $helloContent = document .createElement( 'div' ); $helloContent.className = 'hello-content' ; $helloContent.innerHTML = 'Hello content scripts' ; if ($body) { $body.appendChild($helloContent); } content_styles.css .hello-content { position : fixed ; z-index : 999 ; bottom : 0 ; right : 0 ; width : 10em ; padding : 1em ; background-color : white ; box-shadow : 0 0 5px gray ; text-align : center ; } 拡張機能を更新して適当な Web ページ(この例では https://www.google.com )を表示すると、右下に Hello content scripts というボックスが表示されるはずです。 Google トップページに挿入された Content scripts TypeScript 化する TypeScript で開発できるようにしていきます。 まず、必要なパッケージをインストールします。 $ npm install -D typescript @types/chrome tsconfig.json も用意します。ここはお好きにどうぞ、と言いたいところですが、ここでは以下のように設定します。 src ディレクトリに .ts ファイルを置き、コンパイル後の出力は dist ディレクトリに行う設定です。 { " compilerOptions ": { " target ": " ESNext ", " lib ": [ " ESNext ", " DOM ", " DOM.Iterable " ] , " useDefineForClassFields ": true , " module ": " ESNext ", " rootDir ": " ./src ", " outDir ": " ./dist ", " moduleResolution ": " node ", " baseUrl ": " src ", " resolveJsonModule ": true , " allowJs ": true , " checkJs ": true , " removeComments ": true , " esModuleInterop ": true , " strict ": true , " noImplicitAny ": true , " noUnusedLocals ": true , " noUnusedParameters ": true , " noImplicitReturns ": true , " skipLibCheck ": true } , " include ": [ " src " ] , " exclude ": [ " node_modules ", " dist ", " ./tsconfig.json " ] } ファイルを以下のように移動します。 .js ファイルは .ts にリネームします。 extension-directory/ ├── node_modules ├── dist │ ├── content_styles.css │ ├── hello_extension.png │ ├── hello.html │ ├── manifest.json ├── src │ ├── constants.ts │ ├── content_scripts.ts │ ├── popup.ts ├── packages.json ├── tsconfig.json packages.json に build コマンドを追加します。 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc" }, npm run build コマンドを実行してみましょう。 dist ディレクトリ以下に、コンパイルされた constants.js、content_script.js、popup.js の 3 つができたら成功です。 manifest.json の置いてあるディレクトリが dist に変わったので、拡張機能を読み込み直します。 拡張機能ページで該当の拡張機能をいったん「削除」し、再度「パッケージ化されていない拡張機能を読み込む」で dist ディレクトリを指定します。 パッケージを削除してから読み込み直す Content scripts の最適化 content_script.ts も import を使って共通のモジュール(constants.ts)を利用するように最適化します。 + import { HELLO } from './constants'; + const $body = document.querySelector("body"); const $helloContent = document.createElement("div"); $helloContent.className = "hello-content"; - $helloContent.innerHTML = "Hello content scripts"; + $helloContent.innerHTML = `${HELLO} content scripts`; if ($body) { $body.appendChild($helloContent); } npm run build でビルドしてから、拡張機能をリロードします。適当な Web ページを表示してみると…画面右下の拡張機能が表示されません。Chrome DevTools の Console を見ると、 Uncaught SyntaxError: Cannot use import statement outside a module エラーメッセージが表示されています。 Content scripts のエラー表示 Content scripts は自動的にドキュメントにインジェクトされるため、 script タグに type="module" をつけるといった対処は行えません。 …となると、 import 構文を使わないように書くか、バンドラーを使うしかない、ということになってきます。今後の拡張性も考慮して、バンドラーを導入することにします。 Vite の導入 バンドラーは Webpack でも Rollup でもなんでも構いませんが、洗練された開発環境を提供し、日本語ドキュメントもある Vite をここでは採用します。 まずはインストール $ npm i -D vite vite.config.js ファイルをルートディレクトリに作り、以下のように設定を書きます。 import { resolve } from 'node:path' ; import { defineConfig } from 'vite' ; export default defineConfig((opt) => { return { root: 'src' , build: { outDir: '../dist' , rollupOptions: { input: { content_scripts: resolve(__dirname, 'src/content_scripts.ts' ), popup: resolve(__dirname, 'src/hello.html' ) } , output: { entryFileNames: '[name].js' , } , } , } , } ; } ); 既存のファイルも以下のように移動します。 extension-directory/ ├── node_modules ├── dist ├── src │ ├── public │ │ ├── content_styles.css │ │ ├── hello_extension.png │ | ├── manifest.json │ ├── constants.ts │ ├── content_scripts.ts │ ├── hello.html │ ├── popup.ts ├── packages.json ├── tsconfig.json ├── vite.config.js packages.json の build コマンドを以下のように書き換えます。 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc" + "build": "tsc && vite build" }, vite は TypeScript のトランスパイルを行うので、 tsc でのトランスパイルは不要になります。一方、vite は型のチェックを行わないため、 tsc を型チェックのためだけに利用するように動作を変えます。 tsconfig.json を以下のように修正します。 "rootDir": "./src", - "outDir": "./dist", "moduleResolution": "node", ... "removeComments": true, + "noEmit": true, + "isolatedModules": true, "esModuleInterop": true, noEmit を指定することで型チェックだけ行ってファイルを出力しないようにします。また、 isolatedModules の指定は vite にとって必要な設定 です。 hello.html も調整が必要です。読み込むファイルを .js から .ts に変更します。 <body> <h1>Hello Extensions</h1> - <script type="module" src="popup.js"></script> + <script type="module" src="popup.ts"></script> </body> ここまで設定したら npm run build を実行してみましょう。 ルートの dist ディレクトリに以下のようなファイルが出力されたら成功です。 extension-directory/ ├── dist │ ├── assets │ │ ├── constants.xxxxxxxx.js │ ├── content_scripts.js │ ├── content_styles.css │ ├── hello_extensions.png │ ├── hello.html │ ├── manifest.json │ ├── popup.js さて、出力された content_scripts.js を開くと… import { H as n } from "./assets/constants.xxxxxxxx.js" ; const t= document .querySelector( "body" ),e= document .createElement( "div" );e.className= "hello-content" ;e.innerHTML= ` ${n} content scripts` ;t&&t.appendChild(e); このような内容になっているはずです。あれ… import が使われていますね。これは意図している出力結果ではありません。 モジュールのファイル内容を展開して出力結果から import 構文を無くすには、rollup の inlineDynamicImports オプションを使うと実現ができます。ただし、このオプションはインプットとなるファイルが 1 つの場合のみ利用できるという制約があります。今回のインプットは content_scripts と popup の 2 つがあるので、このままでは使えません。さてどうするか。 Vite の設定を工夫する vite.config.js を 2 つに分割してしまうことで、この問題を回避します。 vite.config.content_scripts.js ファイルを追加し、以下のような設定にします。 format: 'iife' は即時実行関数( "immediately-invoked function expression" )のフォーマットでファイルを出力する設定で、script タグで読み込まれるスクリプトに適しています。 import { resolve } from 'node:path' ; import { defineConfig } from 'vite' ; export default defineConfig((opt) => { return { root: 'src' , build: { outDir: '../dist' , emptyOutDir: false , rollupOptions: { input: { content_scripts: resolve(__dirname, 'src/content_scripts.ts' ), } , output: { entryFileNames: '[name].js' , inlineDynamicImports: true , format: 'iife' , } , } , } , } ; } ); これに合わせて vite.config.js も修正します。 build: { outDir: '../dist', + emptyOutDir: true, rollupOptions: { input: { - content_scripts: resolve(__dirname, 'src/content_scripts.ts'), popup: resolve(__dirname, 'src/hello.html') }, output: { entryFileNames: '[name].js', }, }, }, emptyOutDir は出力先のディレクトリを実行前に空にするかどうかの設定です。 最後に packages.json のコマンドを修正しましょう。 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc && vite build" + "build": "tsc && vite build && vite build --config=vite.config.content_scripts.js" }, config ファイル違いでビルドを 2 回実行します。前に実行する設定では emptyOutDir: true にし、後に実行する設定では emptyOutDir: false にするのが肝です。後の実行で emptyOutDir: true になっていると、先に実行した出力ファイルが削除されてしまうためです。 今回は vite.config.js でビルドしているのが popup.js(インプットは hello.html)だけですが、今後 Service worker スクリプトやオプションページで利用するスクリプトを追加したくなったら、この設定ファイルのインプットに足していきましょう。これらのスクリプトでは先に説明したように import 構文が使えます。Content scripts だけは、vite.config.content_scripts.js でビルドするようにします。 これで準備はできました。 npm run build します。出力されたファイルは設定変更前とほぼ同じですが、 content_scripts.js の中身を見てみましょう。 ( function () { "use strict" ; const n= "Hello" ,t= document .querySelector( "body" ),e= document .createElement( "div" );e.className= "hello-content" ,e.innerHTML= ` ${n} content scripts` ,t&&t.appendChild(e) } )(); constants.ts の内容が展開されています。意図どおりの出力です。 拡張機能をリロードして確認していきます。エラーも表示されず、Popup、Content scripts それぞれ正しく動作していることが確認できたらこのチュートリアルは完了です。 チュートリアルの要点 Content scripts では import 構文が使えない。tsc でトランスパイルすると、この問題でつまづく。 import 構文は使いつつ上記の問題を回避するにはバンドラーを使う必要がある。 Vite でうまいことやるには vite.config.js ファイルを 2 つに分けて、片方を Contents scripts のビルド専用にする。そちらでは inlineDynamicImports: true を設定することで出力後のファイルを 1 つにまとめる。 今回のチュートリアルの最終的な成果物は "TypeScript で Chrome 拡張機能を開発する" チュートリアル ソースコード から参照可能です。 References Development Basics - Chrome Developers TypeScript: tsconfig リファレンス Vite rollup.js 記事を読んでくれた方へ Web サイトや Web アプリケーションの開発に比べると、Chrome 拡張機能を開発するのはニッチな機会ですし、情報も決して多くはないジャンルです。そんな中、よりよい開発体験を得ようと試行錯誤してこの記事にたどりついたあなたのようなエンジニアを、弊社では必要としています。 弊社の採用情報 も是非チェックしてみてください。 www.revcomm.co.jp
この記事は RevComm Advent Calendar 2022 の 15 日目の記事です。 はじめに こんにちは。RevComm シニアエンジニアマネージャーの瀬里です。 普段はエンジニアリング組織づくりをメイン業務として行っています。 「組織づくり」というと聞こえが良いですが、実際は年間数百人の方と面談して一緒に働きたいと思える優秀な方をアトラクトするということがとても重要な業務だったりします。 早いもので、RevComm で働き始めてこの 12 月で 5 年目に突入しました。 この間に自分の働き方がどのように変わったのか、そしてエンジニア組織としてもどのように変わってきたのか、今後どのような組織を目指しているのかについて今回は書いていきます。 マネジメント論やスタートアップ組織論だとかいうものはネットや本を探せばいくらでも出てくるのでほぼ割愛して、リアルにやってきたこと、感じたことを書くことで多少なりとも誰かの役に立てればと思います。 2018~2019 年:組織未満 1 ヶ月の業務委託を経て 2018 年 12 月に入社した際、エンジニアリング組織は業務委託と社員で合わせても 5-6 名くらいだったかと思います。既に MiiTel という主力製品が世に出てから数ヶ月経った頃です。 当時は明確な部署やチームというものもなく、とにかく各々が貢献できる部分を全力でやるという感じでした。 スタートアップでよくあるような障害や、予想もしていない問題が起きて徹夜でやりきるということを一番体験した期間だったりします。 この頃の課題の 1 つは、 過去の自分のインタビュー記事 でも書いていますが個人事業主の集まりという感じでチームとしてのまとまりが少なかったことです。ただ、今となって振り返ると、少ない人数で事業を急成長させるために、それぞれのメンバーが個別に動いてとにかく目の前の課題に全力を尽くすという働き方は当時の最適解であったとも思えます。 少人数なのにチーム制であるとかフォーマットが統一された正確なドキュメント化だとか、不確定な未来について語り合うといったことは時間をとられるだけで開発の速度は上がりません。 一方で反省点があるとすれば、最初期から最低限文書化だけははじめからある程度ルールを設けておけばよかったな、ということです。 技術者としては README.md や ARCHITECTURE.md などで導入や技術選定、機能の開発の意図などをごく簡単にでも残しておくと後で楽です。このちょっと面倒くさくて省いてしまいたくなる作業は、後々になって人数が増えてくるとかなり効いてきます。 小まとめ 実際の業務 / 立場 サーバーサイド・フロントエンド・インフラの設計・実装(がんがんプログラミングする) 入社後半年も過ぎない頃から採用面談もしていた 組織の形 未形成。かろうじてマイクロサービスという単位でオーナーが決まっている程度 全社的に「部署」というものがなかった 求める資質 自ら課題を発見し、率先して取り組めること 重要な課題 人が足りない 組織運営の上での施策例 特になし。そもそも「部署」という概念が存在せず、必要なかった とにかく全員ができることを全部やって会社の成長を図る 2020 年:担当範囲の決定 エンジニア全体の人数が 20 名程度になりました。 徐々にマイクロサービスを起点としたチーム制が成り立ってきた感じです。自分自身も明確にWebアプリケーションを担当するという形で Web チーム全体(バックエンド・フロントエンド)のリーダーとして動いていました。 毎月、徐々にメンバーが増えるという状況でしたが大きな混乱はなく、オンボーディングはメンバーの入社時に適宜行うスタイルでした。工夫していたのは、例えば障害対応の方法について、一度説明を受けたメンバーが次に入ってきたメンバーに説明するようにする、といった後任を育てるという点です。 オンボーディングやKT(Knowldege Transfer)の内容を録画して見てもらうようにするということも考えましたが、ほぼ浸透しませんでした。完全リモートワークなのにここまでやってしまうと人間味がなくなるという理由もあります。 小まとめ 実際の業務 / 立場 Webチームのマネージャー と言いつつ、サーバーサイド・フロントエンド・インフラの設計・実装(がんがんプログラミングする) 組織の形 マイクロサービスチーム 求める資質 自ら課題を発見し、率先して取り組めること 重要な課題 人が足りない 組織運営の上での施策例 オンボーディング用質問チャンネル この頃に社内で分からないことがあるメンバーを積極的に助ける Slack チャンネルとして、QA チャンネルというものがたちあがりました。 (Question & Answerの略) フルリモートワークということで、隣のメンバーの肩を叩いて「ねぇ、これどうやるの?」と気軽に聞くことができない環境において、みんなが助け合うという趣旨の場所は重要です。 ※今は QA = Quality Assuarance(品質)のチームが内部で立ち上がったこともあり、チャンネル名は 「helpme」チャンネルとなっています。 helpme チャンネルに今でもピン留めされている初期の自分の投稿をそのまま転載します。 RevComm特有の開発プロセスとか資料どこ、とかについては即質問。(悩むな。わかりやすく用意できていない先達たちが悪い。あなたのせいじゃない) 1時間調べて悩んで試してもわからなかったら、すぐ訊くべし。 リモートワークだからこそ、困ったときは同僚を頼れる場を作っておくことが安心感に繋がります。また、皆が回答をしているのをみて「自分も何か手伝えることはないか」と自発的に行動してくれるようになる副次的効果があったりします。 エンジニア独自の評価プロセス 明確な評価制度というものが全社で導入され、全社共通で行われるバリューを基にした 360 度評価と、テック独自のスキルも加味した MBO(目標管理 : Management by Objectives)と評価期間内に取り組んだことのプレゼンテーション評価というものを組み合わせて行うようになりました。 2021 年:明確な組織化 エンジニアが 30 人を超えました。いわゆる 30 人の壁というものが出てきます。 実際に様々な技術レベル・価値観の方が入ってくることによって、全員が同じレベル、モチベーションで働くということが難しくなってきます。 良いか悪いかは別として、休日に Slack のメンション通知が少なくなってきたのもこの頃です。以前は四六時中 Slack 通知が届き、自分も含めて「この人いつ寝てるの?」とか思うメンバーもちらほらいました。 今回の Advent calendar で初日を担当した、リサーチディレクターの橋本さんがジョインしたのもこの頃で、リサーチ部門がテクノロジー部門と明確にわかれました。 小まとめ 実際の業務 / 立場 テクノロジー部門の組織づくり 障害対応の場合は手を動かすことも 組織の形 マトリックス型組織 求める資質 自ら課題を発見し、率先して取り組めること 重要な課題 人が足りない 組織運営の上での施策例 技術領域単位でチーム組成 コーポレートエンジニアリング(社内システム向けの業務ツールを担う)という特殊な領域以外に関して、プロジェクトにはそれぞれのチームからメンバーがアサインされてプロジェクトを推進するマトリックス型組織と呼ばれる形を採用しました。 1 チームは極力 5 名以下。経験上、非定型的なエンジニアの業務では 5 名程度までが 1 人のマネージャーが見れる限界だと思います。 採用プロセスの整備 エンジニアの採用には、該当技術領域のチームのメンバーが一度は必ず面談する技術領域単位での採用プロセスを取り入れています。例えばフロントエンドならフロントエンドの現場メンバーが出ます。 スカウトも各チームが責任を持って行う体制へと移行。 これには良い面と悪い面の両方があります。現場メンバーが出ることで、候補者の技術レベルや「一緒にプロジェクトをやっていけそうか」など判断し易くなる一方で、エンジニアの開発時間が面接に割かれて開発が遅れることにも繋がります。 2022 年:常に改善中 さて、人数がだいぶ増えました。 いつのまにか 50 名を突破し、いまこの記事を書いている時点でエンジニアの総数は 80 名。その数が 100 名に近づこうとしています。 組織的には順調に人数も増え、開発やQAなどのプロセスもしっかりとまとまってきています。 日々の技術的課題への自発的な取り組みはもちろんですが、Tech Talkという社内ライトニングトークやもくもく会といったものがエンジニアから自発的に企画されて行われています。 会社全体ではフットサル・アウトドア・ゲームといった各種クラブ活動も続々と生まれてきています。 もちろん、ここまできても課題は出てきます。 主なところでは以下のようなことです。 メンバーは増えているが、開発スピードをさらに高めたい 様々なミッションを担当しているメンバーが全員納得するような評価制度を目指してアップデートを図りたい 課題は解決するしかないので日々改善施策を試す日々が続いています。 小まとめ 実際の業務 / 立場 テクノロジー部門のマネジメント・執行役員 たまに設計・コーディングなども 組織の形 マトリックス型組織 求める資質 自ら課題を発見し、率先して取り組めること 重要な課題 人が足りない 組織運営の上での施策例 マネジメントとプロフェッショナルの分離 従来はシニアエンジニアやテックリードといった、プロフェッショナルなメンバーが管理職(ラインマネジメント)も兼務しているケースが多かったです。 しかし、事業成長速度を出すために 1on1 や評価といったマネジメント業務よりも技術的な課題解決、エンジニアリングなどにプロフェッショナルとして集中してもらう方が良いと判断し、マネジメント業務から離れてもらう方向を取っています。 マネジメントとプロフェッショナルの職種は取り組む課題に違いがあるだけで、どちらが上ということはありません。興味がある分野でそれぞれが最高の成果をあげれば組織としても成長して大きくなり、より大きなことに取り込むことができるようになった結果としてそれぞれが望むキャリアを歩んでいけると思っています。 RevComm ではエンジニアのキャリアアップとして組織的な課題に取り組むマネジメントか、技術的な課題を専門として取り組むプロフェッショナルどちらにより興味があるかヒアリングし、それぞれに合ったキャリア形成を行っていけるように取り組んでいます。 評価制度の見直し 組織の拡大に伴って評価対象の人数も増え、マネージャーの負担が増えてきています。マトリックス型組織においてありがちな課題として、MBO やスキルベースのマネージャーによる評価だけではプロジェクトの貢献度に対する評価がおろそかになりがちということがあります。 現在は、この点を改善をすべく正当な評価をより低負荷で行うことを目標に制度の見直しを行っています。 さいごに RevComm のエンジニア組織は 100 名に近づいてきていますが、まだまだ組織的には「これがベストのやり方である」とか「こういう方が必ずフィットする」という答えはありません。 必要なのはその時々においてベストだと思える状態に組織を持っていくことであり、組織、そして中にいるメンバーのマインドも変わり続けることだと思います。 最後まで目を通していただき気づいた方がいるかもしれませんが、初期から今まで組織的として「求めている資質」と「重要な課題」は同じです。 組織がいくら拡大したとしても一緒に働く仲間に最も求めていることは一貫して同じであり、自ら課題を発見して率先して取り組んでいくような自走力です。 個人的には、プログラミングやビジネスマインドは後からでも鍛えようがありますが、自走力というものは価値観や長年の経験などからくるものであり、一朝一夕では身につくものではないと考えています。そのため、採用の段階で必ず重視してみています。 また、不思議なことにエンジニアが 100 名近くまで増えた現段階においても、やりたいことすべてを行うためにはまだ人が足りていない状況は変わっていません。おそらくこれは人が増えるに伴い会社としてやりたいことも際限なく出てくるので、永久にこの課題は解決されないのかもしれないと思っています。 ここまでで、本の引用を避けてこれまでに体験してきたこと、その時点で感じたことなどを書いてきました。重要なことは、本の内容は知識として活用しつつ組織の実情に応じて必要な仕組みを作り出すこと、組織の全員が自分ごとと捉えて自発的に動ける場を作り出すことだと思っています。 急拡大しながら日々変わり続ける組織で働くことに興味をもっていただいたら、是非弊社の採用サイトを覗いてみてください。エンジニアだけではなく様々なポジションで募集しています。 是非一度気軽に話しましょう。 www.revcomm.co.jp
この記事は RevComm Advent Calendar 2022 の 14 日目の記事です。 はじめに こんにちは。株式会社 RevComm でモバイルアプリを開発している長尾です。 普段は MiiTel Phone Mobile の機能開発やメンテナンスを行っています。 さて、アプリをリリースした後もユーザーさんに快適に使用してもらうためには、アプリのパフォーマンスを定期的に計測し、アプリが軽快に動作しているかを確認し続けることが重要です。 本記事では、iOS アプリのパフォーマンスを計測する方法について、まとめています。 iOS アプリのパフォーマンスを測定するためのツール iOS アプリのパフォーマンスを計測する方法として以下の三つのツールを紹介します。いずれのツールも Xcode をインストールすれば追加でツールをインストールする必要はなく、すぐに使い始めることができます。 Debug Navigator Instruments XCTest metrics Debug Navigator Xcode でデバッガを接続しているときに自動で立ち上がっている画面です。 メモリ、CPU、消費電力やネットワークなどの使用状況が UI に表示されます。デバッグ中に簡易的にパフォーマンスを確認するのに便利です。コード作成中に、何らかのミスで循環参照になっているなどして、無限にメモリを消費してしまうような状況になっていることがあります。そのようなケースでは、メモリの使用状況のグラフを見ると、状況が一目で分かります。デバッガを接続している際は特に設定もなく使用可能なので、とりあえず何か起こってないか確認したいときに使用するツールだと思います。 Instruments 測定したいメトリクスをカスタマイズでき、測定できるメトリクスの種類も豊富です。測定したい項目の選択はテンプレートとして保存することができ、よく測定する項目をさまざまなシーンで容易に測定することができます。 XCTest metrics ユニットテストの中で、特定のメソッドに対して、パフォーマンスを測定することができます。測定できる項目は Instruments ほど多くはありませんが、経過時間、メモリ使用量、CPU 使用率、ストレージに関するメトリクスなどが測定できます。他のツールにない特徴としては、正常に完了したテストのメトリクスを baseline として、次回以降のテストにおいて baseline からの乖離を評価できます。乖離が大きくなっている場合、テストが失敗したとみなすことで、パフォーマンスの低下や予期せぬ不具合を検出することができます。 これらのツールはそれぞれ用途が異なっているので、どれが優れているというわけではありません。適材適所で使用することで、各々のツールの特徴を生かすことができます。 実際にパフォーマンスを測定してみる では、これらのツールを用いて、アプリのパフォーマンス評価を行ってみましょう。 今回評価するアプリは、最近 Apple が公開した StableDiffusion を利用できるライブラリである ml-stable-diffusion を組み込んだ簡単なアプリです。 今回作成したアプリで生成した「a photo of an astronaut riding a horse on mars」 Debug Navigator で大まかなアプリのパフォーマンスを確認する 開発段階においては、Debug Navigator を用いて大まかなアプリのパフォーマンスを把握します。中央にある Memory のセクションを見ると、Memory Use が 2.1GBと表示されています。対象のアプリは、かなり多くのメモリを使用していることが分かります。 本格的に画像生成が始まった際は、メモリ使用量が大幅に増えている また、メモリを大量に使用するのは画像を生成している間のみで、画像が生成されるとメモリが解放されること (2.1 GB -> 48.6 MB) が、下記の画像の Memory Use の値やグラフの落ち込みなどから確認できます。 画像が生成されるとメモリが解放される Instruments を使ってアプリの詳細なパフォーマンスを確認する アプリが意図通りに動作していることがある程度確認できたので、もう少し詳細なパフォーマンスを確認していきます。 最初に挙げた三つのツールの中で、最も詳細にパフォーマンスを測ることができるツールである Instruments を使って、パフォーマンスを見ていきましょう。 ml-stable-diffusion は、CoreML を利用しています。またライブラリが要求するシステム要件も iOS16.2 以上、iPhone 12 以上となっていることから、ml-stable-diffusion を実行するために、機械学習の処理に特化したチップである Neural Engine を利用していることが推測されます。これを Instruments を使って確認してみましょう。 Xcode から Develop tools > Instruments とたどり、Instruments を起動させます。Instruments を起動させると、テンプレートを選択する画面が表示されます。 パフォーマンスを確認したいタイプのテンプレートを選択すると、確認したい項目がセットされた状態で、以下のような画面が起動します。 Instrumentsのテンプレート選択画面 今回は、Core ML のテンプレートを用いてパフォーマンスを確認してみました。 Instrumentsの実行結果の例 推測したとおり、しっかりNeural Engineが使われているようです。他にも Core ML のメトリクスでは、モデルの名前が確認できます。 XCTest metrics を使ってアプリのパフォーマンスを継続的に確認する 最後に、XCTest metrics を用いて、特定メソッドのパフォーマンスを確認します。 XCTest metrics は、単体テストの一部として実装します。 普通の単体テストの一メソッド内で、 XCTestCase クラスの measure メソッド を実装します。このメソッドは引数にクロージャを取り、クロージャに定義された処理に対する実行時間を測定します。クロージャに入った処理を複数回実行することで、メトリクスの baseline を算出します。今回は以下のようなコードでパフォーマンスを測定しました。 func testMetrics () throws { let metrics : [ XCTMetric ] = [XCTClockMetric(), XCTMemoryMetric(), XCTCPUMetric(), XCTStorageMetric()] self .measure(metrics : metrics ) { let _ = ViewController.generate(prompt : “a photo of an astronaut riding a horse on mars”) } } 実行結果は以下のように表示されます。 一度 baseline を設定してからほぼ何も修正せずに再度テストしてみたのですが、14% 数値が悪化してしまいました。一回の実行時間が 200s を超えているので、10% としても 20s。テスト環境の関係上、完全に条件が揃えられているわけではありませんが、パフォーマンスが安定していないように感じました。筆者の主観ですが、ml-stable-diffusion は、ファーストパーティである Apple が GitHub にサードパーティ製ライブラリとして配布していることを考慮すると、プロダクションに投入するには時期尚早と判断したのかなと考えています。 baselineを設定した後の実行結果 おわりに 本記事では、iOS アプリのパフォーマンスを計測するツールをご紹介しました。計測することは、パフォーマンスを向上させる第一歩となります。 今日紹介したツールを使って効率的にボトルネックとなっている箇所を見つけ、アプリのパフォーマンスを向上させるきっかけとなれば幸いです。 公式サイトにはさらに詳しい情報がまとめられていますので、より深く知りたい方は こちら をご確認いただくとよいと思います。
この記事は RevComm Advent Calendar 2022 の 13 日目の記事です。 はじめに RevComm の小門です。 普段はインフラエンジニア / SRE としてクラウド (AWS) の全社横断的なセキュリティ強化、統制を推進しています。 また、私自身ここ数年間はサーバサイドエンジニアとして Python、特に Web アプリケーションフレームワークである Django をキャッチアップしています。 RevComm では最近、開発プロジェクトにも関わりはじめました。 RevComm のプロダクトと Django RevComm の主要プロダクトである MiiTel は営業やコールセンター業務に活用できる IP 電話の SaaS 製品です。 MiiTel の音声解析 AI による分析、可視化した結果を確認するためのダッシュボードである MiiTel Analytics を Web アプリケーションとして開発しています。 MiiTel この Web アプリケーションはフレームワークに Django を採用しており、本記事ではこのリポジトリについて説明していきます。 プロダクトの初期ローンチ以来、機能拡張や刷新(v1 -> v2)を重ねており、今日まで基本的に 1 つのリポジトリで開発を続けています。 ※もちろん、必要に応じて機能分離して別リポジトリ化することもあります。 リポジトリの運用開始時期:2019 年ごろ Django アプリケーション数:約 40 リポジトリの関係メンバー:約 20 名 このように 3 年以上運用しているプロダクトのリポジトリであり、社内では最も大きなコードベースになっています。 また、ありがたいことに RevComm にはほぼ毎月新しいメンバーが入社してくれていて、オンボーディングが終わり次第、開発プロジェクトにアサインされます。 新規メンバーが大規模なリポジトリの開発でスタートダッシュするために、オンボーディングでのルール周知と「仕組み化」の構築が必要だと思います。 数名程度の規模のチームなら問題にならないかもしませんが、数十名以上のチーム規模の場合に個人の裁量を過度に大きくしてしまうと統制が取れなくなり、将来的に開発スピードが低下する可能性が高くなります。 以下では、大規模なリポジトリで円滑なチーム開発を続けていくために工夫している取り組みと、今後の課題について紹介していきます。 工夫している点 Django は MTV という、一般的な MVC フレームワークに似たアーキテクチャを採用しているフレームワークです。 これについては公式ドキュメントでも言及されています。 FAQ: General | Django documentation | Django 以降は Django の MTV に対応した用語で説明します。 レイヤードアーキテクチャ 一般的な MTV アーキテクチャにおいて、ドメインロジックを実装するのは View とされることが多いです。 多くのケースでは、標準のままの Django でも拡張性と保守性を保ちながら開発していくことが可能だと思います。 しかし、大規模なコードベースにおいて多くの処理が View に集中すると、全体の見通しが悪くなり保守性が低下してしまいます。いわゆる Fat Controller(MTV でいうと Fat View)です。 そこで RevComm では個別のドメインロジックを Usecase 層 として View 層から切り離し、View を薄い層として保つようにしています(Usecase 層は View 層と Model 層の間に位置する)。 社内 TechTalk の発表資料より抜粋 ちなみに MiiTel Analytics のプロジェクトは Django に加えて Django REST framework(DRF)も使用しているため UI 層は存在せず、フロントエンドアプリケーションの別リポジトリもあります。 CI/CD の整備 多くのメンバーが関わるリポジトリにおいて運用性と保守性、そして開発利便性の観点から CI/CD を整備することは重要です。 RevComm のリポジトリでは下記のように CI/CD を整備しており、主なツールに GitHub Actions を利用しています。 CI Docker コンテナイメージをビルドする ビルドしたコンテナイメージを使ってユニットテストを実行する CD 特定ブランチへのマージを起点に、AWS 環境へのデプロイを実行 GitHub Actions の活用例については過去の記事でも紹介していますので、ぜひご覧ください。 MiiTel Analytics 開発チームの CI / CD ツール活用を紹介します。 - RevComm Tech Blog エディタ拡張機能の共通化 開発に有用な拡張機能やユーティリティもリポジトリに含めて、チームメンバー間で共有できるようにしています。 (Visual Studio Code を使っているメンバーが多いです) 例を挙げると、API リクエストに便利な拡張機能である REST Client と、その拡張機能で使用する設定ファイル(.http ファイル)をメンバー間で共有しています。 $ tree .vscode ├── extensions.json └── settings.json http ├── my-request.http ├── … .vscode/extensions.json { " recommendations ": [ " humao.rest-client ", … ] } .vscode/extensions.json を活用すると VSCode の拡張機能をプロジェクト内で共通化できて便利です。 課題と対応案 以上はすでに工夫している点でしたが、もちろん現状課題となっている点もあります。 課題点と(採用するかはさておき)取りうる対応案について考えてみます。 アプリケーション起動の複雑性 アプリケーション設定値を環境ごとに使い分けるために環境変数を使用することはよくあると思います。 ところが、大規模な Django ではどうしても必要になる環境変数が増えてきます。 必要な環境変数は .env ファイル化しておけば Docker コンテナ起動時の --env-file オプションで一括で読み込むことができます。 一方で、エディターと連携してデバッガーを起動したり、ユニットテストのためにローカル環境(=非 Docker コンテナ環境)でアプリケーションを起動したりしたいことがあります。 .env ファイルの環境変数を都度 export するのは面倒です。 シェルのワンライナーも考えられますが 、多くのメンバーがいるチームでの運用は現実的ではありません。 そこで、Docker コンテナの起動オプションと同様に、 .env ファイルから動的に環境変数を読み込む方法を考えてみます。 これを実現するには、python-dotenv ライブラリが有用です。 https://github.com/theskumar/python-dotenv $ pip3 install python-dotenv settings.py from pathlib import Path BASE_DIR = Path(__file__).resolve().parent dotenv_path = BASE_DIR / "path/to/.env" if dotenv_path.exists(): from dotenv import load_dotenv load_dotenv(dotenv_path, override= True ) … .env がある場合のみ環境変数を読み込むようにすることで、本番環境では Amazon ECS(コンテナオーケストレーター)でアプリケーション起動する挙動差異を吸収することができます。 テスト実行時間の長さ 大きなコードベースではどうしても CI(特にユニットテスト)の実行時間がネックになってきます。 並列実行 対応策としてまず考えられるのは、テストを並列実行することです。 Django 標準の test コマンド(manage.py test)で並列実行用に --parallel オプションがあり、これが有用そうです。 ※なお Django v3 系では -b / --buffer オプションと --parallel オプションは併用ができず、v4 系で可能になるようです。 同僚の松土が Django Congress 2022 で言及していました。 資料 マイグレーションのスキップ また、Django App があるリポジトリをしばらく運用しているとマイグレーション履歴も多くなってきます。 マイグレーションファイルが増えてくると当然マイグレーション実行の所要時間が長くなり、ローカルで何度もテストを実行したい場合に利便性が下がってしまいます。 そのような場合には、Django test コマンドの --keepdb オプションが有用です。 簡単なデモで動作を追ってみます。 1 つの Django App に 1 つのモデル、そして 1 つのテストケースがあります。 $ tree ├── django_project │ ├── __init__.py │ ├── settings.py │ ├── … ├── manage.py ├── myapp │ ├── __init__.py │ ├── apps.py │ ├── migrations │ ├── models.py │ ├── tests.py … myapp/models.py from django.db import models class MyModel (models.Model): name = models.CharField( "name" , max_length= 128 ) myapp/test.py from django.test import TestCase from .models import MyModel class TestmMyApp (TestCase): def test_sample (self): MyModel.objects.create(name= "hello" ) self.assertEqual(MyModel.objects.count(), 1 ) settings.py の中でテスト時にはデータベース名「test-db」を使用するようにしています。 django_project/settings.py … DATABASES = { 'default' : { "ENGINE" : "django.db.backends.postgresql_psycopg2" , "NAME" : "app" , ..., "TEST" : { "NAME" : "test-db" } } } --keepdb オプションを指定した場合とそうでない場合、それぞれ 2 回ずつテストを実行して、動作の違いを確認します。 まずは --keepdb がない場合。 $ # 1-a. 1回目 $ python manage.py test -v 2 Found 1 test ( s ) . Creating test database for alias ' default ' ( ' test-db ' ) ... Operations to perform: Apply all migrations: myapp Running migrations: Applying myapp.0001_initial... OK System check identified no issues ( 0 silenced ) . test_sample ( myapp.tests.TestmMyApp ) ... ok -------------------------------------------------------------------- -- Ran 1 test in 0 .025s OK Destroying test database for alias ' default ' ( ' test-db ' ) ... $ # 1-b. 2回目 $ python manage.py test -v 2 Found 1 test ( s ) . Creating test database for alias ' default ' ( ' test-db ' ) ... Operations to perform: Apply all migrations: myapp Running migrations: Applying myapp.0001_initial... OK System check identified no issues ( 0 silenced ) . test_sample ( myapp.tests.TestmMyApp ) ... ok -------------------------------------------------------------------- -- Ran 1 test in 0 .025s OK Destroying test database for alias ' default ' ( ' test-db ' ) ... 1-a、1-b ともに実行ログに Applying myapp.0001_initial... OK とあり、2 回ともマイグレーションが行われていることが分かります。 次に --keepdb を指定する場合。 $ # 2-a. 1回目 $ python manage.py test -v 2 --keepdb Found 1 test ( s ) . Using existing test database for alias ' default ' ( ' test-db ' ) ... Operations to perform: Apply all migrations: myapp Running migrations: Applying myapp.0001_initial... OK System check identified no issues ( 0 silenced ) . test_sample ( myapp.tests.TestmMyApp ) ... ok -------------------------------------------------------------------- -- Ran 1 test in 0 .010s OK Preserving test database for alias ' default ' ( ' test-db ' ) ... $ # 2-b. 2回目 $ python manage.py test -v 2 --keepdb Found 1 test ( s ) . Using existing test database for alias ' default ' ( ' test-db ' ) ... Operations to perform: Apply all migrations: myapp Running migrations: No migrations to apply. System check identified no issues ( 0 silenced ) . test_sample ( myapp.tests.TestmMyApp ) ... ok -------------------------------------------------------------------- -- Ran 1 test in 0 .009s OK Preserving test database for alias ' default ' ( ' test-db ' ) ... ポイントは 2-b 実行結果の中の No migrations to apply. で、2 回目のテストでマイグレーションがスキップされていることが分かります。 一方でテーブルデータはテストケースごとに初期化されるので、何度実行してもテストが通ることも分かります。 大規模なアプリケーションの開発時にローカルで何度もテストを実行したい場合におすすめです。 まとめ RevComm における 大規模な Django アプリケーションをチームで開発していくために工夫している取り組みについて紹介しました。 同じように Django で開発している方にとって参考になれば幸いです。 上記以外にも多くのノウハウがあり、一方でもちろん課題もあります。 私はプロジェクトに参画してまだ 1 か月程度ですが、改善できる点は積極的に対応して、チーム全体のパフォーマンスを高めていきたいと考えています。 RevComm のプロダクト開発には Python が広く使われており、主要プロダクトではバックエンドに Django を採用しています。 一緒にプロダクトを開発してくれるエンジニアを積極募集中です。 hrmos.co
Electron この記事は RevComm Advent Calendar 2022 の 12 日目の記事です。 はじめに 1. キルスイッチを用意する 2. パッケージに含まれる内容を確認する 3. Electron が機能を提供しているか確認する 4. キーボードショートカットを設定する 5. 署名・公証 6. ライセンス表記 7. アップデートに備える おわりに はじめに Electron は、JavaScript、HTML、CSS などの Web 技術を使用して、クロスプラットフォームのデスクトップアプリケーションを構築するためのフレームワークです。内部的には Node.js と Chromium が使用されており、OS ネイティブな機能をユーザーに提供することができます。Electron は Slack 、 Visual Studio Code 、 Discord などの多くの人気アプリケーションで使用されています。 デスクトップアプリケーションの開発には、ブラウザ上でのみ動作する Web アプリケーションの開発とは異なる注意点があります。本記事では、筆者が業務で Electron 開発中に得た知見の中から、開発をはじめる前に知っておきたかったことをまとめています。 1. キルスイッチを用意する 「キルスイッチ」は古いアプリを使用しているユーザーに対してアップデートを促す、もしくは利用を停止させる仕組みのことです。 PAGNI 法則 のひとつとしても挙げられており、ユーザーに後から提供することは難しい一方で、将来的には確実に必要になる重要な機能です。 筆者のチームでは electron-updater を使って自動アップデートの仕組みを実装し、アップデートを検知してダイアログを表示、自動更新することを可能にしました。また、WebView にアプリケーションのバージョンを連携するようにし、WebView 内のコンテンツ上で警告を表示して利用を停止させることも可能にしています。 2. パッケージに含まれる内容を確認する Electron ではソースコードを asar 形式 でパッケージングします。asar 形式は元のファイルの情報をそのまま含むので、すべてを復元することができます。ビルドツールによっては初期設定のままだとソースコードだけでなく、リポジトリ内部のすべてファイルをパッケージに含むこともあるので注意が必要です。 機密情報やパスワードを書いたファイルなどが流出しないよう、これらをパッケージに含めないように気をつけてください。 3. Electron が機能を提供しているか確認する 提供されている機能以外を使用すると OS の仕様などを考慮する必要が発生し、修正難易度が高くなるので注意が必要です。 例えば、Node.js のプロセスから通信を行う場合に「Electron axios」などで検索すると結構な数の記事が出てきますが、落とし穴があります。ブラウザであれば、Basic 認証のプロンプトを出すことや OS のプロキシ設定の読み取りなどはブラウザがサポートしている機能なので自ら実装する必要はありません。しかし、外部ライブラリを使用し独自の実装を行った場合は、それらを追加で実装する必要があります。このケースに対して、Electron は net モジュール を提供しています。net モジュールは Chromium を介した通信を行い、認証や OS 設定の読み取りを実行してくれます。 ブラウザと OS が連携されるような機能を用いる場合は、一通り公式ドキュメントに目を通して機能が提供されていないか確認しておくべきです。 4. キーボードショートカットを設定する Electron は Copy や Paste などの標準的なキーボードショートカットであっても、デフォルトの状態では追加されません。 menu モジュール を使用して明示的に追加する必要があります。 5. 署名・公証 署名は、開発者情報をアプリケーションに付与して開発元を明らかにするものです。 Windows では署名がない場合、セキュリティダイアログが表示されます。署名は Windows Authenticode に対応した証明書で行う必要があります。 macOS では署名することに加え、公証を受ける必要もあります。公証は、Apple がアプリケーションに対してセキュリティチェックを実施し、悪質なものでないかを確認することです。macOS 10.14.5 のリリース以降、署名済みアプリケーションは公証を受けなければ GateKeeper に引っ掛かり、ユーザーがインストールできないようになっています。 https://www.electronjs.org/ja/docs/latest/tutorial/code-signing https://developer.apple.com/jp/developer-id/ 6. ライセンス表記 MIT や Apache Software License などオープンソースライセンスには頒布物に著作権表記を含めることが条件に含まれています。ソースコードが閲覧可能な場合はそこに含めればよいのですが、 asar やバイナリに変換して配布した場合は、ユーザーが閲覧可能な形で別途ライセンスを公開する必要があります。筆者のチームでは、アプリケーション内のユーザーが閲覧可能なディレクトリ以下に LICENSE( yarn license にて作成) LICENSE.electron.txt(node_modules/electron/dist からコピー) LICENSES.chromium.html(node_modules/electron/dist からコピー) の 3 点をコピーしています。使用した node_modules の他に、Electron と Chromium のライセンスも含める必要があることに注意してください。 7. アップデートに備える Electron には version support policy が定められており、 Chromium が 4 週間毎にリリースされるのに合わせて 8 週間毎にリリース されることになっています。Microsoft Store に Chromium ベースのアプリケーションを公開する場合に 最新から 2 以上のメジャーバージョンの遅れは許容されない など、アップデートが必要になることがあります。 Electron を最大限に活用するために、初期の段階でテストやリリースを自動化するなど、積極的に投資していくことが望ましいです。 おわりに 以上、Electron 開発中に知った、開発をはじめる前に知りたかったことでした。 Web エンジニアが Electron に触れることは、普段ブラウザに支えられていて知らない事柄に触れるよい機会となると思います。よき Electron ライフを! www.revcomm.co.jp
この記事は RevComm Advent Calendar 2022 の 10 日目の記事です。 こんにちは!12 月 10 日のアドベントカレンダーを担当します、RevComm フロントエンドチーム所属の小山と申します。今回は、対象読者をフロントエンドの開発者として、「ペア設計」を通して、私の React に対する理解が深まったことを記事にしていきます。 ペア設計とは私の所属しているチームで実践している取り組みで、プロダクトの設計方針や懸念点を議題として、私が持っていって壁打ちをさせていただいているミーティングです。現在は週に 2 回、各 40 分程度行っており、フロントエンドチームの中でも JavaScript、TypeScript の開発経験が豊富な方にお願いしています。 ペア設計は RevComm 全体で導入されている制度ではありません。よりよいプロダクトを開発するために、このやり方で進めたいと提案して始めました。RevComm ではそれぞれのプロジェクトチームが自分たちにフィットする開発手法を創り出し実践しています。 今回の記事のテーマは、そんなペア設計の中で議題になったものです。では、そろそろ本題に入っていきましょう! コンポーネントの外でフックを使ったときに生じる Invalid hook call エラー 私の担当しているプロダクトでは、Next.js を使っており、GraphQL のクライアントとして Apollo Client を使い、状態管理に Recoil を使っています。 開発を進めていく中で、Apollo Client の状況に応じて Recoil の atom の値を変更したいケースが出てきました。これができると、バックエンド側でエラーが起きたときに通知を出したり、フォームの値を保存中に変更できないようにマスクをかけたりということが一括してできるようになります。 これは調べていくと、ApolloLink という、Apollo Client とサーバーの間でデータの流れをカスタマイズできる仕組みを使うことで実装できそうなことがわかりました。しかし、コンポーネントに反映させるところでつまづきました。 試しに下記のように ApolloLink の引数に Recoil のカスタムフックを使った関数を渡してみたところ、トランスパイルはできました。 const sampleRecoilLink = new ApolloLink (( operation , forward ) => { const setSampleState = useSetRecoilState ( sampleState ); setSampleState ( true ); return forward ( operation ) .map (( data ) => { return data ; } ); } ); しかし、実行時に以下のようなエラーがコンソールに表示されました。 Error: Invalid hook call. Hooks can only be called inside of the body of a function component. … 普段 React のコードを書いているときには遭遇しないエラーなので調べたところ、Recoil のリポジトリに同様の issue が起票されていました。 https://github.com/facebookexperimental/Recoil/issues/289 issue をみると recoil-nexus というライブラリでも解決できるようです。ただ、この問題の解決だけのために理解せずに導入したくないなという気持ちでした。その他、ここだけ Apollo Client で状態管理をするやり方も検討しましたが、あまり良いやり方ではありません。そこで、ペア設計の時間で相談することにしました。 結果として、Recoil のフックを使用するタイミングでファクトリー関数を使うことによって調整することで解決しました。 Invalid hook call エラーが発生するコード例 まず、以下が Invalid hook call エラーが発生したコード例です。 ルートの(基底となる)コンポーネント内に ApolloProvider を書いて、client (後述)を読み込ませています。Recoil も使いたいので RecoilRoot も追加しています。 function MyApp ( { Component , pageProps , router } : AppProps ) { return ( < RecoilRoot > < ApolloProvider client = { client } > < Component { ...pageProps } / > < /ApolloProvider > < /RecoilRoot > ); } client は別ファイルにて定義します。Apollo Client の以下のドキュメントを参考に実装すると、以下のようなコードになります。 https://www.apollographql.com/docs/react/data/subscriptions const httpLink = new HttpLink ( { uri: SAMPLE_HTTP_URL } ); const wsLink = new GraphQLWsLink ( createClient ( { url: SAMPLE_WS_URL } )); const splitLink = split ( ( { query } ) => { const definition = getMainDefinition ( query ); return definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ; } , wsLink , httpLink ); // 今回追加したいApolloLink const sampleRecoilLink = new ApolloLink (( operation , forward ) => { const setSampleState = useSetRecoilState ( sampleState ); // 実行時に Error: Invalid hook callとなる箇所 setSampleState ( true ); return forward ( operation ) .map (( data ) => { return data ; } ); } ); export const client = new ApolloClient ( { // ApolloLinkを追加 link: from( [ sampleRecoilLink , splitLink ] ), cache: new InMemoryCache (), } ); この書き方では setSampleState(true) の箇所で、Invalid hook call が発生します。 ファクトリー関数とフックの組み合わせ 私は原因が特定できていない状態だったのですが、ペア設計の中でヒントをもらえました。「フック を使用するのは React の中でなければダメで、Recoil の フック を使用するのは RecoilRoot コンポーネントより後でなければダメだよ」といった内容です。あまりピンとこなかったので、もうちょっと掘り下げてみると、「Recoil は基本 React 内でしか読めないので、 ApolloClient の生成をファクトリー関数で行い、Recoil の値が必要な場合は必要な情報を React 内からファクトリー関数に渡せばよい」とのこと。 聞いたときはなんとなくの理解だったのですが、手を動かしながら理解が深まりました。 // 関数に変更 const createSplitLink = () => split ( ( { query } ) => { const definition = getMainDefinition ( query ); return definition.kind === 'OperationDefinition' && definition.operation === 'subscription' ; } , wsLink , httpLink ); // 関数に変更 const createSampleRecoilLink = ( setSampleState ) => new ApolloLink (( operation , forward ) => { setSampleState ( true ); return forward ( operation ) .map (( data ) => { return data ; } ); } ); // フックとして扱うためにプレフィックスを追加。 export const useClient = () => { const setSampleState = useSetRecoilState ( sampleState ); return new ApolloClient ( { link: from( [ createSampleRecoilLink ( setSampleState ), createSplitLink () ] ), cache: new InMemoryCache (), } ); } ; sampleRecoilLink を関数にすることで、定義した段階で実行されないようになっています。その他、client をカスタムフックとして、その中で useSetRecoilState を使うように変更しています。 上記のコードでは、useClient が呼び出された段階で、Recoil の useSetRecoilState が使われます。これで、RecoilRoot 配下のコンポーネント内で使う準備ができました。 図で簡単に示すと以下のようになるかと思います。ファクトリー化とカスタムフックを利用することで、コンポーネントの内部でuseClient を実行したタイミングで Recoil の useSetRecoilValue が実行されます。 ApolloProvider を RecoilRoot の後に読み込ませるように調整 あともう少しだけルートコンポーネントの調整が必要です。RecoilRoot の中で useClient() が使われるように、コンポーネントを分離します。 function MyApp ( { Component , pageProps , router } : AppProps ) { return ( < RecoilRoot > < ApolloProviderWrapper Component = { Component } pageProps = { pageProps } router = { router } / > < /RecoilRoot > ); } const ApolloProviderWrapper = ( { Component , pageProps , router } : AppProps ) => { const client = useClient (); return ( < ApolloProvider client = { client } > < Component { ...pageProps } / > < /ApolloProvider > ); } ; こうすると、以下の図のように Recoil で状態管理をしている内部で、フックを使うという構成にすることができます。 上記の手順で RecoilRoot の内側に Recoil のフックを入れて Recoil の状態を更新ができるようになりました。これで、Apollo Client の状態をみて、通信の途中でフォームの値を変更できないよう制御できるようになります。 終わりに 今回、ペア設計でもらえたヒントを元に実装を進めたおかげで、今まで自分が意識できていなかった React のルートコンポーネント周りでの読み込み順を意識できるようになりました。 ペア設計は、自分の理解できていないことを認識できる場になりますし、その他にもこちらで用意した実装方針が状況に合わせたベターなものになっているか確認できる機会としても機能しています。議題をしっかり持っていってわからないことに向き合うことで成長でき、プロダクトに生かすことができます。自分 1 人ではできない成長ができる Happy な場だと考えて、これからも向き合っていきたいです! RevComm ではエンジニアを募集しています。このブログを読んで興味を持っていただけたら、ぜひ採用サイトをチェックしてみてください。 www.revcomm.co.jp
この記事は RevComm Advent Calendar 2022 の 9 日目の記事です。 はじめに Hello World ! サーバーサイドエンジニアの矢島です。普段は MiiTel Analytics の開発を行っています。 2022年7月に入社してすぐに新機能の開発を任されて、実務で初めて Terraform を使った開発を経験しました。 この開発は、Terraform を使うのが初めてだったことに加えて以下のような要因も重なり、試行錯誤の連続でした。 しばらく変更されていないファイルが多々あったこと M1チップの MacBookという、利用者があまり多くない環境だったこと そこでこの記事では、初めての Terraform 開発で直面したトラブルとその解決方法をまとめました。M1 Mac でなくても発生しうる内容もありますので、特に僕のような Terraform ビギナーの方に有益な記事となれば嬉しいです。 初めてのTerraform 開発でよく起きたトラブルとその解決方法 M1 Mac で使えない provider が存在する $ terraform init Error: Failed to install provider M1 Mac では、 arm64 という命令セットアーキテクチャが採用されており、Intel Mac と実行できるバイナリファイルが異なります。 そのため、更新されなくなった provider や古いバージョンの provider を使用する際は、マニュアルインストールが必要になります。 1.Go をインストール( anyenv の利用を前提とします) $ anyenv install goenv $ goenv install 1.18.4 $ go version go version go1.18.4 darwin/arm64 2.provider を clone, build $ git clone git@github.com:hashicorp/terraform-provider-template.git $ cd terraform-provider-template $ make build 3.build されたファイルを配置 $ mkdir -p ~/.terraform.d/plugins/registry.terraform.io/hashicorp/template/2.2.0/darwin_arm64 $ cp ~/go/1.18.4/bin/terraform-provider-template ~/.terraform.d/plugins/registry.terraform.io/hashicorp/template/2.2.0/darwin_arm64 これで M1 Mac でも該当の provider が使えるようになり terraform init を実行できます。 Template provider に限らず、他の provider でも同様の方法で解決できます。 既存のコードが非推奨になっている 例えば、先に出てきた Template provider は、すでに非推奨となっています。 Terraform Registry Terraform や provider は頻繁に更新されているため、既存コードが非推奨になっているケースに遭遇することもあるかと思います。余裕があれば、書き換えていくのがいいでしょう。書き換えた際には、リソース自体に差分がでていないことを確認します。 1.Template provider の記述を削除 data "template_file" "task_definitions" { template = file("./task_definitions.json") vars = { container_app_name = "app" container_app_image = “XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/app" } } 2.Templatefile function として追加 resource "aws_ecs_task_definition" "ecs_task_definition" { # 省略 container_definitions = templatefile("./task_definitions.json", { container_app_name = "app" container_app_image = “XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/app" }) } 3.差分を確認 $ terraform plan No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. Lockファイルの checksum error が発生する terraform init を実行したときに作成される lock ファイル( .terraform.lock.hcl )は下記のような形式になっています。h1 にローカルで使用しているプラットフォームのハッシュ値が、zh にprovider が配布するパッケージのハッシュ値が記録されています。 provider "registry.terraform.io/hashicorp/aws" { version = "4.45.0" constraints = ">= 4.45.0 hashes = [ "h1:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "h1:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "zh:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "zh:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", ] } 下記の checksum error はすでに lock ファイルがあり、これまで自分と同じプラットフォームの使用者がいなかった場合に発生します。 例えば M1 Mac で以下のエラーが発生した場合 darwin_arm64 が、これまでにないプラットフォームなので互換性がない状態です。 $ terraform init Error while installing hashicorp/template v2.2.0: the local package for registry.terraform.io/hashicorp/template 2.2.0 doesn't match any of the checksums previously recorded in the dependency lock file (this might be because the available checksums are for packages targeting different platforms) これを解消するには、lock ファイルを削除した後に再度 terraform init を実行する必要があります。 しかしそれだけだと、darwin_arm64 にしか互換性のない lock ファイルが生成されてしまい、その他のプラットフォームで terraform init できなくなってしまいます。 そこで以下のように、さまざまなプラットフォームに互換性のある lock ファイルを生成します。 $ terraform providers lock \ -platform=windows_amd64 \ -platform=linux_arm64 \ -platform=linux_amd64 \ -platform=darwin_amd64 \ -platform=darwin_arm64 Command: providers lock | Terraform | HashiCorp Developer 万が一 terraform providers lock の後に terraform init をしても checksum error が発生する場合、以下を実行すると解消します。 Lock ファイルを削除 terraform init をして作られたlockファイルの h1 を退避 Lock ファイルを削除後に terraform providers lock 生成された lock ファイルに退避していた h1 を追加 環境ごとに provider のバージョンに差が出る 前述の通り、Terraform や provider は頻繁に更新されているため terraform init や -upgrade 、 terraform providers lock をしたときに一気にバージョンが上がったり、他の環境とのバージョンの差が出てまうことがあります。 ローカルでこれらの操作をするときにバージョンを変えたくない場合は、 required_providers の version を一時的に固定する方法があります。 terraform { # 省略 required_providers { aws = { source = "hashicorp/aws" version = “4.45.0" } } } Provider Requirements - Configuration Language | Terraform | HashiCorp Developer 最後に 以上が初めての Terraform 開発で起きたトラブルとその解決方法でした。 Terraform 開発は tfstate 管理、IAM role やデプロイフローの設計、リモートとの差分発生の防止など、アプリケーションとは異なるチームでの開発・運用体制が求められると思います。RevComm ではスタートアップとしてアジリティが高くかつ安定した Terraform の開発・運用体制が作られており、開発体験がとてもよかったので、その辺りについても今後記事にできたらと思っています。 RevComm ではエンジニアを募集しています。このブログを読んで興味を持っていただいたら、ぜひ採用サイトをチェックしてみてください。 www.revcomm.co.jp
この記事は、RevComm Advent Calender 8日目の記事です。 はじめに こんにちは。PBX チームの山崎です。 RevComm では毎週 Tech Talk と題して社内勉強会が実施されています。 その中で Docker の hello-world というイメージの存在を知り、早速使ってみました。 $ docker run --rm hello-world Hello from Docker! This message shows that your installation appears to be working correctly. ( snip ) Hello World が表示されました。これ自体は特に面白みはありません。 ところが思いのほかイメージサイズが小さく、これはちょっと気になります。 $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 3c2df5585507 4 weeks ago 69 .2MB hello-world latest 46331d942d63 8 months ago 9 .14kB ということで調べてみました。 中身を見てみよう 普段使うイメージは Linux のユーザーランドがほぼそのまま動いているので、まずはシェルに入ってみます。 $ docker run -it --rm hello-world /bin/sh docker: Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: " /bin/sh " : stat /bin/sh: no such file or directory: unknown. 失敗しましたね。 このサイズなら当然 sh は入らないので失敗するのは妥当です。 しかし中身が分からないのは困ったなとマニュアルを眺めていると、docker image save というコマンドがありました。このコマンドはイメージを tar ball にできるとあるので早速やってみます。 $ mkdir docker-work $ cd docker-work/ $ docker image save hello-world | tar xvf - $ tree --noreport . ├── 42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79 │ ├── VERSION │ ├── json │ └── layer.tar ├── 46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4.json ├── manifest.json └── repositories 取り出せました。manifest.json が何やらメタデータっぽい雰囲気を出しているので確認してみます。 [ { " Config ": " 46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4.json ", " RepoTags ": [ " hello-world:latest " ] , " Layers ": [ " 42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79/layer.tar " ] } ] 名前から、layer.tar がファイルシステムのレイヤーかなと想像できます。解凍してみましょう。 $ cd 42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79/ $ tar xvf layer.tar x hello いかにもなファイルが出てきました。ではこれを実行してみると... $ docker run -– rm -it -v $( pwd ) :/work -w /work ubuntu root@6cbf4ec43930:/work# ./hello Hello from Docker! This message shows that your installation appears to be working correctly. ( snip ) Hello World が表示されましたね。 となると、この hello ファイルが何であるか気になります。調べてみましょう。 root@6cbf4ec43930:/work# apt update && apt install -y file binutils less root@6cbf4ec43930:/work# file hello hello: ELF 64-bit LSB executable, ARM aarch64, version 1 ( SYSV ) , statically linked, stripped root@6cbf4ec43930:/work# objdump -D hello | less static link なので、きっと libc もリンクして単一バイナリで動くようにしているのかなと思いきや、その割にはコードが小さいですね。逆アセンブルしてみるかと objdump を眺めてみても strip されているのでなかなかつらいものがあります。 ここまで調べたら答えを見てもいいでしょうと言い訳しながら答えを開いてみます。 https://github.com/docker-library/hello-world/blob/master/hello.c なるほど。システムコールを直接実行していますね。 すなわち、hello-world イメージでは単体で動作するバイナリを起動しているということがわかりました。 中身を入れ替えてみよう 中身を理解できたら、今度は書き換えてみたくなりますよね。ということで違うメッセージを表示してみましょう。 ビルド環境を整えるのが面倒なので、元の hello ファイルを書き換えることにします。 root@6cbf4ec43930:/work# apt install bsdmainutils root@6cbf4ec43930:/work# hexdump -c hello | less ( snip ) 0000c20 300 003 _ 326 \n H e l l o f r o m 0000c30 D o c k e r ! \n T h i s m e s 0000c40 s a g e s h o w s t h a t ( snip ) 0x0c30 = 3120 バイト以降を書き換えればよさそうですね。やってみましょう。 root@6cbf4ec43930:/work# echo -n ' RevComm ' | dd of =hello bs = 1 seek = 3120 count = 7 conv =notrunc root@6cbf4ec43930:/work# ./hello Hello from RevComm ( snip ) メッセージが変わりました。後片づけをして、Docker ホストに戻ります。 root@6cbf4ec43930:/work# rm layer.tar root@6cbf4ec43930:/work# tar -cf layer.tar hello root@6cbf4ec43930:/work# rm hello ここまでで、以下のようなディレクトリ構成になっています。 $ tree --noreport . ├── 42e000e434c92e9a1ddac6cccd9219b2043d53ddf81e9abbb285dc58edea4f79 │ ├── VERSION │ ├── json │ └── layer.tar ├── 46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4.json ├── manifest.json └── repositories 変更したファイルを docker image load で取り込みます。 しかしながら、このままではうまくいきませんでした $ docker image rm hello-world $ tar -c . | docker image load efb53921da33: Loading layer [==================================================>] 10.75kB/10.75kB invalid diffID for layer 0: expected "sha256:efb53921da3394806160641b72a2cbd34ca1a9a8345ac670a85a04ad3d0e3507", got "sha256:695cd43dbd538aae8230019f942d69bc53390dd7710063aae6b58cbb469414e1" sha256 ハッシュが異なると言っています。おそらく layer.tar を書き換えたので、そのハッシュ値を各所で更新する必要がありそうです。 layer.tar の sha256 の値と、元の hello-world イメージ内設定ファイルを眺めていくと一致する箇所がありました。試行錯誤したところ、以下のように書き換えると動きました。 sha256sum layer.tar の結果で、46331d(省略).json の "diff_ids" を更新する 46331d(省略).json のファイル名を、自分自身の sha256 の値に変更する manifest.json の "Config" を、2.で変更したファイル名にする でもってワクワクしながら実行してみると... $ tar -c . | docker image load 695cd43dbd53: Loading layer [==================================================>] 10.75kB/10.75kB Loaded image: hello-world:latest $ docker run --rm hello-world Hello from RevComm (snip) 動きました! まとめ 最初はなんだ Hello World かと思っていましたが、探ってみると Docker の仕組みに対する理解が深まりました。Hello World といえど奥が深いですね。 おわりに RevComm にはさまざまなバックグラウンドを持ったエンジニアが集まっています。Tech Talk の他にもさまざまな場面で知らない技術に触れ、そして業務で挑戦する機会が数多くあります。 あなたのご参加をお待ちしております! hrmos.co
この記事は、RevComm Advent Calender 7 日目の記事です。 RevComm インフラチーム所属の平島です。主にクラウド (AWS) を担当しています。 今回は、最近 RevComm で導入した AWS Support App in Slack について紹介したいと思います。 AWS Support App in Slack とは 前提条件 導入方法 IAM Role を作成する Slack channel を作成 Slack と AWS を連携させる Slack Workspace の認証 Slack channel を登録する channel に AWS Support App を招待する (補足)複数の AWS アカウントで利用する場合 使い方 ケースの起票 備考 AWS Support からの回答への対応(返信 / 解決) ケースの検索 Service Quota (上限緩和)申請 まとめ AWS Support App in Slack とは 2022 年 8 月 24 日に発表された比較的新しいサービスです( 公式ブログ )。 Slack のチャットを通じて AWS Support への問い合わせや回答への対応ができるようになります。 以前は言語の選択ができず英語でしかケースの起票ができなかったため、導入したものの社内でなかなか使ってもらえませんでした。 ところが最近、いつの間にか 日本語も選択できる ようになっていました! 日本語話者のエンジニアが使う上での障壁が 1 つなくなりました。 今後社内でのさらなる利活用が期待できそうです。 前提条件 Slack を利用していること AWS Support のプランが Business plan 以上であること 導入方法 IAM Role を作成する Slack からの AWS Support への接続を許可するための IAM Role、また role にアタッチする policy を作成します。 policy は AWS Support アプリへのアクセスの管理 - AWS Support を参考にカスタムの policy を作成してもいいですが、既に以下の AWS Managed Policy が用意されているので RevComm ではこちらを採用しました。 AWSSupportAppFullAccess AWSSupportAppReadOnlyAccess ケースの閲覧だけでなく、ケースの起票や返信をする場合は AWSSupportAppFullAccess をアタッチする必要があります。 role の 信頼関係には以下を指定します。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Sid ": "", " Effect ": " Allow ", " Principal ": { " Service ": " supportapp.amazonaws.com " } , " Action ": " sts:AssumeRole " } ] } Slack channel を作成 Slack で AWS Support App を利用する channel を作成します。 channel は public/private どちらでも利用できますが、セキュリティの観点から、 private channel を採用し関係者のみを招待して使うことが推奨 されています。 Slack と AWS を連携させる Slack Workspace の認証 AWS Management Console (以下、コンソール)から Support Center にアクセスし、左メニューから「AWS Support App in Slack」を選択します。 「Authorize workspace」をクリックすると、認証画面に遷移するので「Allow」をクリックします。 Slack channel を登録する 左メニューから Slack Configuration を選択します。 ここで Slack 上で表示するアカウント名を変えることもできます。 「Add channel」ボタンをクリックして channel の登録作業をします。 Slack workspace、 Slack channel、 Permissions (IAM Role) はそれぞれ事前に作成したものを選択します。 private channel を使う場合は Channel ID が必要になります。 Channel ID は channel のリンク URL の最後のスラッシュ以降の文字列で、 C01234A5BCD のような形式のものです。 通知設定(Notifications)はサポートの利用状況に応じて選択してください。 RevComm は今のところ問い合わせが頻発するような状況ではないため、とりあえずすべての通知を ON にしています。 channel に AWS Support App を招待する 作成した Slack channel に AWS Support app を招待する必要があります。 channel 内で /invite @awssupport を入力します。 (補足)複数の AWS アカウントで利用する場合 複数のAWS アカウントで AWS Support App in Slack を利用する場合、 アカウントごとに channel を作成する 1つの channel で複数アカウントに対応させる という選択肢が考えられますが、RevComm では後者を採用しています。 1 つの channel で複数アカウントに対応させるためにには、それぞれのアカウントで IAM Role の作成 Slack WorkSpace の認証 Slack channel の登録 が必要になります。 複数のアカウントで設定をすると、例えばケース起票の際にプルダウンでアカウントが選択できるようになります。 使い方 AWS と連携させた channel 内で、以下のコマンドをチャット欄に入力して操作します。 ケース起票: /awssupport create または /awssupport create-case ケース検索: /awssupport search または /awssupport search-case Service Quota (上限緩和)申請: /awssupport quota これらのコマンドをチャット欄に入力すると、対応する入力フォームが表示されます。 ケースの起票 /awssupport create をチャット欄に入力し送信すると、ケース起票用のモーダルが表示されます。 指示にしたがって 3 ページにわたり必要事項を埋めていきます(コンソール での起票作業の場合と全く同じ内容です)。 モーダルの 3 ページ目に 「Select Language」 のプルダウンがあるので、日本語で用件を書いた場合は「日本語」を選択してください。 最後まで入力して「Review」をクリックすると、以下のように入力内容がメッセージとして表示されます。 修正する場合は「Edit」を押すと再度モーダルが開き、編集することができます。 起票を取りやめる場合は、このメッセージ自体を削除してください。 スクリーンショットなどのファイルを添付する場合は 「Attach file」をクリック チャット欄で「+」ボタンからファイルを添付 @AWS Support のメンションをつけて送信 すると、ファイルがアップロードされ Review の内容が更新されます。 内容に問題がなければ「Create case」をクリックします。 ケースの起票に成功したら、以下のような通知が届きます。 以降、当該ケースに関するサポートとのやりとりはこのスレッドに追加で通知されます。 備考 コンソールから起票した場合でも channel に通知が届きます。 言語で English を選択した場合はオペレーターとのやりとりに Live Chat も選択でき、Slack 上で直接オペレータとチャットができるようです(こちらはまだ試していません)。 AWS Support からの回答への対応(返信 / 解決) AWS Support からの通知には「See details」のボタンが設置されています。 これをクリックすると、ケースの詳細がメッセージとして表示されます。 メッセージ下部には以下のようにボタンが設置されています。 「Reply」を押すと返信用のモーダルが表示されます。 「Resolve」または「Reopen」を押すとケースを解決または再開できます。 ケースの検索 /awssupport search をチャット欄に入力し送信すると、ケース検索のフォームが表示されます。 検索条件を入力し「Search」を押すと結果が表示されます。 検索結果の各ケースの「See details」を押すと詳細が表示され、回答への返信やケースの解決または再開ができます。 Service Quota (上限緩和)申請 /awssupport quota をチャット欄に入力し送信すると、Service Quota 申請用のモーダルが表示されます。 デフォルト・現在の設定値が両方とも表示されるのが便利です。 必要事項を記入し終えたら「Submit」を押して申請完了です。 まとめ 今回は AWS Support App in Slack の導入方法と使い方について紹介しました。 コンソール での操作と比較して感じたメリットとしては ログインの手間が省ける (ブラウザで発生する)画面遷移時のロード時間がない or 体感で少ない ケース起票者以外の関係者でもサポートからの回答がきたことに気づきやすい ケースを探す際に Slack 検索を使ってキーワード検索ができる /awssupport search ではできない といったところでしょうか。 一方で、障害や不具合の発生時にサポートに問い合わせる際は、コンソールでリソースの ID を調べつつ文章を書くことも多いです。 その場合、結局コンソールにログインするため、「ログインの手間が省ける」がメリットになる状況は限定的かもしれません。 導入したてなこともあり社内で活用されてるとはまだまだ言い難い状況ですが、より便利な使い方を模索して、多くの社内のエンジニアに使ってもらえるようにしていきたいです。
はじめに この記事は 2022 年の RevComm アドベントカレンダー 6 日目の記事です。 こんにちは。株式会社RevCommで社内向けシステム開発・運用を担当している Machida Kensuke です。 みなさんは GCP を利用したサービスの開発をされることはありますか?RevComm では基本的にクラウド基盤として AWS を採用していますが、一部では GCP も採用しています。私はデータ基盤として BigQuery を利用しているサービスの開発に参加しました。 BigQuery は他のデータウェアハウスと比較してもコスト効率が良い課金形態で、社内システムでも通話料計算やデータ分析等の用途で使用しています。 環境構築に必要なものが多岐にわたるため、開発の導入部分でとても手こずりました。 この経験から、初めて GCP や BigQuery を触る方でもすぐに着手できるようになったらいいなと思い、この記事を書くことにしました。 手順 前提条件 準備 クライアントライブラリをインストール Cloud Platform プロジェクトを作成 認証設定 クライアントを初期化 データセットを作成 テーブルを作成 BigQuery APIを使用したテーブルの CRUD 操作 やってみる では早速やっていきましょう! 前提条件 Python はインストール済みとします。 準備 BigQuery API の Cloud クライアント ライブラリをインストールする 公式のクライアントライブラリ を使用します。 ※2022/12/06現在、対応の Python バージョンは3.7~3.10 です。 pip install google-cloud-bigquery Cloud Platform プロジェクトを作成 「 プロジェクトを作成 」からプロジェクトを作成します。 プロジェクトでは、API の管理、課金の有効化、共同編集者の追加と削除、Google Cloud リソースに対する権限の管理などを行います。 認証設定 Python から作成したプロジェクトへ API アクセスできるように認証を設定します。 API へのアクセスの有効化 からAPI アクセスを有効化します。 Google Cloud のホームページ の手順にしたがって、サービスアカウントを作成します。 Google Cloud のホームページ の手順にしたがって、サービスアカウントキーを作成して service-account-key-file.json をダウンロードします。この json ファイルを GCP の認証に使用します。 service-account-key-file.json クライアントを初期化 設定が完了したので、実際に Python から BigQuery を動かしてみましょう! まずは、下記のコマンドを実行してファイルを作成します。 touch create_dataset.py 作成した create_dataset.py にライブラリのインポートと認証情報を使用して BigQuery クライアントを初期化します。 from google.cloud import bigquery from google.oauth2 import service_account # 前ステップでダウンロードした service-account-key-file.jsonのフルパスを指定 key_path = "/path/to/service-account-key-file.json" credentials = service_account.Credentials.from_service_account_file(key_path, scopes=[ "https://www.googleapis.com/auth/cloud-platform" ]) client = bigquery.Client(credentials=credentials, project=credentials.project_id) 今回は、 google.oauth2.service_account.Credentials.from_service_account_file を使用して、サービスアカウントキーファイルによる認証を行います。 データセットを作成 次に、データセットを作成します。 BigQuery におけるデータセットとは「テーブルを作成するために必要な箱」のようなものです。 参考: データセットの概要 | Google Cloud dataset_id = "{}.sample_dataset" .format(client.project) dataset = bigquery.Dataset(dataset_id) dataset.location = "asia-northeast1" client.create_dataset(dataset) 前述したクライアントの初期化を含めたソースコード全体は下記のとおりです。 from google.cloud import bigquery from google.oauth2 import service_account def init_client () -> bigquery.Client: key_path = "/path/to/service-account-key-file.json" credentials = service_account.Credentials.from_service_account_file( key_path, scopes=[ "https://www.googleapis.com/auth/cloud-platform" ] ) return bigquery.Client(credentials=credentials, project=credentials.project_id) def create_dataset (client: bigquery.Client): dataset_id = "{}.sample_dataset" .format(client.project) dataset = bigquery.Dataset(dataset_id) dataset.location = "asia-northeast1" client.create_dataset(dataset) if __name__ == "__main__" : client = init_client() create_dataset(client) Python コマンドから上記のコードを実行します。 $ python3 create_dataset.py Cloud コンソール から作成したデータセットを確認して、 sample_dataset というデータセットが作成されていれば OK です。 データセット情報 テーブルを作成 次に、作成したデータセットの中にテーブルを作成します。 テーブルは、先ほど作成したデータセット ID とスキーマを定義することで作成できます。 from google.cloud import bigquery from google.oauth2 import service_account def create_table (client: bigquery.Client): dataset_id = "{}.sample_dataset" .format(client.project) dataset = client.get_dataset(dataset_id) table_id = "{}.{}.sample_table_name" .format(client.project, dataset.dataset_id) schema = [ bigquery.SchemaField( "name" , "STRING" , mode= "REQUIRED" ), bigquery.SchemaField( "age" , "INTEGER" , mode= "NULLABLE" ), ] table = bigquery.Table(table_id, schema=schema) client.create_table(table) if __name__ == "__main__" : # init_client() は前述のものを使用 client = init_client() create_table(client) 上記のコードを実行して、Cloud コンソールから sample_table_name という名前の空テーブルが作成されていれば OK です。定義したスキーマも設定されています。 テーブル情報 BigQuery APIを使用したテーブルの CRUD 操作 空のテーブルが作成できたところで、テーブル上のレコードを CRUD 操作してみます。 データの登録(Create) まずはデータの登録から。 方法は色々ありますが、今回は Python でよく使用される Pandas DataFrame のデータをテーブルに登録します。 参考: テーブルデータの管理 | Google Cloud データの登録は、先ほど作成したテーブルのスキーマを job_config に定義することで行えます。BigQuery のジョブとは、データの読み込み、データのエクスポート、データのクエリ、データのコピーなど、BigQuery がユーザーに代わって実行するアクションのことです。 参考: BigQuery ジョブの概要 | Google Cloud from google.cloud import bigquery from google.oauth2 import service_account def create_data (client: bigquery.Client): dataset_id = "{}.sample_dataset" .format(client.project) dataset = client.get_dataset(dataset_id) table_id = "{}.{}.sample_table_name" .format(client.project, dataset.dataset_id) schema = [ bigquery.SchemaField( "name" , bigquery.enums.SqlTypeNames.STRING, mode= "REQUIRED" ), bigquery.SchemaField( "age" , bigquery.enums.SqlTypeNames.INTEGER, mode= "NULLABLE" ), ] job_config = bigquery.LoadJobConfig(schema=schema) dataframe = pd.DataFrame( [ { "name" : "Machida" , "age" : 2 , }, { "name" : "“Kensuke”" , "age" : 5 , }, ] ) job = client.load_table_from_dataframe(dataframe, table_id, job_config=job_config) job.result() if __name__ == "__main__" : client = init_client() create_data(client) 上記のコードを実行して、Cloud コンソールから作成したテーブル sample_table_name に 2 行 2 列のレコードが作成されていれば OK です。 テーブルのプレビュー データの取得(Read) 続いて、データの取得です。 BigQuery では標準 SQL によるクエリ実行が可能です。 データの取得は、SELECT 句を使用して BigQuery クライアント ライブラリのクエリジョブで取得します。 先ほど作成した {"name": "Machida", "age": 2} と {"name": "Kensuke", "age": 5} の 2 レコードを取得します。 pd.to_dataframe() メソッドを使用すると Pandas DataFrame のデータとして取得できます。 from google.cloud import bigquery from google.oauth2 import service_account def read_data (client: bigquery.Client): dataset_id = "{}.sample_dataset" .format(client.project) dataset = client.get_dataset(dataset_id) table_id = "{}.{}.sample_table_name" .format(client.project, dataset.dataset_id) query = f """ SELECT * FROM `{table_id}` LIMIT 2; """ dataframe = ( client.query(query) .result() .to_dataframe( create_bqstorage_client= True , ) ) print (dataframe) if __name__ == "__main__" : client = init_client() read_data(client) 上記のコードを実行して、 {"name": "Machida", "age": 2} と {"name": "Kensuke", "age": 5} の Pandas DataFrame 型データを取得できていれば OK です。 データの取得 データの更新(Update) 続いて、データの更新です。データの更新は UPDATE 句を使用したクエリジョブを使用します。 先ほど作成した "name": "Machida", "age": 2 レコードの name を Yamada に更新します。 from google.cloud import bigquery from google.oauth2 import service_account def update_data (client: bigquery.Client): dataset_id = "{}.sample_dataset" .format(client.project) dataset = client.get_dataset(dataset_id) table_id = "{}.{}.sample_table_name" .format(client.project, dataset.dataset_id) query = f """ UPDATE `{table_id}` SET name = "Yamada" WHERE name = "Machida"; """ client.query(query).result() if __name__ == "__main__" : client = init_client() update_data(client) 上記のコードを実行して、Cloud コンソールから sample_table_name テーブルの {"name": "Machida", "age": 2} レコードが {"name": "Yamada", "age": 2} レコードに更新されていれば OK です。 データの削除(Delete) 最後は、データの削除です。データの削除は DELETE 句を使用したクエリジョブを使用します。 先ほど変更した "name": "Yamada", "age": 2 レコードを削除します。 from google.cloud import bigquery from google.oauth2 import service_account def delete_data (client: bigquery.Client): dataset_id = "{}.sample_dataset" .format(client.project) dataset = client.get_dataset(dataset_id) table_id = "{}.{}.sample_table_name" .format(client.project, dataset.dataset_id) query = f """ DELETE FROM `{table_id}` WHERE name = "Yamada"; """ client.query(query).result() if __name__ == "__main__" : delete_data(client) 上記のコードを実行して、Cloud コンソールから sample_table_name テーブルの "name": "Yamada", "age": 2 が削除されていれば OK です。 最後に いかがでしたか? 本記事では、 Python と BigQuery を使用した開発の始め方と基本的なテーブル操作を記述しました。本記事が Python と BigQuery を使用した開発を始める方の一助となれば幸いです。
この記事は、RevComm Advent Calender 5日目の記事です。 RevComm の渋谷といいます。MiiTel Phone Mobile のバックエンドや E2E テストなどを主に担当しています。 それとは別に、TechTalk (エンジニア主体の技術共有の場) 運営にも 2021 年 8 月頃から参加しています。 今回は TechTalk 運営企画として RevComm のエンジニアメンバーからアンケートで募集した VSCode おすすめ拡張機能の中から 10 件を選りすぐってご紹介させていただきます。 コーディング補助 Code Spell Checker - スペルチェッカー Error Lens - エラー該当行にエラーメッセージを表示 indent-rainbow - インデントの色分け Python postfix completion - Python 版後置テンプレート ビューワー・エディター audio-preview - 音声ファイルビューワー Rainbow CSV - CSV ビューワー& SQL ライク実行環境 Git Graph - Git 履歴ビューワー メモ Bookmarks - コードの指定行にブックマークを追加 open Junkfile - 指定拡張子のテンポラリファイルをワンコマンドで作成 番外編 vscode-pets - VSCode 内で飼えるペット さいごに コーディング補助 Code Spell Checker - スペルチェッカー Code Spell Checker ID: streetsidesoftware.code-spell-checker スペルチェックを行う拡張機能です。コードやコメントにタイプミスがあった場合に下線が引かれます。 PR などで指摘を受けるとちょっと恥ずかしいので、事前にチェックが入るのは嬉しいですね。 推薦者からも「タイポがなくなる!」と力強いコメントをいただきました。 ただし、チェック対象の文字列が別の意味をもつ場合には指摘されないので、やはり人の目が重要になってきます。 Error Lens - エラー該当行にエラーメッセージを表示 Error Lens ID: usernamehw.errorlens エラー該当行にエラーレベル(エラーや警告など)とエラーメッセージを描画してくれる拡張機能です。 エラーがとても見やすく表示されますので、見落としが劇的に減ります。 発表中にも早速導入してみたメンバーから「いい感じ」というコメントをいただいています。 indent-rainbow - インデントの色分け indent-rainbow ID: oderwat.indent-rainbow インデントごとに段を色分けしてくれる拡張機能です。Python や YAML など、インデントが意味をもつ言語には非常にありがたい機能です。 色の表示の仕方はライン(引用画像左側)と背景色(引用画像右側)から選ぶことができます。 発表中にも何名かのメンバーが早速導入して「素敵!」と感想をいただいています。 Python postfix completion - Python 版後置テンプレート Python postfix completion ID: filwaline.vscode-postfix-python 変数の後にドットで処理を入力するとテンプレートに従って構文を補完してくれる、いわゆる「後置テンプレート」機能拡張機能です。( 参考 ) 簡単な式を入力する際にカーソルを前後に移動させる必要がなくなります。 サジェスト機能もありますので、該当する変数に対して適用できるテンプレートを見つけやすいです。 ビューワー・エディター audio-preview - 音声ファイルビューワー audio-preview ID: sukumo28.wav-preview 音声を扱うことの多い RevComm らしいおすすめ拡張機能です。VSCode 上で音声の波形を確認しながら再生することができます。 推薦者からは「サーバー上の音声聞くのに超便利なので、 PBX と Reseach のメンバーは全員インストールしといてください!」という熱烈なコメントをいただきました。 wav、mp3 、 aac などにも対応しています。 Rainbow CSV - CSV ビューワー& SQL ライク実行環境 Rainbow CSV ID: mechatroner.rainbow-csv CSV ビューワーの拡張機能です。列別に色分けして表示したり、SQL ライクなクエリを実行して該当行のみを抽出したりすることができます。 推薦者からは「ちょっとした調査に便利。SQLに比べてできないこともありますが、GROUP BY とかできます。」とコメントをいただきました。 SQL を実行するためには CSV ファイルのエンコードを UTF-8 にする必要があります。 Git Graph - Git 履歴ビューワー Git Graph ID: mhutchie.git-graph Git の履歴をグラフで確認でき、Git 操作も行える拡張機能です。 推薦者からは「コミット履歴・ブランチ状況が手軽にグラフで見られるのでお気に入りです」とコメントをいただいています。 該当コミットのコードを VSCode 上で変更を比較できたり、ブランチに対する操作が行えます。 筆者はブランチ名を簡単にコピーできる機能が地味にお気に入りです。 メモ Bookmarks - コードの指定行にブックマークを追加 Bookmarks ID: alefragnani.Bookmarks 特定行に対してブックマークを付けられる拡張機能です。 推薦者からは「前日までの作業箇所やポイントなどを bookmark できて、どこで何をやったかが明確になる」とコメントをいただいています。 ブックマークにはラベルを付けることもできますので、どういった意図で付与したのかを後で見返すのも簡単です。 open Junkfile - 指定拡張子のテンポラリファイルをワンコマンドで作成 open Junkfile ID: hidenba.open-junkfile 日時+指定拡張子のテンポラリファイルを簡単に作れる拡張機能です。ちょっとした覚え書きやスクリプトなどに便利ですね。 こちらは発表中にメンバーからコメントで教えていただきました。 番外編 vscode-pets - VSCode 内で飼えるペット vscode-pets ID: tonybaloney.vscode-pets VSCode 内でいろいろな種類のペットを飼うことができる拡張機能です。 推薦者からは「ボールを投げて遊ぶことも出来ます」とコメントをいただきました。 コーディングに疲れた時に息抜きで遊ぶのもいいかもしれませんね。 さいごに RevComm では、このようにチームを超えて皆でナレッジを共有しながら共に良いものを作り上げていこうというチームワークがあります。 TechTalk ではほぼ毎週さまざまなテーマの発表があります。 この記事で RevComm に興味を持ってくださった方がいらっしゃいましたら、ぜひ奮ってご応募ください。 採用情報|株式会社RevComm(レブコム)
この記事は RevComm Advent Calendar 2022 の 4 日目の記事です。 RevComm で音声処理の研究開発を担当している加藤集平です。私は ADHD (注意欠陥・多動症)という障害を抱えています。私の場合は仕事をしていく上で困難がある(障害を持たない人と同じやり方では困難に直面する)のですが、それらの困難にどのように対処しようとしているのかを紹介します。また、弊社の働き方の特徴であるフルフレックス・フルリモート環境が及ぼす影響についても取り上げます。 加藤集平(かとう しゅうへい) シニアリサーチエンジニア。RevCommには2019年にジョインし、音声処理を中心とした研究開発を担当。ADHDと付き合いつつ業務に取り組む2児の父。 個人ウェブサイト X → 過去記事一覧 本記事を読むにあたっての注意 私は医師でもその他の ADHD の専門家でもありません。 ADHD に関する正確な情報は、専門家の発信をご参照ください 。 ADHD の症状(困難に直面するポイントあるいは本人の特性)は人によって異なる ことが知られています。また、同じ症状に対して同じ対処が有効とは限りません。本記事で取り上げるのは私の症状と私が実践している対処法であり、万人に通用するものではありません。 ADHD の診断は医師のみが行うことができます 。自己判断はかえって困難を増大させるおそれがあります(たとえば、症状が似た違う病気かもしれません)。 ADHD とは ADHD (注意欠陥・多動症)とは、精神障害のうち発達障害に分類されるものの一つです。発達障害とは、生まれつきみられる脳の働き方の違いにより、幼児のうちから行動面や情緒面に特徴がある状態です *1 。発達障害の中でも ADHD は不注意・多動性・衝動性の3症状を主な特徴としており、それらの症状の影響で日常生活・学業・仕事などに様々な困難が生じることがあります *2 。かつては子供だけに見られる病気と考えられていましたが、現在では大人になっても症状が継続する場合があることが知られています。 私と ADHD 私が ADHD と診断されたのは、2017 年(30 歳頃)のことでした。当時は前年に発症した強迫性障害 *3 という病気の治療のために心療内科に通っており、通院・治療の過程で ADHD であることが発覚しました。 強迫性障害とあわせて障害者手帳の交付を受けています 思えば物心ついた頃から忘れ物や物をなくすのは日常茶飯事で、部屋は常に散らかっており、コツコツ勉強することは決してなく、学校のテストではよく不注意で失点をしていました。 大人になり仕事を始めてからは、順序立てて仕事を処理することが苦手で締切に間に合わなかったり、他人に出した指示をすっかり忘れたり、単調な作業ですぐに寝てしまったり、体調に波があるために毎日 8 時間パフォーマンスを出し続けることが難しかったりして、仕事の遂行に支障をきたしていました。また、かつての勤務先では毎日オフィスに出社していたのですが、電話番をすることや周囲の話し声(雑音)が苦痛で頭がいっぱいになったりといった困難もありました。 診断を受けてからは、定期的に通院の上、服薬および日常生活の中での治療を続けています。 フルフレックス・フルリモート環境における恩恵と困難 ADHD を持つ人にとって、弊社のようなフルフレックス・フルリモート環境は適しているのでしょうか?私の場合は恩恵のほうが大きく勝りますが、フルフレックス・フルリモートならではの、オフィス出社にはない困難も感じています。これらの恩恵と困難を紹介します。 恩恵 体調の波を吸収しやすい(フルフレックス) ADHD を持つ人すべてに当てはまるわけではないと思いますが、私は体調に比較的大きな波があります。つまり調子のいい日と悪い日の仕事のパフォーマンスの差がかなり大きいです。 フルフレックスの制度下では体調に合わせて比較的柔軟に勤務時間(長さおよび時間帯)の調整ができます。当然、打合せやプロジェクトの進行状況などの制約条件があるので完全に自由に調整できるわけではありませんが、それでも毎日決まった時間に仕事をしなければならない状況よりはずっとよいです。 静かな環境で仕事ができる(フルリモート) 自宅や家族構成などの諸条件に左右されますが、オフィスよりも静かな環境を用意することができる場合があります(私は用意できています)。私の場合は雑音が多い環境が苦手なので、静かな環境は集中力を高めるのに役立っています。 困難 自主的にやる気を管理する必要がある(フルフレックス・フルリモート) フルリモート環境では、オフィスのように衆人環視の中で仕事をするわけではありません。人の目がない環境だとどうしても怠けやすくなります。しかし怠けすぎると、仕事の成果が出ず問題になります。 逆に、やる気に満ちあふれている時には過剰な長時間労働をするおそれもあります。フルフレックスの制度下では(法令の範囲内で)極端な時間の使い方をすることも不可能ではありませんが、健康の観点や、組織の一員として周囲と協調しつつ働く観点からは望ましくないでしょう。 ADHD を持つ人にはやる気のある時とない時の差が激しい人が少なくありませんが、やる気のない時に最低限のやる気を出すことと、やる気に満ちあふれている時に働きすぎないようにする工夫は、体調を整えつつ安定したパフォーマンスを出す上で重要だと考えています。 家事などの私生活と仕事のバランスを意識して取る必要がある(フルフレックス・フルリモート) フルフレックス・フルリモート環境では、仕事中にいつでも私用を挟むことができます。特に在宅勤務の場合は、仕事の合間に家事をすることは珍しくないでしょう。 ところが、ADHD を持つ人には一度集中したら他のタスクになかなか移り難い傾向のある人が少なくありません(過集中)。つまり、家事を始めたらいつまでも仕事に戻れなかったり、逆に仕事に熱中して家事が疎かになったりすることがあります。 仕事に戻れないことは当然問題になりますし、家事が疎かになることも私生活においては問題になりえます。 私が困難に対処している方法の例 「自主的にやる気を管理する必要がある」に対して 朝起きたら布団を畳む ADHD 以前の行儀の問題かもしれませんが、朝起きてしばらくしたら布団を畳みます(ベッドではなく、床に布団を敷いて寝ています)。床に布団が敷いてあっては、いつでも簡単に寝ることができてしまいます。畳んだ状態では、布団を敷くのにひと手間かかるので、簡単に寝ることができません。「布団を敷くのは面倒くさい」という ADHD ならではの感情を利用した工夫です。 仕事前に着替える 在宅勤務では、打合せがなければパジャマのままでも仕事をすることが可能です。打合せがあっても、下半身はパジャマのままでもバレません。しかし、私の場合は気持ちを仕事に切り替えるために、仕事前に必ずパジャマから着替えることにしています。オフィスに出社していれば通勤時間で気持ちを切り替える人も多いかと思いますが、在宅勤務は通勤時間がないので代わりにしっかり着替えることにしています。パジャマよりも寝心地が悪いので、安易な昼寝を防止する効果も期待できます。 筆者の仕事着の例。在宅勤務でもこのような服装で仕事をしています 専用の仕事部屋で仕事をする 誰もが実践できる方法ではありませんが、私はほぼ仕事専用の部屋を用意しています。私生活の場と空間を分けることで、仕事に対するやる気を出しやすくなります。やる気に乏しい日でも、机に座ってしまえば仕事ができることは珍しくありません。 コンテンツブロッカーを使う 会社の PC であれば不要な場合もあると思いますが、ネットサーフィン防止のために自主的にコンテンツブロッカーを設定することは有効です。在宅勤務では私物のスマートフォンをいくらでも見ることができるので、私は私物のスマートフォンにも設定しています。 適度に打合せを入れる 打合せは相手がいるので、安易に欠席することはできません。時々やってくるひどくやる気の出ない日でも、打合せ後は仕事ができることもあります。打合せを入れすぎてパニックになると本末転倒ですが、毎日適度に打合せがあることは安定したパフォーマンスを発揮するのに有効かもしれません。 労働時間をトラッキングする 以上はやる気のない時にやる気を出すための工夫でしたが、やる気に満ちあふれている時に仕事をしすぎない工夫も必要です。そのために、私は Toggl Track で労働時間をトラッキングしています。労働時間をトラッキングすることで、労働時間を毎日適当な範囲に収める助けになります。 「家事などの私生活と仕事のバランスを意識して取る必要がある」に対して リマインダーに頼る 過集中の状態になると、目の前のタスクに集中しすぎて、私生活も仕事も他のタスクを忘れがちになります。忘れてしまうこと自体は仕方ないので、私はリマインダーに頼っています。多すぎて無視してしまわない程度に、何でもリマインダーに登録しています。 私生活に関するタスクは私物のスマートフォンのリマインダーを、仕事に関するタスクは Asana (タスク)・ Google カレンダー (スケジュール)・ Slack (メッセージに対するアクション忘れ防止)などを利用しています。 私生活に関するタスクのリマインダー 私生活の時間はカレンダーをブロックしてしまう 私は昔、仕事に熱中するあまり昼食を取り損ねることがよくありました。そこで、最近は昼食の時間 (12:00 – 13:00) はカレンダーをブロックして、かつリマインダーで知らせるようにしています。 スマートスピーカーに頼る(タイマー・アラーム) 洗濯をしたのに、つい仕事に熱中して何時間も干し忘れたことはありませんか?私はあります。そんな時にはタイマーやアラームが便利です。洗濯のできあがる頃合いに設定して知らせてもらいます。最近はスマートスピーカーに声で指示を出して設定するのが便利でよく使っています。 おわりに 以上の困難や工夫は私にとって一部であり、他にも仕事・私生活を問わず様々な困難に対してさまざまな工夫を日々行っています(ADHD をお持ちの方はお分かりになるかもしれません)。また、周囲の方々の支援なくして良好な社会生活を送ることはできません。改めて周囲の方々に感謝いたします。 RevCommでは一緒に働く仲間を募集しています。詳しくは採用サイトをご覧ください。 www.revcomm.co.jp *1 : 厚生労働省による発達障害の説明 *2 : アメリカ精神医学会によるADHDの解説 *3 : 厚生労働省による強迫性障害の説明
この記事は RevComm Advent Calendar 2022 の 3 日目の記事です。前日は持田さんの「生産性の高い定例会議を行うための準備と進め方」でした。 はじめに 体制の紹介 運営としての活動 定例会議 企画会議 オフィスアワー 執筆をしてもらうために 「あなたに」書いてほしいという旨を伝えること 執筆者をリスペクトし、讃えること 無理をさせないこと、やれる人にやってもらうこと 終わりに はじめに こんにちは小島です。普段はサーバーサイドエンジニアとして MiiTel for Zoom の開発をしています。同時に、僕はこのブログ (RevComm Tech Blog) の管理人もしています。 今回は RevComm のチーム紹介のひとつとして、テックブログを運用するチーム体制や取り組みについて書きます。 体制の紹介 まずは体制です。RevComm ではテックブログは編集部制をとっています。つまり、企画の立案・執筆者のアサイン・スケジュール管理などを担うチームがあり、全員エンジニアで構成されています。現在、チームメンバーは 4 人です。 社内ではテックブログ運営と呼んでいるので、この記事でもそう表記します。 このテックブログ運営を立ち上げている時期に、僕が「我々は何をするためにいるのか」をスライド一枚でさっと定義しました。 2022 年 1 月に書いた運営の仕事を定義したスライドの原本 簡単にいうと、読者に記事を届けるためにやれることはすべてが仕事であるという定義です。 この定義のもとで、大きく 4 つの仕事をしています。 読者に興味を持ってもらうために、記事の内容をよりよくする(ネタ出し・企画立案) 読者に信頼してもらうために、記事を定期的に公開する(執筆スケジュール管理) 読者に不信感を与えないために、記事の内容を校正する(原稿のレビュー) 読者と接点を持つために、SNS などで更新の発信をする 重要なことは、どの仕事も読者を起点にしていることです。 読者にとって意味のあることでなければ、我々の仕事ではありません 。これは当たり前ですが、重要なことです。 運営としての活動 運営は主に次の 3 つの活動をしています。 定例会議(毎週) 企画会議(月1程度) オフィスアワー(ほぼ毎週) 定例会議 弊社では、Asana というタスク管理ツールを全社で採用しています。記事の管理には、 Asana のボード(カンバン)を利用しています。 記事の進捗管理ボード このボードを見ながら、執筆やレビューの状況などを週次の定例会議で管理しています。経験上、毎週必ず実施することが、進捗管理にとって最も効率がよいと感じています。 というのも、テックブログの執筆や運営は優先度がどうしても下がりやすい業務内容だからです。僕も含めて運営メンバーは普段プロダクトの開発などをしているので、ロードマップ実現やバグ修正などと比較すると、どうしてもブログ運営の仕事は後手に回りがちです。 そこで、毎週約 30 分の定例会議の時間を設けることで、その 30 分は自分たちが運営としての仕事について考えたり、忘れていたことを思い出したりします。1 回の時間は短くてもよいですが、毎週記事の進捗状況をチェックすることが重要だと捉えています。 企画会議 定例会議とは別に月に 1 回程度、企画会議を実施しています。 定例会議では記事の進捗などの細々とした確認ごとに終始してしまうので、新しい記事や読者に僕らが伝えたいことは何か?という視点で物事を考える時間を取っています。 実施した実績はまだ少なく、会議体としてはまだ模索中です。 企画会議という名前にはしていますが、記事の企画のブレストだけをテーマにするのはよくないなと考えています。すべては読者のためであるという原則に立ち戻り、運営としてどのような取り組みをするべきかや、読者や執筆者のための取り組みについても考える時間にしています。 そこで出てきた取り組みのひとつがオフィスアワーです。 オフィスアワー 企画会議で、運営として記事の執筆者のサポート体制を作ることがいいのではないかという意見があがりました。そこで、ある運営メンバーに主導してもらい、毎週 1 時間オフィスアワーを開催することにしました。 一般的にオフィスアワーは大学でよく使われる用語で、教員が生徒からの質問などを受け付ける時間のことです。テックブログ運営では、技術記事や社内ドキュメントに関する相談を受け付ける場としてオフィスアワーを定義しています。 RevComm では、社内会議は主に Google Meet を利用しています。毎週決まった時間(現在は水曜の16:00)に Google Meet に集まり、執筆者の質問に答えたり、記事の構成の壁打ちをしたりしています。 また執筆者に限らず、ブログについてよもやまな質問や相談も受ける場としても機能しています。 執筆をしてもらうために 執筆者とのコミュニケーションは最も重要な仕事のひとつです。そして、人と人とのコミュニケーションなので正解はありません。執筆者によって、組織によってやるべきことは違うと思います。 とはいえ、ほとんどの状況で間違っていないと僕が確信しているスタンスがいくつかあります。この記事ではそのスタンスを 3 つ紹介します。 「あなたに」書いてほしいという旨を伝えること 企画を考える時、多くの場合は執筆者をセットで考えます。 「この記事をあなたに書いてほしい」と依頼するときに、なぜ「あなた」が選ばれたのかを説明できることが重要ですし、何よりそれが企画のキモだと思っているからです。 例えば「チームのオンボーディング内容を紹介する記事を書きたいです」と言うのと、「若手エンジニアから見たチームのオンボーディング体制をテーマにした記事を書きたいから、25 歳のあなたに書いてほしいです」と伝えるのとでは、執筆を依頼された人の納得感が違うでしょう。 そして記事のネタと執筆者がマッチしていることは、記事の品質に直結します。組織論を入社 1 ヶ月のメンバーが書くことはできませんし、採用活動などで多忙なマネージャーに最近得た技術的な学びを書いてもらうのもズレた記事になってしまうでしょう。こんな記事があればよさそうだというアイデアに対して、誰が一番そのアイデアをよりよく昇華してくれるかを考える、それが企画だと思います。 そして企画を深く考えられるからこそ、エンジニアがテックブログの運営をやる意義があると思っています。 執筆者をリスペクトし、讃えること 最初依頼から記事の完成まで、執筆者をリスペクトすることが大事だと考えています。 運営は依頼をする立場です。へりくだるのもまたおかしいですが、一緒に記事の作成プロジェクトを進める仲間だととらえ、プロダクト開発のチームメンバーに接するのと同じように接するのがよいと考えています。 また、公開後は執筆者を讃えます。記事の公開当日は社内の Slack で更新を通知し、みんなで讃えます。これに加えて、月に 1 度行われるエンジニアの全体会議で記事を執筆いただいた方の名前を挙げ、感謝するようにしています。 社内 Slack での更新の通知 記事の執筆は大変な仕事です。それを完遂した方には、重ねて感謝しましょう。 無理をさせないこと、やれる人にやってもらうこと 最後に、お互い無理にやろうとしないこと、やれる人にやってもらうことです。これが個人的に最も重要視していることです。 技術記事を書くのは簡単なことでしょうか?特にブログを運営するような人や日常的に記事を書いている人であれば、「記事なんて誰でも書ける」とさえ思っているかもしれません。 しかし、そんなことはありません。どんな文書も訓練しないと書けないし、適性もあるでしょう。また、普段から記事をよく書いている人でも、事業にインパクトのある仕事をしているときにブログ記事の執筆を優先することは難しいでしょう。誰でも指名すれば記事のひとつくらいは書ける、というのは暴論だと思います。 そして重要なことですが、発信の方法は文書執筆がすべてではありません。社内でアンケートを取ったところ、文書執筆は苦手でも登壇には意欲がある人や、スライド作りには苦手意識がない人もいました。要は、人には向き不向きがあるのです。文書執筆が得意ならテックブログで、人前でしゃべることが好きならイベント登壇で、ソフトウェア作りが得意なら OSS という形で発信してもらうのが一番いいと思うのです。 テックブログには採用広報メディアという性格もあります。採用のために何が伝わるといいのかと考えれば、僕らエンジニアが楽しく働いていることが伝わるのが一番だと考えています。とすると、無理にブログを書いてもらうことはむしろ逆効果になりかねません。 体制が未熟でブログ以外の発信の場をまだ用意できていませんが、2023 年はブログ以外の場を用意することを考えたいと思っています。 終わりに テックブログは立ち上げ初期からこの体制で運用し、少しずつ軌道に乗ってきました。最初こそ大変でしたが、徐々にテックブログは社内でも浸透してきて、最近では企画案を持ってきてくれる人や、執筆の立候補をしてくれる人も増えてきました。 立ち上げ期は越えましたが、ブログの性質上これからも継続していくことが何よりも重要です。今後もよりよい発信ができるように運営として改善していきます。そして社内の取り組みを楽しく発信できるメディア運営を目指していきます。 RevComm ではエンジニアを募集しています。このブログを読んで興味を持ってくれた方、参加したいと思ってくれた方もぜひ採用サイトをチェックしてみてください。 www.revcomm.co.jp
2022 年 12 月 9 日 (金) に開催される Developers CAREER Boost に RevComm のエンジニアの陶山嶺が登壇します。タイトルは「一歩踏み出す勇気が変える -コミュニティと出会って変わった私のエンジニア人生-」です。 イベント概要 公式サイトより引用 https://event.shoeisha.jp/devboost/20221209 さまざまな領域で新しいテクノロジーが登場している現在、ソフトウェア開発者を含め、人々の働き方も多様化しています。特定の技術に関するキャリアを歩む人もいれば、ライフステージの変化を迎えることで新たなキャリアを選択する人もいるなど、「エンジニアの生き方」と一口に言うこと自体難しくなってきています。 Developers CAREER Boostでは、自分の価値を最大限に発揮できる場所で、年齢やロールに縛られないキャリアを実現している先人たちの知見や、技術エキスパートがこれまで歩んできたキャリア戦略をお届けすることで、不確実性の高い今の時代を生き抜くスキルとキャリアをブーストします! ぜひこの機会に、これからのエンジニアの生きざまを考えてみませんか? 日程: 2022 年 12 月 9 日 (金) 会場: オンラインのみ 参加申し込みは こちら 主催: 株式会社翔泳社 CodeZine編集部 登壇情報 一歩踏み出す勇気が変える -コミュニティと出会って変わった私のエンジニア人生- PyCon JP への参加をきっかけにわたしのエンジニア人生は大きく広がり、いまでは瀬戸内海の小さな島から Python で仕事をしています。 そんな自分の人生を振り返ったとき、転機になったと感じているのは「PyCon JP との出会い」「好きな場所に住むための転職」「書籍の執筆」です。 どれも決断に迷いがなかったと言えば嘘になりますが、最初の一歩さえ踏み出してしまえば意外となんとかなるものです。 このセッションでは、わたしがコミュニティ活動から得た様々なものを紹介し、「自分も一歩踏み出してみよう」と思えるセッションを目指します。 日時: 2022年 12 月 9 日 (金) 15:25 ~ 15:55 (B-7) 登壇者: 陶山 嶺 リンク: https://event.shoeisha.jp/devboost/20221209/session/4123/ 最後に RevComm は電話営業や顧客応対を可視化する音声解析AI搭載型のクラウドIP電話「MiiTel(ミーテル)」を開発しています。 プロダクトの開発において Web アプリケーション、機械学習/深層学習などの領域で Python が広く使用されており、Django も採用しています。 「コミュニケーションを再発明し人が人を想う社会を創る」というミッションを達成するべく、一緒にプロダクトを開発して頂けるエンジニアを募集しています! hrmos.co
この記事は、RevComm Advent Calender 2日目の記事です。 挨拶 株式会社RevCommでバックエンドチームのマネージャーをしている持田と申します 👨 RevCommとは名前の通り、コミュニケーション ( Com munication) を再発明 ( Re in v ention) することをミッションとしている会社で、普段から相手を思い遣った交流を大事にしています。 私がPMとして定例会議の運営をしているオンライン会議ソリューションの開発プロジェクトでも、ミッションを反映して、より生産性の高い会議を開催できるように制度を整えてきたので、その知見を共有したいと思います💡 準備編 故事成語でも 事予則立 という言葉があります。全てのことは、事前によく考えて準備すればうまくいくという意味の言葉です。もちろん定例会議の前にも、準備をすることが役立ちます。 毎週行うMTGのために都度議事録用のファイルを作成するのは手間です。普段から利用している タスク管理ツールに、自分がアサインされているタスク 1 の情報を書き込むようにします。 「開発環境にリリース済み。テストケース作成中」というような具合です。 タスクに情報を集めることが重要です。 もし、あなたのプロジェクトで定例会議の為だけに資料を作成しているのであれば、極力辞めるべきです。タスクに情報を集めておくことで、アサイニーが転職したり体調を崩したりしても、タスクに関わる情報をうまく引き継ぐことができます。年末休暇明けに自分のタスクを思い出す場合にも役に立ちます。 弊社では、多くのプロジェクトで Asana を採用しています。 全員がAsanaを使うことを強制されているわけではありません。例えば、 Github Project を使っているプロジェクトも存在します。これはメンバーの話し合いで決められています。 いずれのツールも得意不得意があり、選択は会社によって異なるかと思いますが、できるだけ会社で1つのタスク管理ツールを採用することをオススメします。統一することで、プロジェクト間をまたがるタスクの依存関係が定義できたり、自動的な相互リンク、共通のマイルストーンの設定などが可能になるためです。 2 弊社でもAsanaの利用に向けて移行が活発になってきています。 また、採用するツールが決まっていても、プロジェクトとしての運用方針を決めて説明するドキュメントや、運用方針を強制する仕組みが無いと、できることが多すぎて中々メンバーに使ってもらえません。 下記のようなカスタム機能で、運用方法がわかるようにしておくと自律的な更新が期待できます。 GitHubの Issue テンプレート Asanaのタスクテンプレート、ルール あくまで私のプロジェクトの一例ですが、下記のようにAsanaテンプレートを設定することで必要なサブタスクが生成されます (もちろん、状況に応じてテンプレートを使用しないこともできます)。 こうすることで、タスクの進行状況がわかりやすく、副次的に新たにプロジェクトに参加したメンバーでも必要な作業がわかるような仕組みになっています。 進め方編 定例会議では、作業中のタスクについて困っていることが無いか、俯瞰的に見てスケジュール通りに進捗しているかが主な議題になります。 作業中のタスクについて困っていることが無いか確認する 各メンバーに準備の段階でタスクに情報を追記してもらっているので、画面共有で更新されたタスクを表示します。 Asanaには、 最終更新日 > 7日前 というタスクのフィルタがあるので、この結果 3 を画面に映し 4 、各担当者に更新内容を必要に応じて読み上げてもらいます。 この際に相談や決定事項、必要アクションが新たに言及されれば、タスクに書き込みます。誰かが書き込んでいる間、参加者には少し待ってもらってください。後で書くと言って忘れてしまうよりは、情報を残すことに少し時間をかける方が良い時間の使い方ではないでしょうか。 スケジュールの確認 スケジュールに関しては、 どれくらいのプレッシャーがあるか? 参加者は誰か? 上記によって説明量と説明の仕方が大きく変わるかと思います。 AsanaやGitHubに書き起こされたタスクは粒度が細かいため 5 、期初に定義したロードマップ 6 に対しての粒度が大きいレベルで進捗が分かるように意識しています。 リファクタリングや変数名などの話を聞いて疲れている😇ビジネスサイドのメンバーには、ここだけは集中して聞いてもらうようにします。場合によっては、こちらの話題を先に出す方がいいかもしれません。 この際、我々のプロジェクトでは期初に発表した Google スプレッドシートのガントチャートに赤黄青信号を示す色をつけるだけにしています。 まとめ 定例会議の前に、進捗をメモしておきましょう タスクや Issue (チケット) に情報を集めましょう Slackでは見失いやすいです 会議のためだけの資料は読み返すのが大変です タスク管理ツールの使い方を定義しましょう。ルールを書くだけでなく、使い方を制限するよう設定しましょう タスク管理ツールを画面に投影しましょう 参加者に応じて、情報共有の粒度を分けましょう 期初のコミットのトラッキングをしましょう GitHubを使っているなら、Issueと読み替えてください 。 ↩ Unito などのサードパーティの提供するタスク管理ツールの同期ツールを使えばAsanaとGitHub Issueの両方でタスクを管理するハードルは下がりますが、各ツールに属するユーザーのマスターデータなど連携不可能なフィールドも存在するため、やはり1つのツールを使うことが望ましいです。 ↩ 冒頭の準備編でコメントを追加していれば、フィルタ結果でタスクが表示されます。 ↩ タスク管理ツールを画面に映すことは、「毎週画面に映すので必ず更新してくださいね」というメッセージにもなります。タスク管理ツールを使っているけれども更新が少ない && 定例会議で使っていないというチームにはとても有効です。 ↩ これは会社によって実施是非が異なると思いますが、半期や四半期ごとにチームやプロジェクトの外に向かって機能の追加予定を発表することを示しています ↩ 例えば、ビジネスサイドのメンバーに「Refactor: isPositiveフラグを削除」というタスクを見せることにはあまり意味がない。 ↩
この記事は、RevComm Advent Calender 1日目の記事です。 今日から RevComm もアドベントカレンダーをはじめます。12/1~12/25の間、技術や研究、開発組織について毎日記事を投稿していきます。お楽しみに。 今年のアドベントカレンダーの初日の記事では、RevComm のリサーチディレクターの橋本がスタートアップ 企業における研究開発(Research & Development)の意義について書いていきます。 RevComm が提供するサービス まずは、RevComm はどんな企業なのか、どんなサービスを提供しているのか紹介します。私たちは、インサイドセールスやオンライン会議でのコミュニケーションを支援するSaaSサービス「MiiTel」と「MiiTel for Zoom」を提供しています。 MiiTel MiiTel はクラウド型の IP 電話サービスで、デスクトップアプリ、スマートフォンアプリ、ブラウザから電話をかけることができます。 MiiTel 特徴的なのは、 通話内容が音声データとしてクラウド上に保存される 音声認識による文字起こし コミュニケーションスキルの可視化 などができることです。電話で話した内容を本人もしくはチーム内のメンバーが簡単に情報共有できるようにすること、 電話営業を解析して可視化、定量化することで情報共有の効率化、新人教育やセルフコーチングに活用することができます。 MiiTel for Zoom MiiTel for Zoom はオンライン会議サービスZoomで行った会議に対して解析を行うサービスです。会議の録画、議事録の作成、会議の中でのトピックの抽出などができます。多くの技術は、MiiTel から転用しています。 MiiTel for Zoom なぜ研究開発をする必要があるのか? RevComm のように事業を主体としたスタートアップ企業においては、短期的な製品やサービスの成長が最重要課題であり、中長期的にしか成果が得られない研究開発(特に基礎研究)に力を入れるべきではないと考えている方も多いと思います。しかし、私はディープラーニングを主体とした機械学習およびAI技術をコアとした事業であるならば、積極的に基礎的な研究開発に力を注ぐ方が良いと考えています。その理由は3つあります。 先進技術のキャッチアップ 1つ目の理由は、新技術による破壊的イノベーションを素早く発明するために、先進的な技術の動向に常日頃からアンテナを張っている必要があるためです。新技術や新アルゴリズムは論文やテクニカルレポートとして公開されることが多いです。そのような論文からの情報収集を片手間な活動とするのではなく、きちんと日常的な業務として自然と情報が集まることが重要です。そのためには、研究者やエンジニアが基礎的な研究調査に時間を使うことは意味があります。ただし、ある特定の技術分野やタスクにのみ注力してしまわないように注意することも大事で、研究分野を横断するような幅広い視野を持って情報を得るように努力することを推奨します。なぜならば、どの分野で新技術が突如発表されるかわからないからです。 もう一つの重要なポイントは、アルゴリズムの詳細を理解し、そのアルゴリズムの本質を見極めることです。論文で発表されたばかりの技術やアルゴリズムは、まだ多くの問題を抱えています。真の革新的技術かどうかを判断するためには、基礎的な研究に対する理解と見極めるスキルを持った人を育てる必要があります。   新技術の素早いビジネス化 2つ目の理由は、新技術を素早くビジネス化して既存事業を拡大するためです。今年の大きな破壊的イノベーションの一つとして「画像生成AI」があります。年初の OpenAI の「 DALL·E 2 」に始まり、Googleの「 Imagen 」、Midjourneyの「 Midjourney 」、Stability AIの「 Stable Diffusion 」など様々なサービスやツールが発表されて注目を浴びました。特に Stable Diffusion が8月に公開された後、数週間で世界中でいろいろなサービスやビジネスが生まれており、まさに変革の年だったと思います。これらの画像生成は、2006年に発表された「 Denoising Diffusion Probabilistic Models 」がコア技術ではあるのですが、サービス化に寄与したアルゴリズムのほとんどは1年以内に発表されています。もちろん、OpenAIが9月に公開した多言語音声認識器「 Whisper 」にも注目しています。 20年ぐらい前であれば、研究結果が実用化されるのは、発表後10年20年後という感覚でした。しかし、2017年の「 Transformer 」、2018年「 BERT 」、2020年「 GPT-3 」を振り返ってみると、新技術が発表後1年以内にサービス化されるということが理解できると思います。特に、スタートアップ企業は新技術をどんどん取り込んで、より魅力的な製品やサービスをユーザに届けることが重要です。こういったスピード感を持ってビジネスを推進するためには、基礎研究も重要な状況にあると思います。   人材ネットワークの拡充 最後の理由は、人材獲得のためのネットワークの拡充のためです。スタートアップ創業期でも成長期でも、とにかく優秀な人が必要です。先の理由のとおり基礎研究はスタートアップにとっても重要なので、基礎研究ができる優秀な人材を常に採用していく必要があります。 それでは、研究ができる優秀な人材とどこで出会えるかというと、研究が活発な大学の研究室や学術会議(特に国際会議)で出会うことができます。大学教員や大学生・大学院生の視野は意外と狭く、いかにビジネスやスタートアップ界隈で有名になっている企業でも知られていないことが多いです。そんな人たちに興味を持ってもらう、一緒に仕事をしてもらうためには、彼らが注目する学会で活躍するしかありません。そのためには、研究してその成果を学会で発表するということは効果的です。そうすることで、日本中、世界中の研究者の目に止まり、ネットワークを拡大していくことができます。 RevComm が取り組む研究分野 RevComm は、人と人とのコミュニケーションを効率化するAI技術を開発するにあたって、以下のような分野の研究を進めています。 音声信号処理 音声認識 音声感情認識 話者分離・話者認識 保留音・留守番電話判定 音声合成 声質変換 自然言語処理 対話要約 トピック抽出 固有表現抽出・個人情報マスキング マルチモーダル 音声からの顔画像生成 自動応対エージェント 2022年の研究実績 RevComm では、自社での研究開発に加えて、筑波大学、京都大学、九州工業大学との共同研究を行っています。2022年の研究実績としては、国内外の学術会議に7本、国際学術ジャーナルに1本の論文を発表することができました。 情報処理学会 第84回全国大会 2022 春 会話音声から句読点付きテキストの End-to-End 認識 野崎樹文(京都大)、石塚賢吉、橋本泰一(RevComm)、河原達也(京都大) 音響学会 2022年春季研究発表会 Neutral/Emotional Speech Classification using Autoencoder and Output of Intermediate Layer in Emotion Recognizer Santoso Jennifer、Yamada Takeshi(Univ. of Tsukuba)、Ishizuka Kenkichi、Hashimoto Taiichi(RevComm)、Makino Shoji(Waseda Univ./Univ. of Tsukuba) NELE-GANの学習に用いる音声データ量および多様性の影響についての調査 加藤 集平、橋本 泰一 (RevComm) ICASSP 2022 Selective Multi-Task Learning For Speech Emotion Recognition Using Corpora Of Different Styles Heran Zhang, Masato Mimura, Tatsuya Kawahara(Kyoto Univ.), Kenkichi Ishizuka(Revcomm) INTERSPEECH 2022 End-to-end Speech-to-Punctuated-Text Recognition Jumon Nozaki, Tatsuya Kawahara(Kyoto Univ.), Kenkichi Ishizuka, Taiichi Hashimoto(Revcomm) Performance Improvement of Speech Emotion Recognition by Neutral Speech Detection Using Autoencoder and Intermediate Representation Jennifer Santoso, Takeshi Yamada(Univ. of Tsukuba), Kenkichi Ishizuka, Taiichi Hashimoto(RevComm), Shoji Makino(Waseda Univ./Univ. of Tsukuba)  APSIPA ASC 2022 Speech Emotion Recognition Based on the Reconstruction of Acoustic and Text Features in Latent Space Jennifer Santoso, Takeshi Yamada(Univ. of Tsukuba), Kenkichi Ishizuka, Taiichi Hashimoto(RevComm), Shoji Makino(Waseda Univ.//Univ. of Tsukuba)  IEEE Access Speech Emotion Recognition Based on Self-Attention Weight Correction for Acoustic and Text Features Jennifer Santoso, Takeshi Yamada(Tsukuba Univ.), Kenkichi Ishizuka, Taiichi Hashimoto(RevComm), Shoji Makino(Waseda Univ./Tsukuba Univ.)  さいごに RevComm では、音声解析、自然言語処理、画像処理の研究者や機械学習エンジニアを大募集しています。新しい技術を使ってユーザのコミュニケーションに革命を起こしませんか? Research Engineer 採用ページ: https://hrmos.co/pages/revcomm/jobs PRTIMES STORY「AIがコミュニケーションの質を可視化する。レブコムに聞く「音声DX」の未来とは?」: https://prtimes.jp/story/detail/wrVWQOieZZb
サーバーサイドエンジニアの小島孝弘です。今回は 2022 年 11 月 12 日(土)に行われた、DjangoCongress JP 2022 への登壇・参加レポートをお送りします。 manage.py 深堀り 講演ピックアップ Django 4.1 での Asynchronous 日経電子版でのDjango活用事例紹介 終わりに manage.py 深堀り 弊社からは 1 名、サーバーサイドエンジニアの松土慎太郎が登壇しました。以下、 DjangoCongress JP の公式サイト より引用します。 Django 管理コマンド manage.py を深掘り SHINTARO Matsudo manage.py は Django プロジェクトに欠かせない、様々な操作を行うためのコマンドラインユーティリティです。 本発表では、manage.py の内部実装を追いかけていき、どのようなことが行われているか見ていきます。 また、デフォルトコマンド (check, migrate, runserver, shell, testなど) の解説や、カスタムコマンドを非同期処理やバッチ処理で使用する事例を紹介します。 みなさんのハッピーDjango開発ライフの一助になれば幸いです。 docs.google.com 登壇では、manage.py の実装を追いかけることに始まり、Django 4 系で新しく登場するコマンドや機能の紹介、カスタムコマンドの弊社での利用例の紹介をしました。 登壇の様子 以下、登壇した松土からのコメントです。 今回、外部登壇を初めて経験させていただきました。 まずサポート、アドバイスしてくださった同僚たちに感謝したいです。当日も応援に来てくれて心強かったです。 登壇するからには、聞いてくれる人に一つでも新しい情報や気づきを持ち帰ってもらいたく、できるかぎり考え、調べ、準備しました。 また、RevCommでは、TechTalkという社内勉強会を毎週開催しており、事前にそこで発表したことによって、登壇に向けての課題を洗い出すことができました。その準備のおかげで、当日は良い発表ができたと思います。 しかし発表をしたこと以上に、みなさんの反応から得られたことがたくさんあったと感じています。聞いてくれた方、質問をしてくれた方、Twitterで反応してくれた方、ヒント・アドバイスを下さった方、多くの方から学ぶことができました。 協力してくださった方々に報いるために、また来年も登壇を目指してさらに良い準備をしていきたいと思います。 最後にこの場を用意してくださったスポンサー、スタッフの皆様に改めて感謝をお伝えしたいです。ありがとうございました。とても楽しかったです! 講演ピックアップ 筆者の小島が聞いたトークをいくつかピックアップしてご紹介します。 Django 4.1 での Asynchronous speakerdeck.com Junya Fukuda さんのトークで、Django 4.1 での非同期対応の内容と、なぜそのような実装になったのかの背景を解説でした。 4.1 での非同期対応は公式ドキュメントに以下のように書かれています。 Note that, at this stage, the underlying database operations remain synchronous (拙訳: この段階では、基礎のデータベース操作は同期処理のままであることに注意してください) この注意を読んで僕自身も「結局、非同期対応したの?してないの?」と混乱していました。それが今回の Fukuda さんの発表を聞いて整理されて感激しました。 日経電子版でのDjango活用事例紹介 speakerdeck.com 7 年間(!)もの Django での開発実績があるという日経新聞社さんの発表でした。 長い年月を経ているからこその依存関係管理や Lint などのツールの変遷、研修での工夫、社内での運用から得た Tips 集など、現場ならではの知見が豊富でした。 Tips 集の中には負荷を下げる工夫やアクセスが集中したときの対処など、運用課題に関するものが多く含まれていました。 特に HTTPS のコネクションを使い回すことで CPU の利用率を下げたという話題があり、複数のサービスにアクセスする MiiTel でもこの対策は使えそうだと感じました。早速、対応を検討しようと思っています。 終わりに 今回、個人的にも久しぶりのオフラインの技術イベントに参加しました。その場でトークを聞き、休憩時間や廊下での雑談も含め、とても密度の濃い時間を過ごしました。発表者の方に直接質問をしたり、感謝を伝えたりできて、ひとりの参加者としてとても嬉しかったです。 知らなかったライブラリや機能、各社での実践例や運用例などたくさん伺うことができて本当によかったです。 主催、運営スタッフ、スポンサー・会場提供をしてくださった日経新聞社の方々、そして参加者のみなさん。本当にありがとうございました。 RevComm では、今後も Python や Django のコミュニティへの発信に力を入れていきます。また、一緒に盛り上げていける仲間も募集しています。もし興味がありましたら、ぜひ以下の採用サイトをご覧ください。 www.revcomm.co.jp
TL;DR🤩 音声認識器Whisperの認識精度と認識速度について調査 認識精度 英語では論文同様の結果 日本語の認識精度はドメインに依存 baseモデルの推論がドメインにより不安定 ビームサーチの利用により、推論の頑健性が向上 largeモデルのCERはbaseモデルの半分程度 認識速度 baseモデルのRTFはGPUで0.104 largeのRTFは0.408 バッチサイズなどを最適化することで改善 こんにちは。RevCommのリサーチチームでインターンをしている中田亘です。 2022年9月21日にOpenAIからWhisperと呼ばれる音声認識器が 一般に公開 されました。今回は、Whisperの性能に関して調査を行ったので紹介します。 TL;DR🤩 Whisperとは 実験と結果 実験条件 認識精度 英語でのWER LibriSpeech test-clean test-other Earnings-21 日本語でのCER base, largeの比較 他のシステムとの比較 revcomm-test-setにおけるビームサーチ利用の効果 認識速度 モデルごとのRTF baseモデルにおけるバッチサイズによるRTFの変化 ビームサイズによるRTFの変化 まとめ Whisperとは Whisperは、OpenAIが発表した「 Robust Speech Recognition via Large-Scale Weak Supervision 」で提案された音声認識器です。従来の音声認識器は数千から数万時間の音声データで学習したものが主流でしたが、Whisperは68万時間もの音声データをWeb上から収集し、Transformerで学習しているところが大きな特徴です。また、学習の際には音声認識に限らず、翻訳、VAD(音声区間検出)、アライメントなどの様々なタスクで学習されています。これにより、様々な用途に適用可能な汎用モデルが学習できていると報告されています。今回は、Whisperの音声認識の性能に焦点を当て、調査を行いました。 実験と結果 実験では、AWS上にCPUインスタンス・GPUインスタンスを構築し、 OpenAIが提供している baseモデルおよびlargeモデルの認識精度・認識速度について調査を行いました。 実験条件 特筆しない限り、実験条件は下表の通りです。 EC2インスタンス CPU: c5.9xlarge (36CPU) 推論には32CPUを使用 GPU: g4dn.xlarge (4CPU, NVIDIA T4 16GB VRAM) 認識精度テストセット 英語 LibriSpeech test-clean, test-other (オーディオブック読み上げ音声) Earnings-21 (決算説明会の音声) 日本語 TED 講演音声 https://github.com/laboroai/TEDxJP-10K Common Voice Mozilla Common Voice ( https://commonvoice.mozilla.org/ja )の日本語音声データから27時間分の音声データを選んだもの revcomm-test-set (RevComm内製のデータ。通話音声、ビデオ会議音声) 認識速度テストセット revcomm-video (revcomm-test-setのうち、ビデオ会議音声のサブセット 日本語8.5時間) バッチサイズ 1 デコーディング手法 貪欲法 テキストの正規化 英語: Whisper公式実装で使用されている手法 日本語: 句読点除去 認識精度 Whisperの推論をGPUインスタンスで行い、その認識精度について調査しました。 英語でのWER 英語では、baseモデルおよびlargeモデルのword error rate(WER; 単語誤り率)を調査しました。 LibriSpeech test-clean test-other 図1 LibriSpeech test-clean test-otherにおけるbase, base.en, largeモデルによる推論結果のWER まず、図1にLibriSpeech test-clean, test-otherにおける結果を示します。この結果は、論文で示されている結果と一致しています。( 論文 Appendix D.1.1参照)WERの傾向としてbase > base.en > largeが確認されました。 Earnings-21 図2 Earnings21におけるbase, largeによる推論結果のWER 次にEarning-21における結果を図2に示します。Earnings-21は数十分の単一音声ファイルからなります。一方で、Whisperは30秒以下の音声で学習しているため、通常の推論は行えません。この問題に対して、論文では音声に 窓 を適用して30秒に切った後に、認識結果に応じて窓をスライドさせる手法が使用されています( 論文 3.8節参照)。Earnings-21でのWERですが、論文に示されているものより少し悪い結果となっています。原因としては、テキスト標準化手法の違いなどが考えられます。 日本語でのCER 日本語では、largeモデルおよびbaseモデル双方のcharacter error rate(CER; 文字誤り率)を調査しました。 base, largeの比較 図3 各日本語テストセットにおけるbaseモデル、largeモデルの性能 まず、各日本語テストセットにおける性能を図3に示します。こちらに関してもbaseモデルに比べlargeモデルが優れた性能を示しています。TEDとCommon Voiceに注目してみると、baseモデルのCERは低くないものの、largeモデルのCERはかなり低くなっています。一方でrevcomm-test-setに関しては、base、 largeともに非常に悪い認識精度となっています。baseモデルの認識結果を確認してみると、以下のような結果が散見されました。 おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで、おかげで これはTransformerなどの自己回帰型モデルで時折発生する、リピーティング(繰り返し)という現象です。revcomm-test-setのドメインがWhisperの学習に使用されたドメインと異なるため、出力が不安定になっていると考えられます。これに関しては、ビームサーチの利用により大幅にCERが改善することを後ほど紹介します。 論文 では、日本語のCommon Voice9においてCERが0.094、日本語のFLEURSにおいてCERが0.064と報告されていますが、我々が用いたテストセットのうち、特にrevcomm-test-setの認識精度では論文中にあるような低いCERにはなりませんでした。 他のシステムとの比較 広く一般に音声認識サービスを展開している他社の音声認識器を用いて、TEDとCommon VoiceにおいてCERの比較を行いました。図4-6 に、他社の音声認識器とbaseモデル (whisper-base) 、largeモデル (whisper-large) の比較結果を示します。 図4 TEDにおけるbaseモデル、largeモデルおよび他社の音声認識機の比較 図5 CommonVoiceにおけるbaseモデル、largeモデルおよび他社の音声認識機の比較 図6 縦軸をCommonVoice横軸をTEDのCERとした他社の音声認識機の比較 この結果をみると、whisper-baseの性能は奮わないものの、whisper-largeがTED・Common Voiceの双方において優れた性能を示しています。日本語のASRをしたい時に、whisper-largeは有力な選択肢であると言えるでしょう。 revcomm-test-setにおけるビームサーチ利用の効果 先ほど、revcomm-test-setにおいてCERが非常に悪くなる結果を紹介しました。この結果を踏まえて、デコーディング手法にビームサーチを利用することで認識精度が改善しないか検討を行いました。結果を図7に示します。 図7 ビームサーチ利用によるrevcomm-test-setにおけるCERの変化 図7の結果を見ると、baseモデルでは貪欲法 (greedy) と比較して、ビームサーチを利用することによりCERが大幅に改善していることが確認できます。推論結果を見ても、先ほどのようなリピーティングはほとんど確認されず、推論の頑健性が向上していることが確認できました。このことから、baseモデルを用いたrevcomm-test-setの推論において、ビームサーチの利用がかなり有効であると言えます。一方で、largeモデルに関しては、改善はしているもののbaseモデルのような大幅な改善は見られませんでした。 ビームサイズ=2の時のlargeモデルの出力を確認してみると、以下のようなものが確認されました。 正解文 Whisper largeモデルによる認識結果 (ビームサイズ 2) あのーまあこちらーどういう風に使うかっていうとこなんですけどもーあの実はーここあのヒートマップになってて色が濃いところっていうのはー電話がこう通じた時通電した時はーあのこう色がどんどん濃くなっていう感じ回数が多くなった こちらはどういうふうに使うかというところなんですけども、実はここヒートマップになっていて色が濃いところというのは電話が通じたとき、通電したときは色がどんどん濃くなっていく。回数が多くなる。 この例からは、「あのー」「まあ」などの言いよどみ(フィラー)が、whisper-largeの認識結果に含まれていないことが確認できます。これは、whisperの学習に用いられたデータセットにフィラーがほとんど含まれておらず、推論結果にフィラーが含まれにくくなっているためと推測されます。一方で、revcomm-test-setの正解文は、通話音声やビデオ会議の発話を聞こえたとおりに書き起こしたものであり、フィラーが多く含まれます。フィラーを書き起こしていないことが、CERのボトルネックとなっていると考えられます。 認識速度 認識速度に関しては、revcomm-test-setのサブセットであるrevcomm-videoの推論におけるreal time factor (RTF) を調査しました。RTFは以下の式で求められます。 本記事では、inferとglobalの2種類のRTFを調査しました。それぞれ定義は以下のとおりです。 infer: モデルの順伝播のみにかかった時間 global : 音声のデコード、メルスペクトログラム生成などの推論に必要な処理すべてを合計した時間 注意点: 今回の推論では、全ての音声を30秒にゼロパディングしています。これは、Whisper公式実装では、30秒以外の音声は入力できないようになっているためです。 モデルごとのRTF 図8 CPUインスタンス、GPUインスタンスにおけるbaseモデル、largeモデルの認識速度 CPUインスタンス、GPUインスタンスにおける各モデルのRTFを図8に示します。まず、GPUでの推論により大幅にRTFが改善することが分かります。特にlargeモデルでは、globalにおいてGPUインスタンスを利用することにより2.483から0.408へと改善しています。 また、GPUではinferとglobalでRTFが大きく異なることが確認されました。これは、CPUでデコードした音声をGPUに移動させる際の通信などが原因となっていると考えられます。こういった通信のオーバーヘッドは、処理を非同期化することにより改善できる可能性があります。 baseモデルにおけるバッチサイズによるRTFの変化 以上の実験ではバッチサイズを1としていましたが、GPUでのbaseモデル推論においてGPUの使用率を確認したところ40%程度であり、GPUの能力を最大限に利用できていませんでした。バッチサイズをより大きくすれば、さらに効率よく推論をできるのではと考え、バッチサイズによるRTFの変化を調査しました。結果を図9に示します。 図9 バッチサイズによるRTFの変化 結果から、バッチサイズ8程度が一番効率よく推論が行えることが確認できました。ただし、処理の非同期化などにより最適なバッチサイズは変化すると思われます。 ビームサイズによるRTFの変化 先ほどビームサーチの利用により、ドメインにより不安定なbaseモデルの推論がより頑健になることを紹介しました。一方で、ビームサーチを使用することにより処理が増えるため推論時間が長くなると考えられます。そこで、ビームサーチを使用することでどの程度処理時間が増えるかを調査しました。なお、推論はGPUインスタンスで行いました。結果を図10に示します。 図10 ビームサイズによるRTFの変化 まず、全体の傾向としてビームサイズが大きいほど、RTFが大きくなることが確認できます。また、面白い結果として、baseモデルのglobalに関してビームサーチ利用によりRTFが改善されていることが確認できます。これは、ビームサーチを使用することによってリピーティングが改善され、Whisperの出力token長が短くなったためにRTFが改善していると推測されます。 まとめ Whisperの認識精度および認識速度について調査を行い、結果を紹介しました。 認識精度に関しては、英語ではおおむね論文通りの結果が確認されました。一方で、日本語の認識精度については、特にRevComm内製のテストセットによる評価について、論文で示されているほど低いCERにはなりませんでした。Whisperの音声認識結果にはフィラーがほとんど入らないため、対話音声におけるフィラーを聞こえたとおりに書き起こしたテキストを正解とした評価では、CERが大きな値となることがあるようです。また、baseモデルの推論において、ドメインによってはビームサーチが極めて効果的であることも確認されました。 認識速度に関してはGPUの利用が非常に効果的であることが確認された一方で、GPU利用により順伝播以外の時間が増えることが確認されました。これはCPU-GPU間のデータの移動に時間がかかっていると推測されます。