Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

AndroidとiOSの両方開発のメリット

こんにちは、モバイルクライアントグループの隅山です。 普段はAndroid開発を担当しています。最近ではiOS開発にも参加し始めたため、両OS開発についての難しさやメリットについて紹介します。

背景

自分のエンジニア目標としてAndroidとiOSの両方を開発できるスキルを身につけることがあり、スタディプラスのiOSアプリを開発する機会をいただきました。 iOS開発自体は初の試みというわけではなく、元々Objective-Cで簡単なiOSアプリ開発はやったことがありました。 しかし、簡単なアプリ開発と実際のプロダクトにおけるアプリ開発とでは大きな壁があり、そこでのつまづきと学びをここでは紹介します。

iOS開発

開発方針

iOS開発を進め方としていきなり新機能開発を行うとわからないことが多すぎるので、初めにAPIまわりの開発を数件行い、慣れてきた後Viewまわりの開発を行う方針にしました。

まずAPIまわりの開発は3件行い、Swiftコードの書き方など基本的な部分を学ぶことができました。 コードの書き方は調べればわかることが多いので特につまづくことはありませんでした。 APIまわりの開発をスムーズに終わらせることができたので、Viewまわりの開発に手を出そうとしたのですが、そこで多くのつまづきがありました。

内容

f:id:JASON13:20201125190040p:plainf:id:JASON13:20201125190045p:plain
ログイン画面(左)対応前(右)対応後

Viewまわりの開発として、ログイン画面を作り直す実装を行いました。

元のボタンなどは使い回すことができ、テキストを新たに追加するだけなので問題ないと思っていました。 ただ、対応前のログイン画面が数年前のコードからあまり改善されていなかったので下記の対応も追加で行いました。

  • UIViewControllerからUITableViewControllerに変更
  • Xibファイルを使ったレイアウトからコードレイアウトに変更

今回の対応で大きくコードが変わるため、既存画面をリファクタリングするのではなく、新しく画面を作り直して差し替える対応を行いました。 既存のリファクタリングであれば修正該当部分だけ直せばいいのですが、新しく作ったためiOSのViewの作り方を学ぶきっかけにもなりました。

つまづき:ヘッダー・フッターの存在

UITableViewControllerでViewを作る際に、UITableViewのヘッダー・フッターの概念につまづきました。 AndroidではRecyclerViewを工夫して実装する必要があるため、標準で存在するiOSの実装に戸惑いました。 しかし、他のiOSアプリのリストにヘッダーで分類名を追加する場合やフッターで注意書きを追加する場合を見て、ヘッダーやフッターの用途を理解することができました。 どこまでをヘッダーとするかやどこまでをフッターにするかなどはこれからも開発する中で学ぶ必要はありそうです。

今回の画面の場合だとメールアドレスと外部アカウントで二層に分かれているため、2セクションのヘッダーやフッターに文字列を追加する形で実装しました。

つまづき:レイアウトの確認方法

Xibファイルからコードレイアウトでの画面パーツの生成へ変更する対応ではかなりの時間がかかりました。 AndroidではxmlファイルでViewやレイアウトをプレビュー画面で見ながら開発できますが、iOSではコードベースでViewを開発する場合、サイズや位置などを調整する度に毎回ビルドしてシミュレータで確認する必要がありました。 Viewについての知識がなかったので実際に微調整しながら開発していたため、学びながら開発するにはこの方法は向いていなかったようです。

また、Viewまわりで実装漏れが発生するとアプリが落ちるなどViewまわりのエラーはややわかりにくい印象でした。

開発を終えてみての感想

普段Android開発している人がiOS開発する際に身につけるべき知識も多いのは開発前から自明ではあったのですが、それにしてもOSによって結構違う点があるなと感じました。

iOS開発の最初の方は自分でできるだけ行うことを心掛けており、コードを触りながらわからないことは調べることを行っていました。 しかし、終わって振り返ってみると同じグループのiOSエンジニアとのペアプロが一番勉強になりました。 自分一人で開発すると堂々巡りで時間が取られたのですが、ペアプロではその場で気づいたことや疑問点をぶつけることができるので時間もかからず、非常に勉強になりました。

これから違うOSの開発を行う方にはペアプロを強くお勧めします。

両OS開発について

難しさ

両OS開発して感じた難しさを2点あります。

1点目は開発ツールの機能性です。 AndroidはAndroid StudioでiOSはXcodeで開発することが多く、各開発ツールによってできることとできないことを理解することが難しかったです。 例を出すと、自分はAndroid Studioを普段から使っているためメソッドやクラスの呼び出し元に移動するのにCommand+クリックで処理を追うことができましたが、Xcodeではそのような機能がありませんでした。

2点目はOSごとの違いの考慮です。 例を出すと、Androidではマテリアルデザインに従ってレイアウトのマージンを決めますが、iOSではlayoutMarginsGuideなどを用いて読みやすい幅を設定するなどOSごとに推奨のやり方がありました。 また、iOS開発でファイル追加や削除の場合にファイル管理しているproject.pbxprojが変更されるのですが、他の方とコンフリクトが起きた場合の解消方法はかなり難しく感じました。

メリット

上記では難しさを書きましたが、両OS開発によるメリットも多くあります。

  • OSによって仕様の認識ズレがない
  • チームとして開発リソースを削減できる
  • 仕様検討段階で両OSのことを考えられるようになる

まずは二人の開発者でそれぞれ開発するといくらコミュニケーションを取れていようと仕様の認識ズレが生じることはあります。 しかし、一人で開発することによって仕様がOSごとにずれることを防げます。

次に開発リソースを削減することができます。 単純計算すると半分のリソースで新機能開発することができるのでチームとしてはアウトプットを増やせます。

また、各OSの書ける人が増えるということはレビューできる人数も増えるということなので相談できる人が増え、レビューによる指摘も増えるので不具合をより抑えることができます。

まとめ

両OS開発を実現するためには勉強することも多いですしつまづくことも多いと思いますが、メリットも大きいと感じているためこれからもAndroid開発だけでなくiOS開発を続けていきたいと思います。

まず直近やることとしてはプレビューがなく開発時間が取られてしまったためXcode Previewsを導入していきたいと思っております。

GitHub ActionsでSwiftLintをLinuxの上で動かそう

こんにちは、モバイルクライアントグループの若宮(id:D_R_1009)です。 みなさまお元気でしょうか、私はチェンソーマン9巻購読直後のため気持ちが乱れております。どうしてだよ……。

コードの悪魔は厳しいので、そんな気持ちの乱れをコードに持ち込むわけにはいきません。 そんなわけで、iOSチームで取り組んでいるSwiftLintの自動化について書きます。

SwiftLintに期待すること

多くの現場で取り入れられていると思われるので、ご存知の方は次のセクションまで飛ばしてください。

SwiftLintとは

github.com

SwiftLintはeslintktlintのように、Swiftのコードスタイルを矯正指摘してくれるツールになります。

iOSではおなじみ、Realmによってメンテナンスされています。リリースも活発です。 GitHub Releaseのタイトルもちょっと賑やか。

github.com

チーム開発ではSwiftの書き方を揃えてくれるので、書き方の癖によるレビュー時のお悩みを軽減してくれます。 また個人であっても、Swiftのより標準的な書き方を学ぶことができるため助かるツールとなっています。

SwiftLintを動かす

SwiftLintはGitHubのReadmeで紹介されている通り、複数の方法で環境構築することができます。 HomeBrewCocoaPods以外にも、SwiftPMで動かすなどなどケースに合わせて対応できます。

開発マシンに入れることができれば、あとはXcodeのbuild phaseに追加するだけです。 HomeBrewで入れた場合であれば、Xcodeの項目にある通りスクリプトを追加するだけで、Xcode内でビルドのたびにチェックが走るようになります。

f:id:D_R_1009:20201106203321p:plain
盛大に指摘されているところ

一度ビルドを走らせないといけないのが難点といえば難点(特にビルドに時間がかかるプロジェクトでは)と言えるかもしれません。 ただ、コードを書いて実機の動作チェックを行い、コードに戻ってきた時にSwiftLintが指摘してくれている状態になるため、体験としてはなかなか良いものじゃないかなと感じています。

GitHub ActionsでSwiftLintを動かす

SwiftLintを全ての開発者がローカルで走らせ、全てチェックした上で開発をしていれば、CI上で動かす必要はありません。 しかし、実際は見逃しやGitHub上での修正などがあり、見落としが発生してしまいます。 そんなわけで、PRを作成した時にSwiftLintを走らせ、警告が出たらコメントして欲しくなります。

PRへのコメントといえばDangerです。

https://danger.systems

Danger自体はRuby、もしくはJSの環境があれば動きます。 そのため、CI上でSwiftLintを動かす際にネックとなるのは Swift です。

もしもCIサービスとしてBitriseを使われている場合には問題にならないのですが、そのほか多くのCIサービスではLinux上で実行させる方が お得 になっています。

www.bitrise.io

Bitriseは無料プランであれば10分までmacOSの環境を利用できるため、ステップとしてSwiftLintを追加するだけです。 Dangerのステップも用意されているので、あとはGitHubのトークンを用意するだけですね。

問題は他のCIサービスを利用している場合です。 macOSを利用するとLinuxの8〜10倍のコストがかかるため、PRの度に走らせる処理としてはちょっと厳しいものがあります。

そんなわけで、今回はGitHub Actions + LinuxでSwiftLintとDangerを走らせる方法を紹介したいと思います。

SwiftLintをGitHub Actionsで簡単に実行

github.com

一番手軽なのは、GitHub Actionに登録されている Danger Swift を利用することです。 利用方法は上のページの真ん中あたりに書いてあるので、PRをフックとしたGitHub Actionのymlを作れば完了です。

Danger Swift はJS版のDangerを利用し、SwiftファイルからDangerを呼び出せるようにしたライブラリです。

github.com

なおJS版のDangerを利用しているため、Ruby版のDangerにないバグの影響を受けることがあります。 もしもfastlaneからDangerを利用しているような環境では、一部の振る舞いが変わるためご注意ください。

Docker Imageで高速に実行

Danger SwiftのGitHub Actionに問題があるとすれば、それはActionのセットアップに少し時間がかかることです。 Linuxで動いているためコストとしては気になるほどではありませんが、ささっと実行された方が嬉しいものです。 そんなわけで、Danger Swiftを利用できるDocker Imageを利用しましょう。

なお、SwiftをLinuxで動かすための環境構築が難しいため、Docker Imageを利用すると確実な動作が期待できます。 後述のf-meloniさんがDocker Imageを作ったのは、とあるIssueに一時的に対応するためだったりします。 *1

2020年11月6日現在、Danger Swiftのリポジトリは「Docker Imageの配布準備が完了」しているものの「Dcoker Imageのビルドがされていない」状況となります。 Git Tagが打たれた時にビルドがされる設定になりつつ、まだ新規のGit Tagが打たれていないためです。 この記事をみなさまが読んでいる時にはビルドがされていることと思うので、ぜひ次のページから最新のDocker Imageを確認してみてください。

github.com

なお、個人でDocker Imageを作成してくださっているケースもあります。 それぞれSwiftのバージョンなどを確認しつつ、自分のプロジェクトで利用できるかどうかなどを確認してみてください。

github.com

github.com

Studyplus iOSチームの話

最後にStudyplus iOSチームのSwiftLintに関わる話を少しだけ書きたいと思います。

ローカルのSwiftLintをバージョン管理

私がiOSのアプリの開発に参加した頃は、下記のような整理でした。

  • SwiftLintはCocoaPodsでバージョン管理
  • CircleCI + macOSで、fastlaneを介して実行

2020年11月現在は下記のようになっています。

  • SwiftLintは各自のマシーンではHomeBrewで管理、CI上はDocker Imageのバージョンで管理
  • GitHub Actions + LinuxでDocker Imageを介して実行

この「SwiftLintをバージョン管理しなくなった」理由は3つあります。

  1. CIでSwiftLintのバージョンを管理できるのであれば、開発マシンの上のバージョンを管理する強い理由がない
  2. CocoaPodsやSwiftPMで管理するのは、アプリの動作に関係のあるライブラリだけにしたい
  3. リリースビルド時にSwiftLintをスキップできる(実行できないため)

brew update をする頻度が人によって異なるため、そのズレがプロジェクトに影響を及ぼすほどであればバージョンの管理をするべきかもしません。 Studyplus iOSチームではそのような問題が起きるとは考えられなかったため、今回の判断をすることができました。

CI環境構築の知見を共有

GitHub ActionsやCircleCIなど、利用できれば便利なツールであっても、なかなか最初はとっつきづらいものだと思います。 このため、チームの中で知見を共有できるように2つのことに取り組んでいます。

  1. CI関連のセルフレビュー時に、参照したドキュメントのURLをコメントする
  2. ブログとして取り組んだことをまとめ、社内でレビューしてもらう

参考にしたドキュメントやブログなどのURLと一言コメントをPRのセルフレビューで書いておくと、レビューの負荷が下がり、後ほどドキュメントを追いやすくなります。 またCI関連ではよくあることですが、後日ステップの書き方が変わった時、既存の処理をどう書き換えてよいのか判断しやすくなります。 PRレビューで問題があった時に指摘してもらえるようしつつ、知見が偏っている分野では共有する場として利用するようにしています。

ブログは社外への発表であると同時に、社内向けのドキュメントとして書くようにしています。 きっとこのブログも役に立つ……!

終わりに

いかがだったでしょうか?

SwiftLintを導入しているけれどもPRには反映していない、SwiftLintをPR上で動かしているけれどもコストが気になっている、などのお悩みの助けになれば幸いです。 最後までお付き合い頂き、ありがとうございました!

Zoom Marketplaceにアプリを公開するまで

こんにちは、Studyplus for School事業部エンジニアの島田です。

今回は先日リリースしたStudyplus for School(以下FS) のZoomとの連携機能と、そのリリースに至るまでのプロセスを紹介させていただきます。

prtimes.jp

導入の背景

FSを利用している塾・予備校の多くでコロナ禍によりオンライン指導が急速に進んでいます。その中でZoomを導入しているケースが多くありました。オンライン指導をするにあたってミーティング予定を作成するためにZoomを起動する手間を省き、FSからZoomのミーティングの予定をシームレスに登録出来る事が望まれていました。

以上から、開発機能の要件としては

  1. FSからZoomのミーティング予定を登録出来る
  2. ミーティング予定を登録すると、既存の生徒メッセージ機能で予定の内容が生徒へ送信される

となりました。

Zoom OAuth Appについて

Zoomと連携する方法はいくつかありますが、今回の実現方法はZoom Outh App を作成し、FSからZoomのAPI を利用することにしました。

OAuthアプリの作成

アプリの作成については、こちらのページの手順どおりに進めていくだけになります。

基本的にZoomはドキュメントが充実していたので、アプリ作成については大きくつまづくことはありませんでした。

ここではFSのアプリを作成するための主だった設定を抜粋します。

  • App Type: User-managed app
    • 今回は、認証したユーザーに対するミーティング設定なのでこちらとなります。
  • Intent to publish: Yes
    • FSを介して学習塾が利用するためには(App Marketplaceへの)公開が必要となります。
  • Scopes: meeting:write user:read
    • こちらを参考にします。今回はユーザー情報取得とミーティング作成に必要な項目となります。

Rubyでの実装について

上記で作成したアプリケーションのClient IDとClient Secretを利用したRubyの実装サンプルを紹介します。

今回はこのサンプルや本実装でもZoomAPIの処理部分で、それ用のgemは以下の理由で利用しませんでした。

  • ZoomAPIのgemは存在するが、メンテナンスされているgemが少ない
  • 今回、FSで実現するためのAPIは少ないのでgemを利用することの方が今後を考えるとリスク、コストがあると判断

利用するAPIについて

FSでは今回は以下のAPIを利用しました。

APIを利用して、

  • 連携
    1. Zoomとの連携の最初にOAuthでトークンを取得
    2. /me はどのZoomのどのアカウントと紐づいているかをユーザーページで確認するために利用
  • ミーティング作成
    1. FSの生徒メッセージ画面にてミーティング予定を入力
    2. 入力されたミーティング予定でZoomのミーティング作成APIを呼び出す
    3. 作成された予定を生徒にメッセージする といったフローになります。

f:id:yo-shimada:20201105163448j:plain

実装サンプル

RubyでのOAuthを利用したAPI呼び出しを確認するための最小の実装を紹介します。

  1. OAuthトークンを取得する
  2. 上記で取得したトークンを利用して、API(/users/me)でユーザー情報を取得する

概略コード

require "net/http"
require "base64"

class ZoomController < ApplicationController
  CLIENT_ID = ENV["ZOOM_CLIENT_ID"]
  CLIENT_SECRET = ENV["ZOOM_CLIENT_SECRET"]
  REDIRECT_URI = "http://localhost:3000/zoom/oauth"

  def oauth
    uri = URI.parse("https://zoom.us/oauth/token")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"

    query = {
      grant_type: "authorization_code",
      code: params["code"],
      redirect_uri: REDIRECT_URI,
    }

    credential = Base64.strict_encode64("#{CLIENT_ID}:#{CLIENT_SECRET}")
    headers = { "authorization" => "Basic #{credential}" }
    response = http.post(uri.path, query.to_param, headers)

    if response.code == "200"
      body = JSON.parse(response.body)
      # access_token, refresh_tokenを永続化する
      # body
      # => {"access_token"=> "xxx",
      #     "token_type"=>"bearer",
      #     "refresh_token"=> "xxx",
      #     "expires_in"=>3599,
      #     "scope"=>"meeting:read meeting:write user:read user_profile"}
      Rails.logger.debug("#{body.inspect}")

      # 試しに自分の情報を取得する
      profile = users_me(body["access_token"])
      render plain: profile
      return
    end

    render plain: "NG"
  end

  private

  def users_me(token)
    uri = URI.parse("https://api.zoom.us/v2/users/me")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === "https"

    headers = { "authorization" => "Bearer #{token}" }
    response = http.get(uri.path, headers)

    body = JSON.parse(response.body)
    "#{body["last_name"]} #{body["first_name"]}, #{body["email"]}"
  end
end

アプリの申請について

実装が完了したら、いよいよZoomのApp Marketplaceに公開するためアプリの申請をします。申請フローついてはこちらにまとまっています。

チェックリストを元に、レビューのプロセスに必要なページの用意やアプリの設定をします。

大まかにまとめると、

  • マーケットプレイスへ公開するために必要なアプリ情報をZoomのアプリ設定画面から設定する
  • アプリを利用するために必要なドキュメントを整備する
  • Zoomがアプリをレビューするために必要な環境を整える
  • セキュリティアンケートに回答する

となります。

中でも手間がかかったのは、「ドキュメントの整備」になります。 Zoomマーケットプレイスでは英語のアプリ情報のみがサポート(レビュー)対象となっています。そのためアプリ情報に関連する全てのコンテンツ(利用規約、プライバシーポリシー、Zoom連携機能のサポートページ)とアプリ情報に利用する画像や説明文のテキストを英語に翻訳する必要がありました。ページによっては新規に英語版のページを用意することにもなりました。 アプリが特定の地域・国のみでの利用を前提としている場合にも、このコンテンツは英語である必要があります。また、特定の地域のみで利用する場合にはそれをアプリ情報の長文説明に記載する必要があります。

アプリ申請の却下について

アプリ申請が却下される場合には、およそ2~3営業日ほどでメールが来ます。メールには却下理由が丁寧に指摘されたGoogleドキュメントが添付されています。

レビューはこのレビューのプロセス の順番で実施されているようで、途中で不備が見つかるとそこでレビューを終わらせて指摘をしてくるようです。

機能・ユーザビリティテスト

このフェーズで指摘された点は、主に上記で説明した英語化をする箇所の認識の漏れ等がありドキュメント整備の不備によるものが多かったです。

英語化する箇所が多かったものの、難易度としてはそこまで難しいものではありませんでした。

セキュリティテスト

このフェーズでは、セキュリティに関してのアンケートへの回答とサービスの脆弱性チェックがされます。

そこで指摘された内容が、

  • OAuthトークンの暗号化
  • TLS 1.1のバージョンアップ
  • LocalStorageに機密情報を保存しない。機密データを保存する必要がある場合は、httponlysecure の属性を設定したセッションクッキーの利用をする

といった内容になり、かなり細かくチェックを受けました。

この中で対応が大変だったのは、LocalStorageの指摘になります。これを改善するためにFSのログイン方法を大きく変更する必要があり、その為の修正におよそ1ヶ月ほどの時間を要する事になりました。

最後に

  • Zoomのアプリ設定画面やドキュメントはしっかりしていて開発はスムーズに出来た
  • 申請後のZoomからのフィードバックのスピードが早く、開発が間延びする事が少なく助かった
  • 開発以外での英語化対応に時間をとられた
  • セキュリティテストは、下手な脆弱性診断を受けるよりよっぽど良かったのではないか
  • 認証方法の変更については指摘の課題感は以前からあったが優先して対応するタイミングがなかったので、このタイミングで出来た事は良かった(Zoom連携機能のリリースは遅れたが)

以上の事を経て、無事Zoomマーケットプレイスに公開されました。

marketplace.zoom.us

データ分析基盤の改善を始めた話

こんにちは。Studyplus事業部 サーバーサイドエンジニアの山田です。

普段はバックエンドのRailsアプリケーションの開発をしていることが多いですが、今回はここ最近取り組んでいるデータ分析基盤の改善について紹介していきます。

はじめに

スタディプラスではユーザーの学習記録をはじめ様々なデータを蓄積しており、それらのデータを活用していくためのデータ分析基盤が存在します。

わたし自身も問い合わせや障害時の調査、新しい機能の検討の中でデータが必要になった時などデータ分析基盤を使うことはよくあります。その中でこういうデータが保存されているとやりやすいのにとか、このデータはよく使うので簡単に抽出できるようにしたいなど改善したいことが出てくるようになりました。

そこで手を挙げてデータ分析基盤の改善を進めていくことになりました。

本記事では、その改善について以下の2点について紹介していきます。

  • どのようにデータ分析基盤の改善を始めたか
  • 改善の中で実際に行った施策(データウェアハウスの作成)について

データ分析基盤の現状と課題

改善を進めていく上でまず出てきた大きな課題は、データの流れや使われ方などの全容を把握して管理している人がいない、またデータ分析基盤の専任のチームや人がいないということでした。

これによって以下のような状態が発生しやすくなっていました。

  • データの収集や抽出に無駄が発生する
  • 改善が局所最適になりやすい/改善自体がおこなわれにくい

もう少し詳しく説明していきます。

データの収集や抽出に無駄が発生しやすい

弊社ではデータは主にBigQuery、Amazon S3、MySQLなどにためて見るようになっています。

各データソースに蓄積されているデータを把握していないと、例えばBigQueryからすぐに抽出できるデータを苦労してMySQLから抽出しようとしたり、抽出自体を諦めるといったことが発生していました。

実際にあったこととして、過去のある時点のユーザーデータを抽出しようとした時にMySQLとBigQueryから抽出しようとしたが実はS3にそのデータが保存してあるということがありました。データの保存場所を把握できていればすぐに抽出できたものをかなり時間をかけて対応をしてしまうということがありました。

データを保存し始めた当時は必要性があり追加されたが、その後あまり使われなくなり保存していること自体が知られていないデータが存在するようになっていました。

改善が局所最適になりやすい/改善自体がおこなわれにくい

BigQueryにためている主なデータはアプリのFirebaseイベントログです。一方、MySQLにはスタディプラスアプリが使うデータそのものを保存しています。

これらはそれぞれアプリの開発チームとバックエンドの開発チームが管理しているため、それぞれのチームが見える範囲での改善となりやすくなります。

またデータを使うディレクターや広告チームのメンバーがデータ抽出したい、ダッシュボードを改善したい場合に依頼先が決まっていないため、それぞれができる範囲で対応しようとすることが多くなっていました。

例えば、あるデータを抽出する時に複雑なSQLを書く必要があったり、一度に取得することができないため複数回に分けて実行し手作業でまとめたりなど抽出に時間がかかってしまうとことがありました。 目の前のタスクを進めることを考えると手間はかかったとしても今の基盤からデータを抽出してしまった方が早い場合もあります。一方、トータルで考えると基盤自体の改善を進めた方がよいこともあります。

基盤を整備していく専任のチームがないためその知見を蓄積して改善を進めるというサイクルが回りにくいといった状況がありました。

何から取り組んだか

改善を行っていくにも現状を把握していないとどこから解決していくか道筋を立てることは、当たり前ですが難しいです。まずは現状の整理から進めました。

大きくは以下の2点を中心に進めました。

  1. データ分析基盤の全体構成を把握して図にする
  2. データを誰がどんなタイミングで利用し、どんな課題を感じでいるかを整理する

全体構成を把握

データの流れを知り、どんな使われ方をしているかを俯瞰してみれるようにまずは全体構成図を作成しました。

自分自身はサーバーサイドチームのエンジニアですが、データを蓄積するのはサーバーを経由しない場合もあります。例えば、スマホアプリのイベントログをFirebaseにためるような場合はアプリから直接Firebaseにデータをためることになります。また誰がどんなタイミングでデータを使っているか把握していない部分も多かったため社内のドキュメントをあさりつつ他のチームのメンバーにもヒアリング行い全体構成図を作成していきました。

実際に作成した図からは一部省略していますが以下のような図となりました。

データ分析基盤全体構成図

分析でよく使うデータソースは以下の3つになります。

  • BigQuery
    • スマホアプリのイベントログ
  • Amazon RDS(MySQL)
    • アプリケーションのデータ
  • Amazon S3
    • サーバーのログや一部アプリケーションのデータを加工して保存

それぞれのデータソースは多くの場合はRedashを通してクエリを実行しています。また、KPIなど定期的に見るものはダッシュボード化されている状態でした。アドホックにデータを見る場合はRedashを経由せずにBigQueryのコンソール上で直接実行するような場合もあります。

課題のヒアリング

改善のためデータを活用している部署やメンバーに状況を聞くことは重要です。 そこで各部署やメンバーがどんな用途でデータ使っているかや使う中で感じている課題をヒアリングして洗い出すということを行いました。

ヒアリングはMTGで聞いたり、スプレッドシートに記入してもらうよう呼びかける形で行いました。 その結果以下のようなジャンルで課題を抽出することが出来ました。

  • 分析に使いたいデータの不足
  • ダッシュボードの使いづらさ
  • SQLのパフォーマンスの問題
  • 基盤の運用コスト
  • 基盤やテーブル構成の把握の難しさ

改善する課題/施策を選択する

課題を抽出して改善点が見えてきたので、次は何から改善を進めていくかです。

データ分析基盤の改善は、現時点では組織としてチームを作ってトップダウンで行っているのではなく、草の根的にボトムアップで行っている段階でした。そのためまずは早期に成果を上げ、信頼を勝ち取っていくことが重要だと考え1以下の観点で行っていく施策を検討していきました。

  • 効果が出やすい
  • あまり大勢の人を巻き込まずに少数で進められる
  • 改善を短期間でできる

BigQueryのデータの使いづらさと料金面の課題

ヒアリングした課題の中にBigQueryを使った分析での課題がありました。

アプリのFirebaseイベントログをBigQueryにためて分析できるようにしていますが、イベントログに対して直接SQLを発行して分析するような使い方になっていたため下記のような課題がありました。

  • コスト面の課題
    • Firebaseのイベントログは膨大なためクエリを実行するたびにそれなりのデータ容量をスキャンすることになります。BigQueryは使用したデータ量で課金されるため、使いすぎるとコストが膨れ上がってしまいます。そのため、予算を超えないように一日に一定の料金以上のクエリを実行できないよう設定がされています。実際の利用者に確認したところ、分析で何回かクエリを実行しようとすると扱うデータによってはすぐに上限までいってしまうということが発生していました。
  • SQLの扱いづらさの課題
    • BigQueryではネストされた列や繰り返し列を扱うことができます。分析によく使う列が繰り返し列になっていると毎回UNNESTするなどSQLが複雑になってしまうことが多くありました。

これらのことが影響して下記のようなことが発生していました。

  • 料金を気にしてBigQueryのデータを使った分析を遠慮してしまう。
  • 複雑なSQLを書くのに時間がかかる。SQLが得意な分析者以外はBigQueryを使うことを諦めてしまう。

データは使われて初めて価値があり蓄積するだけではただのコストになってしまうため、とてももったいない状況でした。

データウェアハウスの作成

上記のBigQueryの課題は解決できれば効果も大きいです。また、対応案を検討し短期間で少人数対応できる方法としてデータウェアハウス2の作成を選択しました。

ここから具体的に対応した施策について説明していきます。

よく使われているデータ/SQLを抽出する

利用者にヒアリングを行いよく使われるデータを抽出していきました。

例えば、アカウント登録やログインのイベント、DAUなどは分析の中でよく使いますがそれらと合わせてユーザーの属性を利用することも多いです。最初からそれらのデータが使いやすい形で保存されていると毎回イベントログ全てに対してSQLを実行する必要がなくなりデータ容量を抑えることができます。またSQL自体もシンプルになります。

以下のイメージのように予め扱いやすいよう加工したデータを組み合わせることでほしいデータが抽出しやすくなります。

DWHイメージ

ただし、あまりに多くのデータを別テーブルに入れても滅多に使われないテーブルが出てきます。そうするとデータを加工する時に実行するクエリのコストやデータの保存自体のコスト、多くのテーブルを管理する手間などがかかり逆にコストが上がってしまいます。

それを避けるためにまずは下記のようなデータに絞って別テーブルに入れました。

  • KPIなど定期的に抽出することがわかっているデータ
  • 既存のクエリのWith句で頻出するデータ

また、Firebaseイベントログの中にほぼ分析で使わないイベントが多くあることもわかったため、それらを除いたテーブルを作成しました。それにより元々のイベントログのテーブルの容量から 1/20 ほど圧縮されたテーブルを作成して分析ではそちらを使うようにしました。

クエリのスケジュール実行

データウェアハウスは取り込んだ生のイベントログに対して日時でクエリを実行して作成していくようにしました。クエリをスケジュールして実行する仕組みは、BigQueryで用意されているクエリのスケジュール機能を選択しました。

理由は設定が簡単ですぐに使い始めることができるためです。 手軽に設定できる分クエリが増えてくると管理が難しくなってくる可能性もあります。しかし、弊社のBigQueryの使用状況ではそこまで多くのクエリをスケジュールすることは当面の間なさそうなため問題ないと判断しました。

詳細な設定の手順は公式ドキュメントを参照いただければと思いますが、以下のようにクエリのスケジュールから画面に従い順番に設定してくことですぐに使い始めることができます。

スケジュールされたクエリの新規作成から スケジュールクエリ1

クエリのスケジュールや書き込み先のテーブルを設定 スケジュールクエリ2

利用状況のモニタリング

施策をやりぱなっしで終わらせずに取り組んだ施策を継続的に改善していけるように、クエリの使用状況を可視化してモニタリングできるようにしました。

具体的には誰がどれぐらいBigQueryを使用しているかを可視化する簡単なダッシュボードを作成しました。 BigQueryのコスト可視化ダッシュボードを10分で作るを参考にさせてもらい、Googleデータポータルを使った以下のようなダッシュボードにしました。

クエリ使用状況

単純に全体のコストをみるだけであれば、GCPのコンソールで見ることも可能ですが、誰がどのようなタイミングで使っているかというのも見ていきたいのでユーザー別にクエリの使用状況を可視化するようにしました。

利用者の数が多い場合は、部署などグループ毎に可視化するほうが有効かもしれません。

改善後

アプリのイベントログに対して直接クエリを実行していたのを、データウェアハウスを作成してそちらを参照するようになりました。

データ分析基盤全体構成図_改善後

データの容量の課題

容量の小さくなったテーブルを見るようになったため、ダッシュボードによっては1/1000ぐらいのスケールで使用する容量を減らすことができました。 使用できる容量の上限に余裕ができるようになったためアドホックな分析が必要になった場合も以前と比較して容量を気にせず気軽にクエリを実行して分析できるようになりました。

クエリの複雑さの課題

With句を何個も使ったり、UNNESTしたりというSQLを書く必要性が減ったのでシンプルなSQLが多くなりました。 実際に分析するメンバーからかなり楽になったと言ってもらうことができました。

result_slack

継続的な改善が必要

課題に対して一定の改善は行うことができましたが、分析対象によっては今回作成したデータウェアハウス では十分ではなく元のイベントログのテーブルからデータの抽出が必要な場合もあったりします。

そのため継続してよく使われるデータを抽出してデータウェアハウスを改善していく必要があると感じています。

終わりに

データ分析基盤の改善の取り組みをどのような流れで進めたかとデータウェアハウスを使った改善を紹介しました。

今回行った対応は初歩的なことかもしれませんが、改善を回していく流れを作ることができました。また改善を通して他チームのメンバーとデータ分析基盤のこういうところを改善したいという話を以前より気軽にできるようにもなりました。

取り組み自体はまだ始まったばかりでこれから改善していくことは無数にありますが、改善を続けて組織のデータ活用を促進する基盤を作っていきたいです!


  1. データマネジメントが30分でわかる本11章データガバナンスに記載された内容を参考にさせてもらいました。

  2. BigQuery自体をデータウェアハウスと呼ぶこともありますが、ここでは保存された生データに何らかの加工を加えて扱いやすくしたデータのことをデータウェアハウスと呼んでいます。

Kubernetesを本番導入しました

こんにちは、SREチームの栗山(@sheepland)です。 さて、スタディプラスでは2020年9月に念願の Kubernetes本番導入 を果たしました🎉。 本番導入といってもまだ10マイクロサービスあるうちの1つをKubernetes上で稼働させているだけです。しかしそこに至るまで様々な苦労がありました。 今回はKubernetes本番導入をするにあたりやってきたことや使用したツールを紹介したいと思います。

スタディプラスのインフラの現状の課題

Kubernetes導入の話の前にスタディプラスのインフラの現状の課題を簡単にお話したいと思います。

デプロイ速度が遅い

スタディプラスの一番大きなメインのサービスはAWS Elastic Beanstalkで稼働しています。 Elastic Beanstalkを使ってgracefulにデプロイするためには、EC2インスタンスを追加→デプロイ→古いインスタンスを停止といった流れになります。EC2インスタンスを1から起動しセットアップ&デプロイするのでどうしてもデプロイ時間がかかってしまいます(現状10分以上かかっている)。

Elastic Beanstalkのマネージドサービスがゆえの不自由さ

Elastic Beanstalkはあまりサーバーを意識せずに使えるというメリットがありますが、専用の知識が必要でデプロイも遅く設定の更新やロールバックも遅いといった課題があります(他にも細かい不満はたくさんあったりします)。 またローカルで動かすことができないので試行錯誤は実際のElastic Beanstalkを使って行う必要があります。マネージドサービスのつらいところですね。

AMIの更新、OS/ライブラリ/ミドルウェア/言語のバージョンアップが手間がかかる

スタディプラスのメインのサービス以外はEC2上で動いています。 それらのEC2のAMIの更新やOS/ライブラリ/ミドルウェア/言語のバージョンアップをする場合、新しいEC2を立ててリクエストをそっちに流し古いEC2を削除する...といった流れになり手間がかかるという問題があります。

CI/CDパイプラインが複雑

CI/CDパイプラインにはJenkinsおよびHubotを使っていますが、なかなか複雑なフロー&処理内容になっています。 またJenkins自体の管理コストの高さも問題です。

カナリーリリースが手間がかかる

言語、フレームワークのバージョンアップや、大きな機能のリリース時にはカナリーリリースをしたくなるものです。しかし現状、カナリーリリースをするためには、新しいElastic BeanstalkもしくはEC2をたてて、新しい方へ少しずつリクエストを流す必要があります。そして終わったらインスタンスを破棄し…となかなか手間がかかります。

Kubernetesを選んだ理由

上記であげた課題を解決するために、Kubernetesを導入することを決めました。 「アイデアというのは複数の問題を一気に解決するものである」という言葉がありますが、まさにKubernetesは様々な課題をまとめて解決できる可能性を秘めている素晴らしいソリューションです。

他にも、コンテナオーケストレーションツールとしてKubernetesを選んだ理由としては以下があります。

  • 必要な機能が揃っており、やりたことがスマートにできる
  • 自己回復力(セルフヒーリング)の高さ
  • ユーザ数の多さ、人気の高さ
  • 強力なエコシステム
  • 進化の速度が速い
  • 高い拡張性
  • 特定のベンダーに依存しない

導入戦略

いきなり一番大きなメインのサービスをKubernetes移行するのはリスクがあったため、手始めにEC2上で稼働している小さめのサービスをKubernetes上で稼働するようにしました。失敗しても影響が小さいサービスを移行し、そこから得た知見を元に一番大きなサービスを移行するという戦略です。 結果的にはKubernetesの知見を溜めることができ、Kubernetes本番導入までの時間を短くできたので戦略として正解でした。

構成

KubernetesクラスタとしてEKSを使っています。 LBに関してはAWS ALB Ingress Controllerを使って作成をしています。 またシングルテナント(サービスごとにKubernetesクラスタを作成)にすると、Kubernetesクラスタバージョンアップのコストが高くなってしまうため、マルチテナントを選択しました。

監視ツールの移行

監視システムはMackerelを使っています。しかしDatadogのほうが高機能でKubernetesにもフィットするため、Datadogに移行することを決めました。 現在はKubernetes上で稼働しているサービスとAWSサービス(RDS、ALB等々)をDatadogで監視しており、今後全ての監視をDatadogに移行する予定です。

IaCツールの移行

IaCツールはAnsibleをメインで使っています。しかしAnsibleのAWSモジュールはあまりメンテナンスされていないという問題や、現在の構成との差分が取れない、使っている人が少ないため情報が少ないという問題があります。 そこでKubernetes移行のタイミングでAWSリソースをTerraformを使って構成管理していくことに決めました。 まだ全てをTerraform化はできていませんが、Kubernetesクラスタを作成するのはTerraformを使ってできるようになっています。

使っているツール

Kustomize

マニフェストファイルの管理にはKustomizeを使っています。 Kustomizeの良い点は以下です。

  • 環境差分をシンプルに定義できる
  • ConfigMapやSecretを更新した際にPodも合わせて再生成できる(configMapGenerator、secretGenerator機能)
  • リソースに対してnamespaceやlabelを簡単に一括付与できる

Skaffold

KubernetesへのデプロイツールとしてSkaffoldを使っています。 Skaffoldの良い点は以下です。

  • imageのbuild、imageのpush、マニフェストファイルのapplyをまとめて実行できる
  • ローカルのKubernetesでも、ローカル以外のKubernetesでも同じコマンド(skaffold run)でデプロイできる
    • またローカルの場合はimageのpushがskipされる
  • ローカルではskaffold devコマンドによって、コードの変更を自動検知して imageのbuild、マニフェストファイルのapplyを自動実行してくれる。これによりDockerfileの修正やマニフェストファイルの修正の確認が素早く行える
  • profile機能によって環境差分が定義できる
  • image tagの生成ルールが柔軟に指定できる

その他

kubectx、kubensはcontext、namespaceの切り替えに重宝しています。(今後はkubectlプラグインのほうを使っていきたい) またkube-ps1も、どのcontext、namespaceを使っているかがひと目で分かり重宝しています。

デプロイ

デプロイサービスとしてCircle CIを使っています。 最近のトレンドであるGitOpsも考えましたが、ファーストリリースはできるだけミニマムの構成にしたいというのと、チームの学習コストの増加を避けるために、アプリケーションのCIでも使っているCircle CIを使うことを選択しました。(今後はGitOpsでデプロイすることも検討しています。)

デプロイの流れは以下のようになっています。

  1. masterブランチにPull Requestをmergeする
  2. Circle CIのworkflowが起動する。以降はworkflow内の処理
  3. imageのbuild & ECRへpushがされる
  4. staging環境へデプロイがされる
  5. Slackへ「staging環境へデプロイが終わりました。動作確認をして問題なければ承認ボタンを押して下さい」と通知がされる(Circle CIのジョブへのリンク付き)
  6. Circle CI上の承認ボタンを押したら、production環境へデプロイがされる
  7. デプロイ完了通知をSlackへ通知がされる

細かい工夫としてはproduction環境デプロイ前にkubectl diffを実行して、現在の環境とこれから適用するマニフェストの差分をCircle CI上で実行&出力し、意図した変更が行われるかを確認できるようにしています。(Slackに差分確認ボタン付き通知もしている)

ログ収集

ログ収集&ログ分析は Fluentd + S3 + Athena で行っています。 詳しくは以下の記事をご覧ください。
Kubernetes上でのFluentdを使ったログ収集について

負荷試験

サービスを新しい基盤で動かす際には負荷試験が不可欠です。 今回はLocustを使って負荷試験を行い、本番のリクエスト数の3倍でも問題ないかと、どのくらいまでのRPSを処理できるかをテストしました。 負荷試験によってどこがボトルネックになるかを把握でき、万が一処理限界に達した場合に監視で気付けることを確認できて、安心してリリースすることができました。

クラスタバージョンアップ試験

今後定期的にKubernetesクラスタをバージョンアップしていく必要があります。 その際スムーズにバージョンアップができることを確認するためにバージョンアップ試験をしました。
あえて古いバージョンでクラスタを作成し、作成したバージョンアップ手順書に沿ってバージョンアップ作業を行い、滞りなくバージョンアップができることを確認しました。 この事前のクラスタバージョンアップによって、Terraformのディレクトリ構成の改善(複数バージョンのKubernetesクラスタを定義できるように修正)ができたりしてとても有意義でした。

Kubernetesを組織に浸透させるために

真にKubernetesを組織に浸透させるためには、SREメンバーだけではなくアプリケーションエンジニアもKubernetesに習熟することが肝要です。 しかしKubernetesはそれなりに学習コストがあり、実際に手を動かしてみないと理解が難しいという側面があります。
そこで現在はKubernetes輪講会を開催しており、Kubernetes完全ガイドを教材にして持ち回りで講義を開いています。輪講という形であれば強制的に人に教えられるレベルに引き上げられるため、参加者の負担が大きいですがとても効果的だと感じています。まだ始めたばかりですがKubernetes輪講会の完走を目指して参加メンバー一丸となって頑張っています。

今後について、やりたいこと

ひとまずKubernetes導入は達成できましたが、やりたいことはまだまだあります。
まずはメインシステムのKubernetes移行です。それが終われば、GitOps、 スポットインスタンスの活用、サービスメッシュ、Progressive Deliveryなどを検討していきたいと思っています。

さいごに

今回は一部のサービスをKubernetes移行しましたが今回の作業によって下地ができたため他のサービスのKubernetes移行は楽になっていると思います。今後はKubernetes移行を加速させ、開発速度を上げていき、サービスの価値向上に結びつけていきたいと思っています!

iOS 13におけるSiri Shortcuts 最小実装+α スライド書き起こしと補足

こんにちは、モバイルクライアントグループの明渡です。

先日iOSDC Japan 2020にてLTへ登壇させていただき、「iOS 13におけるSiri Shortcuts 最小実装+α」というテーマで発表いたしました。

fortee.jp

フィードバッグも思いの外たくさん頂戴いたしまして、登壇してみてよかったなと嬉しい限りですし、励みになります。ありがとうございました!

こちらは、上記LTをテーマとした以下の内容を含む記事となっております。

  • スライドの内容書き起こし
  • 補足
    • 一部のフィードバックへの回答
    • 観測した一部のニコ生コメントへの補足
    • Ask the Speakerで回答した内容
    • スライド作成過程で削った発表内容

なお、タイトルでiOS 13におけるとうたっておりますが、iOS 13から14へのアップデート内容はこちらの記事の内容でカバーしてる範囲について特に影響ございません。

StudyplusのSiri Shortcuts

2020年6月23日、StudyplusでSiri Shortcuts機能をリリースしました🎉

Siri Shortcutsに対応した画面

2画面対応を行い、それぞれAppleがSiri Shortcutsの実装を勧めるアプリを利用する上で何回も繰り返し行われる操作に該当すると判断して実装しました。

  • ユーザーのQRコード画面
  • 書籍のバーコード読み取りカメラ画面

f:id:m_yamada1992:20201012144642j:plain

Siri Shortcuts 利用状況

※2020年6月23日〜9月13日時点

  • ショートカット経由でアプリを開いた
    • 回数:4,697
    • 人数:4,438
  • Add to Siriボタンからアプリを開くショートカットを登録した
    • 回数:7,942
    • 人数:4,889

補足: 集計方法

導入済みのFirebase Analyticsにて上記の集計対象の操作をイベントとして定義し、ユーザーが該当の操作をした際に送信してます。 送信タイミングはこの発表のスライドにて記載したコードの範囲に含まれております。

Siri Shortcuts 最小実装

実装方法の前提

Siriショートカット自体に実装方法は大きく二通りありますが、紹介するのはNSUserActivityを用いる方法のみです。

  • Intents & Intents UIを用いる
    • アプリにて繰り返し行う動作を様々な値を受け取りつつ再現可能
  • NSUserActivityを用いる
    • 特定の画面を開いた状態のアプリを起動するなど、単純な動作を再現可能

iOS 14のアップデートでIntents UI側は追加要素があったものの、NSUserActivityのみ用いる実装には影響ないです。

実装手順

1. Info.plistにアクションのIDを定義

<plist version="1.0">
<dict>
<!--  中略  -->
    <key>NSUserActivityTypes</key>
    <array>
        <string>$(BUNDLE_ID).materialBarcodeRead</string>
        <string>$(BUNDLE_ID).viewQRCode</string>
    </array>
</dict>
</plist>

2. 該当のアクションを行うNSUserActivityオブジェクトを生成

Info.plistへ定義したアクションIDを含む、NSUserActivityオブジェクトを生成します。

コードはStudyplusバーコード読み取りカメラ画面用のオブジェクト生成処理より引用しました。

import Intents

struct MaterialBarcodeReadAction {
    
    static var actionName: String {
        return "\(bundleId).materialBarcodeRead"
    }
    
    static var userActivity: NSUserActivity {
        let userActivity = NSUserActivity(activityType: actionName)
        userActivity.persistentIdentifier = actionName
        userActivity.title = "教材のバーコードを読み取る"
        return userActivity
    }
}

3. 該当のアクションで開くUIViewControllerへオブジェクトを設定

生成したNSUserActivityオブジェクトを、アクションの結果開きたい画面のuserActivityプロパティへ入れます。

final class MaterialBarcodeReaderViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let activity = MaterialBarcodeReadAction.userActivity
        userActivity = activity
    }
}

4. AppDelegateにハンドル時の処理を実装

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

    switch userActivity.activityType {
    case MaterialBarcodeReadAction.actionName:
        // バーコード読み取りカメラ画面表示
        return true
    case ViewQRCodeAction.actionName:
        // QRコード画面表示
        return true
    default:
        return false
    }
}

最小実装を済ませるとできること

iOS 13以降はプリインストールされている"ショートカット"アプリにて、利用可能なアクションとして選択肢が表示されます。

※選択肢に表示されるには、Siri Shortcuts実装した後の対象画面を1回以上開いている必要はあります。

ショートカットの登録手順

"ショートカット"アプリ上で以下の操作を行うと選択肢に表示されています。

f:id:m_yamada1992:20201012174411j:plain

f:id:m_yamada1992:20201012174333j:plain

ギャラリータブからも追加する導線もあります。

f:id:m_yamada1992:20201012174338j:plain

Spotlightの“Siriからの提案”にショートカットを載せる

最小実装のみだとユーザーが見つけにくいので、導線を増やしましょう。

実装手順

1. NSUserActivityオブジェクトにて提案に載せる値を設定

NSUserActivityオブジェクトを生成時に、Spotligit向けの値の設定します。

#if canImport(CoreSpotlight)
import CoreSpotlight
import MobileCoreServices
#endif
/* 省略 */
    static var userActivity: NSUserActivity {
        let userActivity = NSUserActivity(activityType: actionName)
        userActivity.persistentIdentifier = actionName
        userActivity.title = "教材のバーコードを読み取る"

        userActivity.isEligibleForPrediction = true
        #if canImport(CoreSpotlight)
        let attributes = CSSearchableItemAttributeSet(itemContentType: kUTTypeContent as String)
        attributes.title = "教材のバーコードを読み取る"         
        attributes.contentDescription = "登録したい教材のバーコードを読み取れます"
        userActivity.contentAttributeSet = attributes
        #endif
        
        return userActivity
    }

isEligibleForPredictiontrueへ設定すると、Spotlightで"Siriからの提案"として表示を有効にします。 このプロパティをtrueにしないとSpotlightに永遠に表示されないので注意です。

2. 該当ViewControllerを開いた際に通知する

画面からOSへ、このアクションが実装されている操作をした旨を通知をします。

final class MaterialBarcodeReaderViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let activity = MaterialBarcodeReadAction.userActivity
        userActivity = activity

        activity.becomeCurrent()
    }
}

activity.becomeCurrent()が、実装済みのNSUserActivitiyオブジェクトで再現できる操作をしたことを通知する処理です。

通知の実装を済ませるとできること

該当の画面をよく使う場合、"Siriからの提案"へ表示されるようになります。

"Siriからの提案"で該当アクションを選択すると、アクションが実行され画面に遷移します。 この導線から、ショートカットの追加はできません。

"Siriからの提案"のデバッグについて

端末の設定 > デベロッパ > Display Recent ShortcutsをON にしましょう。

"該当の画面をよく使う場合"という定義が、具体的にどの程度の回数や条件を満たせば良いのかはアプリ開発者に公開されていません。動作確認時は必ずデバッグ機能を利用しましょう。

Add to Siriボタンを配置する

Add to Siriボタンを配置すると、実際にその画面をよく利用するユーザーが見つけやすくなります。

なお、Add to Siriボタンを配置するか否かの判断は、後述するAdd to Siriボタンを配置するとよい画面、配置しないほうがよい画面も踏まえることをお勧めします。

実装手順

1. ボタンを配置する

final class MaterialBarcodeReaderViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let activity = MaterialBarcodeReadAction.userActivity
        userActivity = activity

        activity.becomeCurrent()

        let addToSiriButton = INUIAddVoiceShortcutButton(style: .automaticOutline)
        addToSiriButton.shortcut = INShortcut(userActivity: activity)
        addToSiriButton.delegate = self
        
        view.addSubview(addToSiriButton)
    }
}

Add To Siriボタンのオブジェクトが上記におけるaddToSiriButtonです。 ショートカットへ登録させたいアクションのNSUserActivityオブジェクトを基に生成したINShortcutを、shortcutプロパティへ入れます。

また、INUIAddVoiceShortcutButtonDelegate に準拠して、次に実装を行うボタン押下時のイベントを受け取れるようにします。

2. ボタンを押下後の処理を実装

extension MaterialBarcodeReaderViewController: INUIAddVoiceShortcutButtonDelegate {
    
    func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, 
                 for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {

        addVoiceShortcutViewController.delegate = self
        present(addVoiceShortcutViewController, animated: true, completion: nil)
    }
    
    func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController,
                 for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {

        editVoiceShortcutViewController.delegate = self
        present(editVoiceShortcutViewController, animated: true, completion: nil)
    }
}

INUIAddVoiceShortcutViewController、およびINUIEditVoiceShortcutViewController は親にUIViewControllerを持つSiriに追加画面専用クラスです。 上記コードのpresentは通常のUIViewControllerでモーダル表示を行う際の処理と本質的に変わりません。

補足: 親にUIViewControllerを持つためできることの例

親にUIViewControllerを持つSiriに追加画面専用クラス

なので、iOS 13以降の標準であるmodalPresentationStyleプロパティへ.pageSheetが設定済みのモーダル表示をした際に、下スワイプ操作で画面を閉じた場合に通常の画面と同様に検知する手段があります。

extension MaterialBarcodeReaderViewController: INUIAddVoiceShortcutButtonDelegate {
    
    func present(_ addVoiceShortcutViewController: INUIAddVoiceShortcutViewController, 
                 for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {

        addVoiceShortcutViewController.delegate = self
        addVoiceShortcutViewController.presentationController?.delegate = self 

        present(addVoiceShortcutViewController, animated: true, completion: nil)
    }
    
    func present(_ editVoiceShortcutViewController: INUIEditVoiceShortcutViewController,
                 for addVoiceShortcutButton: INUIAddVoiceShortcutButton) {

        editVoiceShortcutViewController.delegate = self
        editVoiceShortcutViewController.presentationController?.delegate = self 

        present(editVoiceShortcutViewController, animated: true, completion: nil)
    }
}

表示元のクラスをUIAdaptivePresentationControllerDelegateへ準拠して、presentationController?.delegateプロパティに入れます。

extension MaterialBarcodeReaderViewController: UIAdaptivePresentationControllerDelegate {
    
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        // 画面が閉じた際に行う処理
    }
}

通常のUIViewControllerと同様に、下スワイプにより画面を閉じる操作を検知して処理を実行できるようになります。

Studyplusでは、カメラ映像のキャプチャを伴うバーコード読み取り画面でSiriに追加画面を開く際にキャプチャを一時停止しております。 そしてSiriに追加画面でショートカットの追加・編集・削除・キャンセル時に加え、下スワイプ操作で画面を閉じた際にもキャプチャを再開する実装をしました。

3. Siri Shortcutsの追加・編集完了時の処理を実装

Siriに追加画面にてショートカット追加完了時
extension MaterialBarcodeReaderViewController: INUIAddVoiceShortcutViewControllerDelegate {

    func addVoiceShortcutViewController(
             _ controller: INUIAddVoiceShortcutViewController, 
             didFinishWith voiceShortcut: INVoiceShortcut?, error: Error?) {

        controller.dismiss(animated: true, completion: nil)
    }
    
    func addVoiceShortcutViewControllerDidCancel(
             _ controller: INUIAddVoiceShortcutViewController) {

        controller.dismiss(animated: true, completion: nil)
    }
}
Siriに追加画面にてショートカット編集・削除完了時
extension MaterialBarcodeReaderViewController: INUIEditVoiceShortcutViewControllerDelegate {
    
    func editVoiceShortcutViewController(
             _ controller: INUIEditVoiceShortcutViewController,
             didUpdate voiceShortcut: INVoiceShortcut?, error: Error?) {
        controller.dismiss(animated: true, completion: nil)
    }
    
    func editVoiceShortcutViewController(
             _ controller: INUIEditVoiceShortcutViewController, 
             didDeleteVoiceShortcutWithIdentifier deletedVoiceShortcutIdentifier: UUID) {
        controller.dismiss(animated: true, completion: nil)
    }
    
    func editVoiceShortcutViewControllerDidCancel(_ controller: INUIEditVoiceShortcutViewController) {
        controller.dismiss(animated: true, completion: nil)
    }
}

Add to Siriボタンを配置するとできること

"ショートカット"アプリを経由せず、自分のアプリからSiri Shortcutsを追加・編集・削除することが可能になります。

実装してみたうえでの私的考察

Add to Siriボタンを配置するとよい画面、配置しないほうがよい画面

Add to Siriボタンは、ショートカットを追加後も編集と削除の導線を担保し続けることが前提です。

それを踏まえると、それぞれ以下の通りです。

配置すると良い画面

  • 配置することで本来の役割を全うするのに支障が出ない
    • もともと余白が十分にあり、永続的に表示し続けても問題ない
    • クッションページとして別画面を挟む形で表示しても差し支えない

配置しないほうがよい画面

  • 配置すると本来の役割を全うするための領域が狭くなる画面
    • 様々な情報を見るための画面など

画面の役割を邪魔せず配置できる場合はメリットが勝るでしょう。 そうでない場合はデメリットが大きいので、ボタンの実装はやめておきましょう。

提案フレーズの選びかた

提案フレーズとは

NSUserActivityオブジェクトのsuggestedInvocationPhraseプロパティです。

Siriに追加画面から登録する際、呼びかけるフレーズのデフォルト値として表示されます。

f:id:m_yamada1992:20201012192427j:plain

そのまま呼びかけて不都合のないフレーズを設定しておくと、ユーザーは自分で「何て呼びかけよう?」と考える手間を省けて便利です。

補足: 提案フレーズの多言語対応

実際に動作検証はしておらず恐縮ですが、おそらく通常アプリ内で多言語対応した文言を呼び出す際に利用するNSLocalizedString(key, comment)では実現できません。 iOS 13時点での話ですが、Spotlightに表示する文言などはちょっと更新してビルドしたりアプリ再インストールしても即座に反映されなくて、検証に少し根気がいるんですよね...

AppleのSiri ShortcutsサンプルコードであるSoupChefにて、提案フレーズとIntents UIから参照する文言はNSString.deferredLocalizedIntentsString(with:table:arguments:) を利用しているので、そちらを試すとよさそうです。

よりユーザーが利用しやすいフレーズを選ぶには

3つに気をつけて選定してみましょう。

  • 他アプリのショートカットと重複しない
  • 検証する端末
  • 検証する環境
他アプリのショートカットと重複しない
  • 同じ端末内に共存している可能性が高いアプリのショートカットを確認
    • 類似のショートカットがある場合、共存しても呼び分けできるようアプリ名を含めるのがオススメ
    • アプリ名は優先して聞き取りしてくれる様子
    • アプリ名でも略称などは要検証

なお、StudyplusではTwitterにQRコードに関するショートカットが存在することを考慮して、QRコード画面を開く提案フレーズを「スタディプラスのQRコード」としました。

補足: 提案フレーズにアプリ名を含めることの是非

いただいたフィードバックにて、「ガイドラインに以下の記述があるので、アプリ名は含めない方がよいと思っていたのですがいかがでしょう?」とご質問をいただきました。

Exclude your app name. The system already identifies the app associated with a shortcut.

引用元:Shortcuts and Suggestions - Siri - Human Interface Guidelines - Apple Developer

私が実装調査時に主に確認したのはWWDCのSiri Shortcuts関連動画の方なのですが、あくまで「アプリを識別するのには不要だから無闇に含める必要はない」という文脈で語られていた認識です。 それを踏まえての私的解釈ですが、アプリ名を含めることでユーザーの利便性向上を見込めるなら入れても良いんじゃないかと思ってます。

現実的として、同じ端末内に共存する類似ショートカットを複数利用することがありうるならば、最初から呼び分けられる提案フレーズで登録できたほうが親切でしょう。

ただ、1度登録した後でもユーザーが自由に編集できる要素なので、必死に他アプリの調査をしてなんとしても重複を回避しなければいけないというほどのものではないです。提案フレーズ検討時に、自分の端末にインストール済みアプリたちのショートカットをざっと確認してみる程度で十分だと思います。

検証する端末
  • サポートしているOSバージョンのうち、一番低いメジャーバージョンの端末で確認
    • OSバージョンが進む毎に聞き取り性能が大きく向上している
  • サポートしているOSバージョンの範囲で極力スペックの低い端末で確認
    • ハード面でも聞き取り性能が異なる

StudyplusでQRコード画面の提案フレーズ検討時に、アプリの略称を提案フレーズに含めた際の検証をしました。 具体的には、Studyplus内にてユーザーの間で「スタプラ」と略して呼ばれがちなため、提案フレーズに「スタプラ」を含めようとしました。

しかしながら、最低サポートバージョンであるiOS 12、かつiOS 12まででサポートを終了した古いiPad端末で検証したところ、10回中8回聞き取り失敗したので諦めました。 こちらの検証をした筆者は比較的に機会に拾われやすい声質と自負しているので、採用してしまうと使われなくなる恐れがあると察知しました...

検証する環境
  • できるだけユーザーが実際に利用しうる環境と近い状態で試す
    • スポーツジムで利用するアプリなら実際に持っていき聞き取りを試すなど
  • 机の上に平置きした状態で試す
    • 角度や端末への接地面の都合からか、手で持つより意図しない雑音が拾われがち

アプリ略称の検証をした際に、机の上に平置きで10回中8回失敗、手に持った状態では10回中4回失敗でした。

高い確率で発生しうるユースケースとして、端末を机の上などに平置きした状態での検証まではしておくことをお勧めします。

参考

NSUserActivityオブジェクトのプロパティ⇔画面の表示箇所対応表

f:id:m_yamada1992:20201012201722j:plain

実装・発表にあたり参考にしたWebページのURL

さいごに

このLTに登壇したきっかけが「ひとまず特定画面を開く単純なSiri Shortcuts機能をリリースしよう!」と調査・実装・検証・ドキュメントまとめて社内展開まで行なったことです。それらの対応にかかったのが合計で3営業日。

Siri ShortcutsはiOS 12から13へアップデート時点で大幅に挙動が変わっている箇所がそこそこあります。 にも関わらず、Apple公式の一部ドキュメントすらiOS 12段階の情報で更新が止まっている箇所がありました。 その辺りの検証に大いに戸惑い、盛大につまづいた時間も込みで3営業日です。

それらの経験を踏まえて、「これくらい最初から情報がまとまっていれば1日は短縮できた」と思える情報を詰め込んで発表してみました。

ユーザーが頻繁に使う画面に心当たりがあれば是非、実装してみてください。実装の際にこちらの記事が少しでも助けになれば幸いです。

iOSDC Japan 2020に参加しました

こんにちは、モバイルクライアントグループ iOSエンジニアの大石です。

9/19~21に開催された iOSDC Japan 2020 へ参加した件をブログにします。 今回、弊社としてはシルバースポンサーとしてスポンサードしており、LTへの登壇は1名、他のメンバーは勉強会・カンファレンス参加補助で参加しました。

はじめに

弊社スタディプラス 株式会社はシルバースポンサーとして、参加者へ送付されたノベルティボックスに缶バッチを提供させていただきました。もし、機会がありましたらどこかに付けていただければと思います。(切実)

f:id:k_oishi:20201009173041j:plain:w320

感想

参加したメンバーの感想です。 今回は弊社のiOSエンジニアとAndroidエンジニアの全員が参加しました。 弊社は昨年までiOSチームとAndroidチームに別れていたのですが、今年からモバイルクライアントグループという一つのチームになりました。 両方のプラットフォームの開発に少しずつ無理のない範囲で携わり、個人としては新しい開発スキルの習得、組織としては将来的にはエンジニアリソースの効率化を目指しています。

明渡

今年が2度目の参加で、iOS 13における Siri Shortcuts 最小実装+αというテーマのLTでiOSDCへ初めて登壇してきました。 LTの解説・補足記事は別途こちらのブログへ投稿する予定です。遅筆で恐れ入ります...

LT待機部屋では「生放送だし、コメント拾いながら発表できるとかっこいいよね!」等雑談が交わされておりましたが、もちろんそんな余裕はなく。ドラが鳴る前に発表し切れたのでそれだけで御の字かなと思ってます。

視聴したうち、弊社でも是非なるべく早めに取り組めたらいいなと思ったセッションの感想を綴ります。

Xcode Preview でUIKitベースのアプリ開発を効率化する

https://fortee.jp/iosdc-japan-2020/proposal/a88be712-b87a-4d87-bc6d-2579c2ce9b35

Xcode PreviewsをSwift UIではなくUIKitでも利用するための方法と、利用した際のメリットを具体的に提示していただけたセッションです。

UIKitベースでもXcode Previewsを導入する方法があると知ってはいたものの、iOS 12のサポートをし続けた状態での導入手順に煩雑な印象があり、iOS 12を切ってから導入を進めたいと思ってました。 こちらのセッションを視聴して、今はできるだけ早急に導入したい気持ちです。

古くから存在する機能は特に、ソースコードが仕様として取り扱われている側面が残念ながらそこそこ存在しております。プレビュー用のコードを記載しておきさえすれば、Xcode上でビルドをせずに画面の仕様を確認することができるのは非常に魅力的です。

最近チーム内でiOSとAndroidの開発タスクの行き来が活発になっており、iOS開発歴の浅いメンバーにも心強いツールとなります。 良いことづくめであるイメージが具体的にできたので、極力近いうちに対応をねじ込む機会をうかがいます。

iPadOSDC: Multiple Windows

https://fortee.jp/iosdc-japan-2020/proposal/b60ddbb9-7b37-4f24-b530-c87581d35e43

iOS 12のサポートを終了すると発生する、UIApplicationDelegateSceneDelegateへ差し替えるに対応の際に大変有用な知見の詰まったセッションでした。

こちらのセッションの事前知識がない状態で臨むと、対応時により考慮漏れが発生したかもしれないです。 とてもありがたい内容で、参考にさせてもらいながら作業すると思います。

また、アプリのプロセスがキルされる前に画面を復帰するための仕組みに非常に既視感を覚え、やはりそういう実装に落ち着くのねと一人ほくそ笑んだりしておりました。Androidは時代を先取りしすぎている...?

若宮

今回初めての参加になりました。若宮(id:D_R_1009)です。 久々にニコニコ動画にログインし、楽しく視聴しました。遅延もなく、非常に見易かったです。

普段はAndroidとiOSとFlutterを混ぜて開発しているので、アーキテクチャあたりに引かれつつ、尖っていて面白かった2セッションを挙げたいと思います。

Synchronized iPhones, Again!

https://fortee.jp/iosdc-japan-2020/proposal/3a9b496e-8745-4d1d-8952-8ac45a42ca8a

登壇についてのブログ。スライドも掲載されています。

https://www.toyship.org/2020/09/22/154728

個人的な印象になりますが、iPhone/iPadは近距離の端末で接続をした際にとても安定しています。 そんな端末たちがスムーズに連携していく様は感動的でした。

最初のARには騙されてしまいましたが、後半の有機的なネットワークを接続する箇所は、iOSだけに閉じることなくネットワークを考える上で非常によい題材になる気がしました。 実機で行うにはチョット金額的に厳しいものがありますが、三桁台の端末を繋いだ時にどうすれば良いのかなど、アルゴリズムとiOSの仕組みの両方に興味が湧くとても刺激的なセッションだったと思います。

Apple Siliconへの長い旅

https://fortee.jp/iosdc-japan-2020/proposal/0188c283-2804-42cc-acb2-0287ec38ca57

iOSと The other OS のそれぞれを思い浮かべながら聞いていると、この10年の進歩がうかがえる40分と3秒の濃密なセッションでした。 なぜiPhoneが早いのか、なぜiPadが綺麗な描画だったのか、なぜApple SiliconによりMacが置き換えられていくのかと言った興味にひたすらに答えてもらったような気がしています。

モバイルアプリを作っていると、そのアプリが動いている端末のディスプレイサイズを意識することはありますが、SoCを意識することはなかなかありません。 おそらくビルド時にストップしてしまった時や、なんらかのオプションを追加しなければならなくなった時程度ではないでしょうか。 その中で、アプリの動作を支えてくれる。そして速度を保ってくれるSoCを考えることができ、同時にOSのバージョンアップ理由もなんとなく理解できる、意義深いセッションだったと感じています。

隅山

主にAndroid開発を行っていますが、最近はiOS開発も始めたので勉強のため初参加しました。

今まで勉強会でiOSの5分枠LTは見たことありましたが、40分枠の発表をがっつり見るのが初めてだったのでiOSならではのつらさを感じることができました。(つらさへの共感コメントも多かった印象) 自分的に面白いと思った発表の感想を書いていきます。

エラーアーキテクチャ設計について考える

https://fortee.jp/iosdc-japan-2020/proposal/68905652-4f5d-444b-965f-ba572b750467

自分はよくユニットテストを書いていて、テスト観点でエラーケースを考えるときに漏れがないか心配することがありました。 そこでこの発表を通して、エラーアーキテクチャ設計の考え方が非常にいいなと感じました。

エラーハンドリングを頭の中で整理すると漏れなどが生じて、同じ処理でも異なってしまう可能性があります。特に複数人開発だと統一感がなくなりやすいかなと思います。 解決方法として、エラー型をラッパークラスに定義し、コンパイラにエラーハンドリングの検知をさせることで上記の問題を解決する考え方が勉強になりました。 開発の本質ではないエラーハンドリングをコンパイラに任せることで、開発の本質に集中できると思います。

テストコードが増えるとバグは減るのだろうか? - 「0% → 60.3%」で見えた世界の話

https://fortee.jp/iosdc-japan-2020/proposal/f12fb46e-1604-4339-8f76-4289fb835e6e

この発表ではテストコードに関する共感が非常に多かったです。 自分としてもテストコードを運用していく内にわかったことが、今現在の不具合を発見するためにテストコードを書くわけではなく、今後の機能追加で不具合を発生させないために書くということがわかりました。

以前テストコードは仕様書のようなものという風に教わったのですが、この発表を通してそれを再確認することができました。 テストコードを書くことでどのような入出力を想定していて、どのような入出力がイレギュラーケースなのかを把握することができます。 自分が触れたことない部分のコードをリファクタリングすることは不具合との戦いになると思うので、弊社でもテストコード(仕様書)をまとめることで心理的ハードルを下げることができればと思います。

中島

普段は主にAndroid開発をしている中島です。iOSDCは初めての参加でしたが、オンライン上でも活気が伝わってきました。

iOSの方の開発はほとんどしたことがないので新鮮な気持ちでセッションを聴講していましたが、特に興味深いなと思ったものについて感想を述べたいと思います。

iOSリジェクト戦記 ~リジェクトされないための課金ページ~

https://fortee.jp/iosdc-japan-2020/proposal/09b08386-9b39-4225-873e-229bfdc0bcaa

iOSのリジェクトについて、今までiOSエンジニアの方々が苦労していたのをよく見ていましたが、この発表では特に激しい戦いである課金関連申請の辛さを改めて見せつけられました。

自動継続についての説明というガイドラインに載っていない項目の記載、目立たせるべきまたは目立たせてはいけない文字列、審査員のさじ加減などといったスライドそれぞれに辛さが感じ取れました。 巻物風のスライドの作り方も印象に残りやすく、発表の工夫として素晴らしかったと思います。 また、「次回の申請に問題を持ち越す」仕組みをAppleから提案されているということを初めて知りました。

この発表を通して様々な理由によるリジェクト事例と対策を知ることができましたが、まとめの「課金ページのデザインに正解はない」に真理を見た気持ちになりました。

Firebase Dynamic Links で既存のユーザーだけでなく、潜在的ユーザーにも体験を提供したい!

https://fortee.jp/iosdc-japan-2020/proposal/009e39f5-b07b-481b-9e43-d5bd6dc7217e

Firebase Dynamic Links は自分も割と昔から実装経験があり、Androidでの挙動や実装については少し勉強していましたが、iOS側の実装や挙動については細かいところまで把握できていませんでした。

UniversalLinksとCustom URL SchemeそれぞれをOSのバージョンで使い分けられているとは聞いたことがあったのですが、この発表でそれぞれの受け取り方などを把握することができました。 AndroidではminimumVersionCodeの判定は自動で行われるが、iOS側ではハンドリングするコードが必要だった点など、差異について知ることができたのがよかったと思います。

大石

素晴らしいセッションばかりでしたが印象に残ったセッションの感想と、初めて担当したスポンサーの申し込みに関して書きたいと思います。

「iOSエンジニアだし、Androidアプリも作れるでしょ?」

https://fortee.jp/iosdc-japan-2020/proposal/5fd45cf8-2911-419a-9bba-b535d26fe98a

モバイルに関してはiOSのみの経験だった私にとってぴったりのセッションでした。 私もモバイルアプリ開発はiOS専門でしたが、最近はAndroidも触りつつあります。 iOSとAndroidのUI、アーキテクチャ、IDEの違いなど、iOSアプリエンジニアがつまずくであろうポイントがまとめられているセッションでした。 今後もスライドを見返していくことになると思います。

Flutter移行の苦労と、乗り越えた先に得られたもの

https://fortee.jp/iosdc-japan-2020/proposal/ffc2099c-a65c-414b-90f0-677b90260201

リリースから10年という歴史の長いアプリのFlutterへの移行という興味深いセッションでした。 具体的にはAdd-to-appという既存のネイティブアプリにFlutterプロジェクトを部分的に組み込むという移行手段でしたが、Flutterならではの挙動の違いなどを知ることが出来ました。 スポンサーセッションではありましたが、通常のセッションと同じくらい興味深いセッションでした。 弊社のアプリもそれなりに歴史がありますので将来的なアプリのリニューアルなどの検討をする必要があるかもしれないと、ふと思いました。

初めてのスポンサー申し込み

弊社はこれまでもiOSDC Japanへのスポンサードをしていましたが、社内の体制が変更になり各種イベントへのスポンサードの対応をイベントに関係するグループで行うことになりました。 そのため、今回のiOSDC Japanへのスポンサーの申し込みをモバイルクライアントグループのリーダーである私が担当しました。

  • スポンサー費用の申請
    例年通り、社内のブランディングおよび採用向けの露出としての予算が取られていたので予算の申請はスムーズに進めることができました。

  • ノベルティ
    ほぼリモート勤務状態のため、Slackで弊社の管理部へノベルティに関して相談したところ、既存のノベルティの在庫状況から缶バッチが良さそうでは?という提案を受け、缶バッチを提供させて頂きました。

  • スポンサー枠のカタログ入稿
    今回のiOSDCでは参加者へ配布するカタログにスポンサー記事の掲載枠があるとのことでした。 iOS開発者とUIデザイン関連の参加者を想定して、弊社の2つのプロダクトの紹介をテーマとして弊社のデザイナーに制作を依頼しました。 入稿の締め切りまで1ヶ月ほどありましたので、余裕を持って制作することができました。 次回も機会があれば今回とは違うものを作ってみたいと思っています!

f:id:k_oishi:20201009173605p:plain:w600

  • 次回はやるぞ!と思ったこと
    他のスポンサー企業さんがTwitterを活用したスポンサードの事前告知や提供したノベルティの紹介、社員の登壇内容の告知をされていました。今回、弊社ではそこまで手が回っていなかったため、次回は計画的に準備したいと思っています。

最後に

今回はオンラインでの開催でしたが、多種多様なセッションを聞く事ができ、弊社のエンジニアもLTで登壇することができました。来年もまたスポンサーとしてのイベントへの協力と、各エンジニアが登壇できるような新しいこと・面白いことを業務でやっていけるような会社にしていければと思っています!

おわり