sitateru tech blog

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

2019年4月8日月曜日

地方活性化のイベントに参加してきました

4月 08, 2019
こんにちは、シタテルの藤本です。
イベントの参加のために久しぶりに東京に行きましたが、渋谷駅から出られなくなりそうになりました。
あらためてお上りさんには厳しい街であることを実感しました。



先日「スマホで見つける地方のしごと」というシンポジウムにパネラーとして
参加させていただきました。

内容を簡潔にすると「首都圏での離職と地方へUIJターンを希望する方と地方企業の
マッチングを進めて地方を活性化していこう」というイベントでした。

200名以上の方が参加され、各者の熱いプレゼンもありここから少しづつ
地方へ人材が広がっていってくれるのではないかと感じることができたイベントでした。

そんなイベントについて簡単にレポートしてみたいと思います

プレゼンター、パネラー

プレゼンター、パネラーとして以下の方々が参加されていました。

  • 求人サイト運営企業
  • 地方自治体
  • 商工会議所
  • 地方への移住者(Uターン)

イベントの大まかな流れ

以下の内容で2時間ほどのイベントでした。

  1. 厚生労働省からの本取組みにおける施策のついての説明
  2. 求人サイト運営企業からのプレゼン
  3. 地方自治体、商工会議所、移住者からのプレゼン
  4. パネルディスカッション

各者の主張について

各者の主張について個人的に印象的だったことについて書いていきます。

求人サイト運営企業

求人サイト運営企業の方からのプレゼンで印象に残ったのは以下の2点でした


  • 転職は孤独であり寄り添うことに注力したい
  • 転職は一般的になっている

転職は孤独であり寄り添うことに注力したい

転職にする時は非常に多くの「不安」と戦うことになると思いますが共有・共感者が少ないといった問題があり、そこに「賛成」と後押しすることで生き方を一緒に考えていこうといったものでした。

個人的には人材の流動性が高まることはいいことだと思っているので求人サイト側がこういったことを考えていただけるとありがたいなと思いました。

ちなみに弊社のBaseValueの一つに「よりそう」と言葉が定義されており、大きく以下の3つで構成されています。

  • リスペクトする
  • 声を聴く
  • 感動を与える
詳細が気になった方は以下を参照いただけると嬉しいです。

シタテルの目指す未来|シタテル株式会社

転職は一般的になっている

転職をポジティブに捉えているのは全世代で5割以上となっているということでした。
プレゼンターの方の言葉ですと「ポジティブ、ネガティブというより転職は一般化してきている」といわれていることがとても印象的でした。

地方自治体・商工会議所

地方自治体・商工会議所でも地方企業の魅力や人材育成・確保に向けた種々の取り組みを
されているのがすごく伝わってくるプレゼンでした。
印象的だったのは以下の3つです。

  • 人材育成・確保に向けた種々の取り組みをされている
  • コメリは新潟の上場企業だった
  • 地域資源を生かしたビジネスを展開

人材育成に向けた種々の取り組みをされている

小型ロケットの製造から打ち上げを通じた技術者の育成を行なっており、実際にロケットの打ち上げを成功されている地方がありました。

企業の枠を超えた技術者の連携や情報交換、人脈の育成などを推進されているとのことで地方を活性化していくために技術を磨いていくことは大切だなと感じました。

またUターンを促進するために東京からの無料のバスを出していたり、子供の時からものづくりフェアを実施して小学生に体験し興味を持ってもらい、ものづくりの楽しさを感じてさらに地元企業への魅力に気づいてもらうなどの取り組みをされていることも印象的でした。

コメリは新潟の上場企業だった

家の近くにコメリというホームセンターがあり、品揃えもよく安いのでよく利用させてもらっています。

その企業が新潟の三条市発祥の企業でしかも上場しているのは知りませんでした。
さらに三条市には上場しており本社をおいている企業が多くあり、人口1人あたりの上場企業数は東京、大阪についで3位とのことです。

地域資源を生かしたビジネスを展開

「もくロック」というブロックの木製玩具があるのですがこれの原材料となっているのが
地元の金属加工目メーカーが生み出しものということでした。
地元の資源を使い、地元の企業が新たなビジネスを生み出すということは素晴らしいなと思い印象的でした。

地方への移住者

広島への移住者の方でまちづくりのコンサルタントを行われている方でした。
非常に広島への熱い気持ちを持っている方で印象的だったのは以下の2点です。

  • 経営者との距離が近くなった
  • 「自分がいなくても回る」から「自分がいるから回る」

経営者との距離が近くなった

頻繁に経営者と面と向かって話す機会があり東京にいる時にはなかったことであるということでした。

「自分がいなくても回る」から「自分がいるから回る」

東京よりも人口が少なくなるので自分の役割や影響の割合が多くなるため、自分を必要とされる、頼られることが強く感じられるようになったということでした。

ぜひシタテルへ!

弊社シタテルですが本社は熊本にあります。
私も東京で13年程働いておりましたが2018年より熊本へUターンしてシタテルで働いております。

転職についての不安は色々とありましたが1つだけ挙げるとすると
前職ではほとんど管理業務を行なっており、実務でのプログラミングは5年以上していない状態でした。

そんな状態でどこまでやくに立てるか不安では、転職してまずは1年やってこれることができました。
まだまだ未熟で周りに迷惑をかけてはいますがシタテルのエンジニアの方はみんな教えたがりで優しく、わからないことがあり聞いたら、進んで色々と教えてくれるのでいつも助かっています。

私のように少しプログラムから離れてしまったがまた現場に戻りたいと考えている方もいるのではないかと思いますので、もし首都圏にて「また現場でプログラミングをしたい」、「いろんな技術に触れたい」という方、かつ「地方へ移住してみたい」という方がいらしたぜひシタテルへお話を聞きにいらしてください!

エンジニア│熊本or東京勤務 - シタテル株式会社のWeb エンジニア中途・契約・委託の求人 - Wantedly

少しだけ熊本の風景を

参加したイベント時に使用した風景写真です、熊本には自然が多く、いろんな風景を楽しむことができます。

近くの公園で桜満開になった時


家族でよく出かける天草の海

2019年3月12日火曜日

初めて見るとぎょっとするかもしれない JavaScript の構文 4 選

3月 12, 2019
こんにちは! エンジニアの諏訪です。シタテルでは主にフロントエンドを担当しています。
今日はフロントエンド制御のエンジンともいえる JavaScript に関する話題です。
1990 年代半ば Web 時代の幕開け (※) とともに誕生した JavaScript ── ほんとうの名前は ECMAScript というのだそうですが ── 今も昔も変わらずクライアント側での処理機構としては唯一無二の立ち位置を保ち続けてきました。
よく考えてみるとこれってとてもすごいことですよね!
※ 余談ですが World Wide Web の誕生から今日 (2019.03.12) でちょうど 30 年だそうです。
さて、上で「今も昔も変わらず」と書きましたが JavaScript 自身はもちろん何度も進化を重ねてきています。
その中でも ES2015 (ES6) では多くの新しい構文が追加され、処理内容によっては従来よりも大幅にシンプルな書き方ができるようになりました。
新しい構文は便利なのですが、勉強を始めたての新人やレガシーなプログラマが初めて見るとびっくりするような記法も。
今日はそんなユニークな (ちょっとぎょっとする) 記法を4つピックアップしてみました。

ドット3つ ...

JavaScript のコードで、3つ並んだドットを見たことがありませんか。
let array = [ 1, 2, 3 ]

let result = [ 0, ...array ]
こんなコードを初めて見た時は「・・・あれ〜?」と脳が一瞬フリーズしてしまうこと必至です。ちなみにだじゃれです。
こちらは スプレッド構文 (spread syntax) と呼ばれるもので、 ES2015 で導入された新しい記法です。
配列やオブジェクトの展開操作を楽にしてくれます。
展開操作って?
大雑把に言うと Array や Object の外側の括弧を取り払って丸裸にします
例えばこういうことです。さっきの Array の例だと:
let array = [ 1, 2, 3 ]

let result = [ 0, ...array ]
console.log(result)
// => [0, 1, 2, 3]
Object の場合だと:
let obj1 = { potato: 2, carrot: 3, onion: 5, niku: 8 }

let obj2 = { water: 65535, spice: Infinity }

let result = { ...obj1, ...obj2 }
console.log(result)
// => { "potato": 2, "carrot": 3, "onion": 5, "niku": 8, "water": 65535, "spice": Infinity }
といった具合です。
応用編として、メソッドの引数に使うことだって可能です:
function createUser(firstName, lastName, bornYear, bornMonth, bornDay) {
    // doSomeProcess
}

let name = ['Colonel', 'Sanders']
let birthday = [1890, 9, 9]
let user = createUser(...name, ...birthday) // => createUser('Colonel', 'Sanders', 1890, 9, 9) を実行するのと同じ

ビックリマーク2つ !!

ビックリマークひとつならおなじみの否定演算子ですが
ビックリマークふたつの記法もあるのか!!とびっくりびっくりしたことがあります。
じつはこれ別に ES2015 の構文ではなく、単に古典的な否定演算子を2つ重ねただけのものです。(本稿のほかの項目と違って JavaScript の黎明期からあるものです。)
Boolean (真偽値) 以外の値を Boolean にキャストしたいときに使うと便利です。
たとえば次のようなコード
function hasName (user) {
  return ( user.name !== undefined && user.name !== null && user.name !== '' )
}
は次のように書けます:
function hasName (user) {
  return !!user.name
}
条件判断をシンプルに書きたいという方、ぜひこの書き方を取り入れてみてください。

(おまけ) いろんな値の truthy / falsy について

ちょっと復習しておきたかったのでまとめてみました。
  • truthy (Boolean にキャストすると true となる) の例
    • true
    • 0 以外の Number (ex: 1, -1, 3.14, -273.15, 6.02e+23, etc)
    • Infinity と -Infinity
    • 空でない文字列 (例: "a", "false")
    • [] (空の Array)
    • {} (空の Object)
    • 1 == true (緩やかな比較)
  • falsy (Boolean にキャストすると false となる) の例
    • false
    • 0 (Number)
    • "" (空の String)
    • null
    • undefined
    • NaN
    • 1 === true (厳密な比較)
注目すべきポイント (落とし穴) は
  • 空文字列は falsy だが、空の配列や空のオブジェクトは truthy
  • “false” や 配列 [false] などは truthy
あたりでしょうか。すぐ忘れそう…

関数のアロー記法 () => {}

先ほど array の話をしたので今度は arrow の話をしなければという義務感を抱いています (嘘です)。
JavaScript といえば無名関数 (ラムダ式) ですが、ES2016 以前は無名関数を記述するには function という予約語を使うのが常でした。
setTimeout(function() {
    console.log('Hello!')
})
ES2015 からはアロー構文 () => {} を使って書くこともできるようになりました。
setTimeout(() => {
    console.log('Hello!')
})
function と () => {} どちらを使ってもよいのですが、1つだけ挙動の異なる点があるので注意が必要です。
何が異なるのかというと、メソッド内での this の扱いです; function () {} の場合はメソッドの呼び出し元のオブジェクトを指しますが、 () => {} の場合はメソッドが記述された文脈上のオブジェクトを指します。つまり、 function () {} の場合は this が動的に変わるのに対して () => {} の場合は this が固定されます。
ローカル変数レベルで後者のような挙動をするラムダ関数を closure というのでしたっけ...λ

テンプレートリテラル `${}`

プログラムを書いていて、動的に文字列を生成しないといけない場面でよくお世話になるのが printf のようなメソッド。メソッド名は言語によって様々ですが、世の中の大半の言語で提供されている構文です。
ところが JavaScript にはこれまで対応するものがなかったのです。そんな中 ES2015 になってようやく登場したのが「テンプレートリテラル」。
なんと文字列全体をバックチック ` で囲います。 (バックチックを約物に用いる言語ってあまりなかった気が…)
そしてプレースホルダは ${} と書きます。
let username = `山田太郎`
let message = `ようこそ、${username}さん!`
console.log(message) // => "ようこそ、山田太郎さん!"
そしてすごいのが、Ruby のヒアドキュメントよろしく改行をそのまま書くことができます
let body = `${client.name}様

いつも大変お世話になっております。
${company.name}の${staff.name}です。

${message.content}`
console.log(body) // => "山田太郎様\n\nいつも大変お世話になっております。\n〇〇株式会社の...
一点だけ残念なのが、ソース全体をインデントしてしまうと、そのインデント分の空白文字も無視されずに含められてしまうこと。そのためテンプレートリテラルで記載した部分はインデントすることができず、ソースの可読性が若干下がります。
Ruby のヒアドキュメントが上手にインデントを除去してくれるのに比べると少し物足りなく感じます。

以上 ES2015 のユニークな構文を一部ご紹介しました。
新しい記法は少し古いブラウザではほとんどサポートされていないものもあって、動かなかったらどうしよう…という不安から新しい書き方で書くことを躊躇してしまいがちですが、その不安を取り払ってくれる polyfill もちゃんとあります。
正しく使えば、より書きやすく読みやすく無理のない安全なコードに近づける新しい構文。しっかりと学び、追いつき、使いこなせるようになりたいものです。

2019年3月4日月曜日

Good Project Award 2019で最優秀賞をいただきました!

3月 04, 2019

こんにちは!シタテル株式会社UI/UXデザイナーの田仲です。
私が担当しているマイオペレーター(以下、マイオペ)という縫製工場の生産管理者向けのシステムが、「JBUG(ジェイバグ:Japan Backlog User Group)」が開催する『Backlog World 2019』内の『Good Project Award 2019』にて最優秀賞を獲得いたしました。

『Backlog World 2019』とは

株式会社ヌーラボの日本最大級のプロジェクト管理ツール”Backlog(バックログ)”のユーザーコミュニティである”JBUG(ジェイバグ)”が主催した、プロジェクト管理に関わる全ての方のための祭典です。
Backlog Worldとして2回目の今年は、「プロジェクトマネジメント×働き方改革」というテーマで、 数々のセッションやワークショップ、情報共有の場、Good Project Award(表彰イベント)などでプロジェクト管理に関する知見を相互に高め合うことを目的としています。
2019年1月26日(土)、秋葉原UDXにて開催されました。
https://backlogworld2019.jbug.info/

『Good Project Award 2019』とは

2018年〜2019年に活動したプロジェクトの課題やそれに対するアクション、その結果得られたことのストーリーを通し、プロジェクトマネジメントのヒントが共有されることを目指したアワードです。
Backlog Worldイベント内コンテンツとして「Good Project Award 2019」というピッチコンテストが開催され、来場者投票と審査員の審査により、最も素晴らしいものを表彰します。

『Good Project Award 2019』への応募経緯

アワードの存在を知ったCTOの和泉さんから「だしてみたら?」ともちかけてもらったのがきっかけです。
マイオペは2018年の頭から立ち上げ開始し、7月ごろにリリースをしました。Backlogは使用していなかったのですが、応募条件に利用有無は問われていなかった、応募することにしました。

『Good Project Award 2019』に登壇するまで

エントリーフォームより応募

2018年末頃に、プロジェクトの目的や結果、熱い想いを書きました。

ピッチコンテスト出場決定

2019年1月上旬に、アワード実行委員会より一次選考通過の連絡が届きました。

ピッチコンテスト当日

一次選考通過した他の4プロジェクトの方々と共に登壇しました。
マイオペからは、生産と開発の間をとりもつディレクションを担当している宇田川さんが発表しました。

審査員からのコメント

ソリューションについてはプロではないが工場の現場をよく知っている人が、関係者をどんどん巻き込んで共感をベースにプロジェクトを推進していたという点がユニーク。
さらに、もっとも重要だと感じたのは言語の共通化。プロジェクトはいろんな部署の人が入っているが言語の共通化は意外とスルーされているところも多いのではないか。工場もエンジニアもわかる言語の共通化やアイコンをつかうことで、年齢・カルチャー・価値観など異なる人を巻き込んでいる点が素晴らしい。最終的に、スムーズな導入に繋がったのは、細かい点まで配慮されたプロジェクトづくりがなされた結果ではないだろうか。

最後に

先日、プロダクト名が入った盾が届き、みんなで喜びました!

2019年2月28日木曜日

デザインレビューはじめました

2月 28, 2019
こんにちは、デザイナーの藤村です。デザインチームで最近デザインレビューを始めました。とても勉強になるし、やっていてデザインそのものの面白さにさらに気付かされています。チームでTryしている内容をご紹介します。

※ここで言う「レビュー」とは、リリース判断の可否を決める機能のことではなく、デザインをブラッシュアップさせるための、批評とフィードバックそのものを指しています。

開発チームの一員としてのデザイナー

シタテルの開発チームは、デザイナー1名+エンジニア数名+関係者(企画、マーケ、営業、生産など様々)という構成です。デザイナーは特定のサービスに所属しており、これまでデザインのレビューはサービスチーム内でのみ行なっていました。シニアエンジニアが実装判断した上でチームで要件定義を行い、デザイナーが仕様の叩きを作成、フィードバックをもらいながら徐々にビジュアル面を作り込んでいくという流れが主です。

開発チームでのレビューのいいところ

複数の職種で構成されているため、さまざまな視点からの批評を集めることができます。
エンジニアは機能的な破綻がないか、条件による表示を想定できているか、堅牢性を保つことができているのか、と言った視点で批評を返してくれます。お客様と向かいあっている方からは、お客様に対して自身が機能や画面説明ができるのかという視点で、マーケティング担当は有用なデータが取れるのかという視点で、それぞれフィードバックを返してくれます。

悩ましいところ

一方で、狭義の意味での”デザイン”を批評する環境がありませんでした。機能上は問題ないのに違和感がある、なんだか野暮ったい、画面を使おうとしたとき操作に迷う瞬間がある。。ちょっとした相談はデザイナー同士でしてましたが、お互い忙しそうだと遠慮してしまったり、各々がモヤモヤしながら自分ひとりで格闘していました。

デザインチームでレビューを始めた

シタテルのデザイナーは週に1回情報共有のための定例会を行なっていました。各プロジェクトの進捗を共有することを目的にしていましたが、みんなでデザイン力を上げようということで、他サービスのデザインレビューもやってみることにしました。
意見がほしいときは、以下のテンプレートを使ってGithubにissue化します。見てほしいデザイナーにアサインをして、後はみなさんの意見を待ちます。

レビューをしてみる

Zeplinにコメントをしあう形で進めました。1デザインに対して数十に及ぶ場合もありました。私自身もいざコメントをしようとすると、どこに違和感や改善点を見出したのか、なかなか言葉にすることができず思った以上に時間がかかりました。

どうしても、「こうして欲しい」という指示型のコメントになってしまうのです。「ここでは目的と違う認識をしてしまうので再考が必要だと感じました」のように、根本的な問題点を指摘するようにするには、批評者自身もそこまで潜っていかないと見つけることができないんだなと実感しました。デザインをどう見るか、焦点がどんどん精緻化していくので、とても面白く勉強になります。

3週間やってみて

デザイナー定例でみんなで振り返って以下のような感想があがりました。やってみた感覚と共に、もっとよくするためのアイディアも色々出てきました。当たり前のことなんですが、実際にやってみるって本当に大切ですね。
これからもレビューを重ねていって、私たち自身のデザインを見る目を育てながら、チームで成長できる方法をTryしていきたいと思います!

2019年2月27日水曜日

シタテルウェブサイトの多言語化

2月 27, 2019

こんにちは!
シタテル株式会社CTOの和泉です。

シタテルのウェブページはWordpressで運用されています。

https://sitateru.com/

なんと!今後の世界展開(あるのか!?)を目指して多言語化を行いました!!

images/image-1.png

多言語化するにあたってプラグインなど色々と調査した結果Polylangというプラグインがとても使いやすかったので紹介します。

Polylang

どんなプラグイン?

Wordpressウェブサイトの多言語化をサポートしてくれるプラグインです。

機能としては

  • ポストやページ、メディア、カテゴリ、タグ、メニュー、ウィジェットなどの翻訳
  • urlによる言語の切り替え
  • 言語切り替え機能がウィジェットやナビゲーションメニュー
  • プログラムによる言語判定
    などなど。

自動翻訳を組み込むこともできるようになっていますが、今回はすべて手動で翻訳しました。

軽く使い方

インストールはプラグインメニューから新規追加で行なえます。

images/image-3.png

が、今回はProバージョンの機能を使いたかったので、Polylangのウェブサイトからライセンスを購入し、Proバージョンのバイナリを入手して、圧縮ファイルをアップロードする形でインストールしました。

Proバージョンだと言語間でURL末尾が同じものにできるなどの追加機能があります。
ウェブサイトで使用しているJavascriptのライブラリの都合でこれが必須だったのでライセンス購入しました。

プラグインをアクティベートすると、メニューに「languages」が追加されます。

images/image-2.png

詳しい設定の仕方はこちらを参考にさせていただきました。わかりやすい!

多言語対応化WordPressプラグインならPolylangが秀逸!その特徴と使い方

Custom post type

独自の投稿テンプレートを使えるようにする「Custom post type UI」というプラグインを使っているのですが、Polylangはこれにも対応してくれています。素晴らしい!

images/image-4.png

PolylangのAPI

ページなどWordpressの機能で作られている部分は設定画面から多言語化できるのですが、シタテルのサイトではヘッダーなどテンプレートにPHPでゴリッと記載している部分もありました。その辺りはPolylangのAPIを使って言語による切り替えなどを実装することで実現できます。

PolylangのAPI

こういうのがしっかり揃っているところも、このプラグインの素晴らしいところですね。

まとめ

ということで、いくつかの候補の中からこれを選んで正解だった気がします。

今回はサービスページの英語対応をしましたが、ユーザー登録やログイン後はまだ日本語のみですので、引き続き多言語化をすすめていきます!世界中で衣服生産のプラットフォームとして、皆さんに価値が提供できるようにがんばります。ご期待ください!

2019年2月21日木曜日

Alexaスキルを作ろう - 第2回

2月 21, 2019
こんにちは。シタテルでエンジニアをしている熊谷です。

SPECというサービスのバックエンド開発などを行っています。

今回は前回の続きで、Alexaスキルの作成について書いていきます。


今回は、前回作成したスキルから呼び出されるLambdaを作成します。

まずAWSコンソールにログインして、Lambda - 関数の作成に行きます。

そこから「一から作成」、「設計図」などが選べます。

「設計図」でalexaを検索すると、
それぞれのスキルタイプに合わせたsdkが用意されています。
使用したいタイプのsdkがある場合は、それを使用しましょう。

今回はとても単純なスキルを作成したので、「一から作成」を選択します。
いろいろな言語が選べますが、今回はNode.jsで作成しました。

関数を作成すると、シンプルなレスポンスを返す関数が書かれているので、その中身を変更します。

今回作成したスキルは、「言葉を逆さまにする」です。
シンプル...!

コードを記述します。

exports.handler = async (event) => {
  const word = event.request.intent.slots.word.value;
  const response = {
    "version": "1.0",
    "response": {
      "outputSpeech": {
        "type": "PlainText",
        "text": word.split('').reverse().join('')
      },
      "shouldEndSession": false
    },
    "sessionAttributes": {}
  };
  return response;
};

こんな感じになりました。

responseの形式について、詳しくは応答の形式を参考にしてください。
「intent.slots.word」の部分の「word」は前回設定した「スロット名」に対応します。

次にこのLambdaを呼び出すトリガーを設定します。

「トリガーの追加」から、「Alexa Skills Kit」を選択して、スキルID(alexa developer consoleを参照)を設定します。

alexa developer consoleでテストしてみましょう。

前回、スキルの呼び出し名とサンプル発話を設定しました。

テスト画面で、

「<スキルの呼び出し名>を開いて、<サンプル発話>おはよう」と入力すると‥


ちゃんと逆さまにして返してくれましたね!

シンプルなスキルですが動くと面白いと思います。


今回はここまでにします。

次回は実機でのテストとスキルの公開について書ければと思います。

ありがとうございました!

2019年2月18日月曜日

Google Cloud Functionsで画像を自動リサイズする

2月 18, 2019

Google Cloud Functions

こんにちは、DevOpsチームの甲斐です。
今回は、Google Cloud Functions(以下、GCF)で画像を自動リサイズする手順を紹介したいと思います。

GCFとはAWSでいうところのLambdaです。いわゆる、サーバーレスってやつですね。
最近はGCPを触ることが多いのですが、先日GCP上のWordPressのアップロード画像をリサイズするために、
GCFを使って自動的に画像をリサイズするようにしました。
AWSのLambdaも触ったことありますが、GCFのほうが手間が軽い印象を持ちました。
まだGCFをお使いになったことがない方は、ぜひご参考にしていただければと思います。

前準備

GCFを使う上で必要な環境は以下のとおりです。

  • GCPプロジェクトの作成
  • 課金の有効化
  • Cloud Functions APIの有効化
  • Cloud SDKのインストール

詳細は、以下に書かれていますので、こちらをご参照ください。
https://cloud.google.com/functions/docs/quickstart-console?hl=ja

今回の仕様

今回の仕様は以下のとおりです。

  • Google Cloud Storage(以下、GCS)の当該バケットのuploadsディレクトリにアップロードされた画像を自動的にリサイズ
  • リサイズする画像フォーマットはimage/jpegのみ
  • オリジナル画像は".orig"拡張子をつけてバックアップ
  • リサイズされた画像はオリジナルの画像と同じ名前で上書き

実装

それでは、早速実装の手順を紹介していきたいと思います。
今回はNode.js v8で書きました。それ以外にもNode.js v6, Python(beta), Go(beta)が使えます。

1. 作業ディレクトリの作成

mkdir -p gcf/convert_image
cd !$

2. npm init

npm init

3. 必要なパッケージをインストール

今回は、@google-cloud/storage(GCS関連パッケージ)とgm(画像編集パッケージ)をインストールします。

npm install @google-cloud/storage gm --save

4. コーディング

コードは以下のとおりです。
最初に少しハマったところとしては、リサイズした画像でオリジナル画像を上書きすると
再びイベントが発火されるので無限ループに陥ってしまったことです。
対策としてメタデータにコンバート済みである旨を記述し、処理の最初にメタデータをチェックすることで判断するようにしました。

'use strict';

const gm = require('gm').subClass({imageMagick: true});
const fs = require('fs');
const path = require('path');
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();

exports.convertImage = data => {
  if (data.resourceState === 'not_exists') {
    console.log(`Skip because not_exists`);
    return;
  } else if (!data.name) {
    console.log(`Skip because no name`);
    return;
  } else if (!data.name.startsWith('uploads')) {
    console.log(`Skip because not uploads: ${data.name}`);
    return;
  } else if (data.name.endsWith('.orig')) {
    console.log(`Skip because original image: ${data.name}`);
    return;
  } else if (data.contentType !== 'image/jpeg') {
    console.log(`Skip because not image/jpeg: ${data.contentType}`);
    return;
  }

  const file = storage.bucket(data.bucket).file(data.name);
  console.log(`Uploaded image : ${file.name}`);

  const tempLocalPath = `/tmp/${path.parse(file.name).base}`;
  return file
    .getMetadata()
    .then(data => {
      console.log('[Check already converted or not]');
      const metadata = data[0];
      if (metadata.metadata && metadata.metadata.isConverted) {
        console.log(`${file.name} is already converted.`);
        Promise.reject();
      } else {
        console.log(`${file.name} is not yet converted.`);
        Promise.resolve();
      }
    })
    .catch(err => {return;})
    .then(() => {
      console.log('[Download file]');
      return file
        .download({destination: tempLocalPath})
        .catch(err => {
          console.error('Failed to download file.', err);
          return Promise.reject(err);
        });
    })
    .then(() => {
      console.log('[Backup a image]');
      return file.copy(`${file.name}.orig`);
    })
    .then(() => {
      console.log('[Convert a image]');
      return new Promise((resolve, reject) => {
        gm(tempLocalPath)
          .samplingFactor(2, 2)
          .strip()
          .quality(85)
          .interlace('Line')
          .colorspace('sRGB')
          .write(tempLocalPath, (err, stdout) => {
            if (err) {
              console.error('Failed to convert image.', err);
              reject(err);
            } else {
              resolve(stdout);
            }
          });
      });
    })
    .then(() => {
      console.log('[Upload a converted image]');
      const options = {
        destination: file,
        resumable: false,
        metadata: {
          metadata: {
            isConverted: true
          }
        }
      };
      return file.bucket
        .upload(tempLocalPath, options)
        .catch(err => {
          console.error('Failed to upload a converted image.', err);
          return Promise.reject(err);
        });
    })
    .then(() => {
      console.log('[Remove a temporary file]');
      return new Promise((resolve, reject) => {
        fs.unlink(tempLocalPath, err => {
          if (err) {
            reject(err);
          } else {
            resolve();
          }
        });
      });
    });
};

5. デプロイ

デプロイは以下のようにします。
今回の場合、runtimeはnodejs8になります。また、メモリやタイムアウトもオプションで設定できます。

gcloud functions deploy <name> --entry-point <entry-point> --runtime <runtime> --trigger-bucket <trigger-bucket> --region <region> [--memory <memory> --timeout <timeout>]

デプロイが完了したら、以下のコマンドで正常に登録されたことを確認します。

gcloud functions list

6. テスト

サンプル画像を当該バケットにアップロードし、正常にリサイズされることを確認します。

% gsutil cp sample.jpg gs://<bucket>/uploads/
% gsutil ls -l "gs://<bucket>/uploads/sample.jpg*"
   2601078  2019-02-14T02:37:45Z  gs://<bucket>/uploads/sample.jpg
  14583723  2019-02-14T02:37:43Z  gs://<bucket>/uploads/sample.jpg.orig
TOTAL: 2 objects, 17184801 bytes (16.39 MiB)

まとめ

簡単ですが、GCFで画像をリサイズする実装手順を紹介しました。
今回はGCSをトリガーにしていましたが、それ以外にもGoogle Cloud Pub/SubやHTTPリクエストをトリガーにすることも出来ます。
GCFの詳細については本家ドキュメントをご参照ください。

https://cloud.google.com/functions/docs/?hl=en

また、以下のGitリポジトリにGCFのサンプルプログラムがありますので参考にしてみてください。

https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/master/functions