エンジニアからPdMになった件。

こんにちは、カンカクでカフェ事業のPdMをやっている しほちゃん @shihochan_jp です。
カンカクへエンジニアとして入社してから2年間はAndroidアプリ開発をメインに担当しました。去年の夏よりだんだんPdM業務をはじめて秋より本格的にPdMとして仕事をしています。

このブログではしほのカンカクでのこれまでこれからについてまとめて、リアル店舗開店などカンカクならではPdM業務について話したいと思います。

エンジニアとしてのこれまで

Androidエンジニアとしての仕事

2019年10月にカンカクにジョインしてから、自社経営カフェのオーダーアプリ COFFEE App の Androidアプリの開発を担当しました。今となってはトレンドと逆行するかもしれませんが、Flutter製のアプリをスクラッチでフルネイティブ化しました。
入社した日にリポジトリ作成からはじめて色々と攻めた技術やライブラリを利用しながらも、予定通り12月にリニューアルできました。

リリース後も、飲み放題の月額メンバーシッププランの開発やメニュー画面リニューアルなど大きな改修もこなしてきました。

PdMとしてのこれから

エンジニアからPdMになるにあたっての不安

自分がPdMになるにあたって社長やマネージャーと何度か1on1を実施し、率直な不安や期待値調整を丁寧に実施しました。最初から迷いなく進んだかと言うとそんなことはなくかなり不安がありました。

  1. コードを書けない不安
    分析のQuery以外のコードをほとんど書かなくなって3ヶ月が立ちましたが、振り返っても自分はコードを書くことが単純に好きだったんだなぁと感じています。仕事上のストレスのほとんどは対人関係によるものだと思っているので、自らのコードと向き合う環境を捨て、ストレスの渦の中へと突撃することには大きな不安がありました。

  2. 技術キャッチアップが遅れる不安
    現在リリースを控えているラウンジ機能は、Jetpack Composeを利用して開発されています( こちらのブログもぜひご覧ください )。後任のAndroidエンジニアのコードレビューも担当しているのですが、プロダクトのコードを書いていないので満足のいくキャッチアップができていないです。時間を見つけて手元で動かしてみてなんとかレビューしている形です(いつもレビューが遅れてすみません)。

  3. 今後のキャリアの不安
    今まで8年間、エンジニアとして仕事をしてきたキャリアを一度ゼロへリセットしてしまうのではないかという不安がありました。またエンジニアとして仕事がしたくなった際に錆びついた技術力が理由となり選択肢が狭まってしまうこともあるかもしれません。

なぜPdMにチャレンジすることにしたのか

ここまでたくさんの不安を吐露したわけですが、最終的にはPdMを引き受けることにしました。

  1. 技術の分かるPdMとしてのキャリア
    店舗側を担当する非エンジニアとの施策策定や意思決定の場においてCOFFEE Appをはじめ、システムの概要がわかっている人材が必要とされていました。今までの仕事の中でも課題感を感じることが多かったことなので、自分がPdMになることでエンジニア→PdMというキャリアの答え合わせをしたいと思いました。

  2. 一番店にいるエンジニア
    他の会社にはないカンカクならではの魅力として実店舗が存在することが挙げられます。自分たちが開発したプロダクトが店舗で実際に利用されているところを見ることが自分にとっての仕事のモチベーションにもなっています。自分は都内在住で自転車移動なこともあり、頻繁にお店に出向き積極的にコミュニケーションを取っています。現場でのコミュニケーションを通してスムーズにPdM業務へ移行できるイメージを持つことができました。

  3. 事業グロースのためにできることはなんでもするマン
    カンカクへは立ち上げフェーズから参画することができ、かなり古株社員になってきました。カンカクへ入社を決めた際にも自分は技術を極めることよりも新しい事業を創りたいという気持ちが強くあったことを覚えています。今一度、会社を見たときに自分ができること、自分が活きることとしてPdMの道があるのであればチャレンジしたいと思いました。

色々書きましたが、社長とマネージャーからの「チャレンジしてみてやっぱりエンジニアが良かったらまたエンジニアに戻ろう」との言葉と、今までの仕事や自分の強みを活かせる仕事であると思ったのが大きなところです。

カンカクのカフェ事業PdMのお仕事

カンカクではリアル店舗を経営しているので、通常のWeb事業のPdMでは経験しないような業務があります。多くの機器や工事もあるので意思決定後の戻しが致命的な問題になることもあります。ここではカンカクならではの仕事について簡単にお話したいと思います。

新店舗開店業務

カンカクでは去年1年で4つの新店舗を開店させました。プロダクトチームとして新店舗の開店を補助するタスクは一覧化して管理しており、開店を重ねる度にかなり洗練されてきました。開店が近づくと各タスクは毎日のStandup Meetingで進捗を確認する時間を設けています。

f:id:shihoochan:20220118220930p:plain

下北沢開店タスク

タスク内容としては

  • コーポレートサイトへ新店舗情報を追加
  • Slackへの売上通知Botの追加
  • オペレーションiPadのセットアップ
  • ネットワーク構築とWiFiセットアップ

など多岐に渡ります。担当は各メンバーに手を上げてもらうケースもあれば、属人化しないように複数人で対応するものもあります。中には特別な技術を必要としないタスクもありますが、情報セキュリティなどの観点からエンジニアで補助を行っています。PdMとしては、こちらの一覧の管理、ディレクション、セキュリティ意識向上のための周知・徹底などを行っています。

店舗との連携業務

実店舗では本当に様々なことが起こります。自社製の店舗コンソールやアプリなどWebの技術を多く利用して店舗運営をしているので、店舗スタッフには通常の飲食業よりは高いITリテラリーが求められます。店舗側とは定例を設定し定期的に情報共有を行う以外でも日々の業務を効率化するためにたくさんの仕組みを導入しています。

  • Slackを活用したオペレーション
    カンカクではアルバイトスタッフさんにもSlackに参加してもらっています。心理的安全性を確保しながら業務を効率化するためにルール整備や改善などを日々実施しています。

    • help-meチャンネル
      スタッフが日々の業務の中で分からないこと、困っていることがあればこちらのチャンネルに書いてもらいます。(自分がすぐ反応しています(๑•̀ㅂ•́)و✧)

      f:id:shihoochan:20220118214504p:plain

      help-meチャンネルの活用例

    • newsチャンネル
      調理オペレーションの変更やアプリの新機能リリースなどの情報が投稿されるチャンネルです。各店舗の店長と分担しながら情報共有とオペレーションの徹底を行っています。

  • 開発チーム発信のプロダクト・店舗業務改善
    前述の通り、自分は普段から積極的に店舗へ来店するようにしています。PdMとして店舗でのオペレーション課題のキャッチアップ、リリースした機能が意図通りに利用されているかの確認などを行っています。カンカクではエンジニアによる店舗での課題キャッチアップがきっかけとなった施策や機能がたくさんあります。こちらについてはまた別の機会にブログにできればと思います。

おわりに

今回は振り返りも含めてかなりエモめの投稿になってしまいました。プレスリリースで発表させていただきましたが、カフェ事業としては1月20日に初となるラウンジ業態の店舗を開店します。カフェ事業以外でも弊社のビジョンである「Building the next Ordinary. 新しいライフスタイルを作る」大きなプロジェクトが控えています。カンカクでは、一緒に新しいライフスタイルを作り上げていく仲間を募集しています。少しでも興味がある方は是非一度お話しましょう。

Jetpack Compose: 文字は何色か

こんにちは、カンカクでAndroidエンジニアをやっている @haru067です。
最近はJetpack Compose(以下、Compose)をゴリゴリに書いています。

ということで、今回はComposeの話をしたいと思います。
(執筆時のComposeのstableバージョンは1.0.5であり、これを想定して説明します。)

TextFieldとエラーラベル

Composeには文字入力を扱うComposableとして、TextFieldがあります。

@Composable
fun Example() {
    TextField(
        value = "value",
        onValueChange = {},
        label = {
            Text("Error label")
        },
        isError = true,
    )
}

f:id:kankak_inc:20211221122617p:plain

labelを設定すると、入力文字の上にラベルが表示されます。isError = trueを設定したときにはエラー表示となり、ラベルは赤色で表示されます。

ここでクイズ

次の2つのComposable、Quiz1Quiz2を実行したとき、エラーラベルの色はそれぞれ何色になるでしょう?

@Composable
fun Quiz1() {
    MaterialTheme(typography = Typography(body1 = TextStyle(Color.Green))) {
        TextField(
            value = "value",
            onValueChange = {},
            label = {
                Text("Error label1", color = Color.Blue, style = TextStyle(Color.Yellow))
            },
            isError = true,
        )
    }
}

@Composable
fun Quiz2() {
    MaterialTheme(typography = Typography(body1 = TextStyle(Color.Green))) {
        TextField(
            value = "value",
            onValueChange = {},
            label = {
                Text("Error label2")
            },
            isError = true,
        )
    }
}
1. Color.Green
2. Color.Blue
3. Color.Yellow
4. エラーの赤色
5. それ以外

正解

次のリンクを押して、答えを確認してみてください。

いかがでしたでしょうか? 両方とも正解できた方はおめでとうございます! このあとの文章を読む必要はなさそうなので、こちらをクリックしてください。

正解できなかった人も安心してください。このあとしっかり解説します。

解説: Quiz1

Quiz1はそこまで難しくありません。Textの実装を確認すると、Textは次のようなロジックで文字色を決定していることがわかります。

  1. 引数color: Colorが指定してある場合、colorを使用する
  2. 引数style: TextStylestyle.colorが指定してある場合、style.colorを使用する
  3. それ以外の場合はLocalContentColorを参照する(後述)

Quiz1Text("Error label1", color = Color.Blue, ...)となっており、colorが最優先された結果、エラーラベルはColor.Blueになります。

ということで、これは単純な優先順位の問題でした。厄介なのはQuiz2です。Quiz2では、Textに何も指定していません。単純に考えるとエラーの赤色が適用されそうですが、実際にはそうなりませんでした。謎ですね。

これを理解するには、CompositionLocalについて知る必要があります。

CompositionLocal

CompositionLocalは、Composableに対して暗黙的にデータ渡すための仕組みです。
・・・と言っても理解しづらいと思うので、まずは例を見てみましょう。

@Composable
fun CompositionLocalExample() {
    MaterialTheme {
        Column {
            // MaterialThemeがデフォルト値としてContentAlpha.highを設定
            Text("High alpha") 
            CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
                Text("Medium alpha")
                CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
                    DescendantExample()
                }
            }
        }
    }
}

@Composable
fun DescendantExample() {
    // Composableを跨ぐ場合も適用される
    Text("Disabled alpha")
}

f:id:kankak_inc:20211221123138p:plain:w300

Textに何も指定していないにも関わらず、alpha値が変化しています。
Textは内部でLocalContentAlpha.currentという値を参照しており、CompositionLocalProviderを通じてLocalContentAlphaに値を設定することでalpha値が暗黙的に変化する、という仕組みになっています。

なんて危険な仕組みだ!と思った人は嗅覚が鋭いです。知らないうちに値が変わるということは便利であると同時に、問題が発生したときのデバッグを困難にします。実際、公式ドキュメントでも使うべき/使うべきでない用途については丁寧に解説されています。 developer.android.com

では逆に、これを使うべきタイミングはいつでしょうか?典型的な例はThemingです。先程の例ではMaterialThemeで囲った箇所のalpha値が変化していますが、MaterialThemeの実装を覗くと、CompositionLocalProviderを使用していることがわかります。

脱線: provides

LocalContentAlpha provides ContentAlpha.mediumという表記を見て「ん?」と思った方は多いのではないでしょうか。 provides中置記法で、ComposeというよりはKotlinの機能です。LocalContentAlpha provides ContentAlpha.mediumLocalContentAlpha.provides(Content.Alpha.medium)と実質的に同じです。

解説: Quiz2

さて、CompositionLocalについて学んだところで、Quiz2に戻りましょう。

@Composable
fun Quiz2() {
    MaterialTheme(typography = Typography(body1 = TextStyle(Color.Green))) {
        TextField(
            value = "value",
            onValueChange = {},
            label = {
                Text("Error label2")
            },
            isError = true,
        )
    }
}

Quiz2ではMaterialThemetypographyを指定していました。 実装を確認すると、MaterialThemeではtypography.body1に対して CompositionLocalProviderLocalTextStyleを設定していることがわかります。

ここで Textの文字色の決定ロジックを再確認しましょう。

  1. 引数color: Colorが指定してある場合、colorを使用する
  2. 引数style: TextStylestyle.colorが指定してある場合、style.colorを使用する
  3. それ以外の場合はLocalContentColorを参照する

ポイントは2です。styleのデフォルト値は LocalTextStyle.currentなのでQuiz2におけるTextstyleの値はMaterialTheme(...)でprovideされたスタイルになります。 この結果、style.colorMaterialTheme.typography.body1.colorということになり、Color.Greenが優先された、となるわけです。

エラー時の振る舞い

残る最後の疑問は「エラー時の赤色はどうやって実現しているのか?」です。

余白が少ないので細かい流れは追いませんが、TextFieldの実装を追っていくと、最終的にはここにたどり着きます。要するに、TextFieldlabelLocalContentColorをprovideしており、label = {...}内に記述されたText

3.それ以外の場合はLocalContentColorを参照する

の規則に則って赤色になるわけです。おしまい。

おわりに

カンカクでは、Jetpack ComposeやCompositionLocalに興味のあるAndroidエンジニアを募集しています。 気になる方は以下のリンクよりご気軽にご応募ください!

App Clipsの10MB制限を超えないためにEmergeを使ってみた話

この記事はiOS Advent Calendar 2021 18日目の投稿です。

こんにちは。カンカクのiOSエンジニアの@redryeryeです。この記事はApp Clipsのサイズ制限で悩んでいた時に導入した、アプリサイズ削減・モニタリングツールのEmergeについてご紹介します。

はじめに

カンカクが都内で展開している『KITASANDO COFFEE』『TAILORED CAFE』『WAKE』では、アプリをすぐにインストールすることが難しいお客さまでも、事前に注文してスムーズに商品を受け取っていただけるようにApp Clipsでのオーダーに対応しています

f:id:redryerye:20211218140531p:plain
TAILORED CAFE SHIBUYA
App Clipsとは、一言で言ってしまえばアプリをコンパクトにしたものです。アプリ本体の一部の機能のみをApp Clipとして提供するため、本体アプリとApp Clipsのサイズは異なり、本体アプリよりも小さくなります。通常のApp Soreからインストールする体験よりもすばやいインストール時間が求められるため、10MB以下というサイズの制限が存在します。

COFFEE Appの本体アプリはかなり軽量で、現行のバージョンだと16.5MBあり、ほとんど気にせず機能追加を行っていてもApp Clipは10MBに収まっていました。しかし、ある日Failしたビルドログを見に行くとそこには10.8MBに太ったApp Clipの姿がありました。

そこでサイズ削減について調べた結果、辿り着いたのがEmergeでした。

Emergeとは

EmergeとはスタートアップアクセラレータのY Combinatorの卒業生が作ったアプリのサイズ削減ツールで、元々はiOSのみでしたが最近Androidの対応も開始しました。

具体的な機能としては、既存のアセットやファイルの占有率が分かるインサイトを分かりやすく表示したり、CIに紐付けてアプリを定期的にアップロードすることでサイズの偏移をモニタリングして、SlackやGitHubに通知してくれたりします。

この記事の執筆時点では、直接サインアップすることは不可能で、emergetools.comからWaitlistに登録する必要があります。

使うまでの流れ

Waitlistに登録して数日後、Emergeのチームからミーティングのスケジュールに関するメールが届きました。(彼らのチームのベースはUSにあるため、日付を決めるときはタイムゾーンの違いに注意)

ミーティングではEmergeの説明に加えて、その場でアカウント登録して実際にアプリをアップロードして、サイズ削減が可能な箇所を丁寧に説明してくれました。 デモの後でも、困ったことはデモの時に作られたSlackのチャンネルを使って質問すれば答えてくれます。

使ってみての感想

不要なファイルを分かりやすく表してくれるので便利なのと、サイズを常にモニタリングすることで知らぬ間にアプリが肥大化するという事態が防げるので安心感があり助かっています。

不要なアセットの可視化が便利

一般的に、既存の機能を削らずにiOSアプリのサイズを削減するときに主に以下の二つの方法があります。

  • アセットを最小限にする
  • プロジェクトの設定を最適化する(ビルドの設定やライブラリのリンク方法など)

EmergeのInsightsを使えば、前者の「アセットを最小限にする」ことは容易に可能でした。

f:id:redryerye:20211216085548p:plain
Insightsでは最適化されていないアセットが可視化される (Emerge exampleより)

後者の「プロジェクトの設定を最適化してサイズを削減する」というのは、ミニマムにすればいいというシンプルな話ではないため、iOSの様々な知識が必要です。そこで、デモの時にEmergeからアセットの効率化以外の最適化方法についてアドバイスをくれたのですが、これが改めて発見した気づきもあり役立ちました。彼らのブログにもそのサイズ削減の方法についていくつか書かれています。

結果、COFFEE AppのApp Clipは10.8MBから10MBになりました。

f:id:redryerye:20211216084327p:plain
削減されたときの様子
10MBの制限ギリギリに着地させたのは、10MBを数百KB超えていてもApp Store ConnectでSubmit可能であることに加えて、この先にApp Clipの大幅な機能変更を予定していないためです。

サイズを削減した後のリバウンド対策にも

一度サイズを削減したアプリがリバウンドしないためにEmergeのモニタリング機能を重宝しています。

fastlane pluginを使用してEmergeにビルドをアップロードすることが出来るので、CIに紐づけて定期的にサイズを測ることで動向を監視しています。

f:id:redryerye:20211216091510p:plain
Slackに連携するとアップロードするごとにレポートが投稿される
f:id:redryerye:20211216125045p:plain
アプリサイズの動向も見られる

また、GitHubに連携するとPRごとに差分のインサイトをコメントしてくれるので、細かく監視したい場合は便利ですね。

おわりに

Emergeはサイズ削減とモニタリングを簡単に実現させてくれました。まだ新しいツールなので特に国内だと導入例が少ないですが、アプリサイズを管理したい場合はぜひ使ってみてください。

また、カンカクでは仲間を募集しています。新しい挑戦をしてみたい方、ぜひ一緒に開発しましょう。

Resources

Emerge Doing Basic Optimization to Reduce Your App’s Size - Apple Developer

カンカクのこれまでの2年半を簡単に振り返る

はじめまして、株式会社カンカク@k_kinukawa です。 今日からこのブログを通じて、カンカク開発メンバーが日々の業務で得た経験や技術を発信していきます。

今回は、2019年に創業したカンカクのこれまでの2年半を、開発という視点で簡単に振り返りたいと思います。

株式会社カンカクとは

「Building the next Ordinary. 新しいライフスタイルを作る」というミッションの下、現在はCAFE事業、GOOD EAT CLUB事業、EC事業の3つの事業に取り組んでいます。

COFFEE App

カンカクの運営するカフェで利用できるモバイルオーダーアプリです。 システム全体としてはアプリだけでなく、

  • 店舗で注文や販売商品を管理する「店舗コンソール」
  • システム管理画面「管理コンソール」
  • 店頭でお客様が注文状況を確認することができる「オーダーディスプレイ」

も含まれており、全て自社で開発しています。

f:id:k_kinukawa:20211208131745p:plainf:id:k_kinukawa:20211213090729j:plain
COFFEE Appとオーダーディスプレイ

2019年夏、KITASANDO COFFEE 開店直後に代表の松本とフリーランスの開発メンバーと共にミニマムな要件でリリースしました。アプリはFlutter、ユーザー認証はFirebase Authentication、DBはCloud Firestore、店舗コンソール、管理コンソールはPHPで開発をしていました。

リニューアル(2019年12月) ~ 現在

KITASANDO COFFEE 開店後、各領域に長けているエンジニアメンバーの入社が決まっていきました。初期プロトタイプの構成を維持する案もありましたが継続的な開発を見据え、2019年12月にフルコミットメンバーの得意な言語、フレームワーク、構成にリニューアルしました。

  • Ruby on Rails
  • React
  • Swift(iOS)
  • Kotlin(Android)
  • Heroku

アプリのUIもこのタイミングで一新し、体験が格段に向上しました。

リニューアル当初、インフラはHerokuを利用していましたがパフォーマンスとセキュリティの観点から2021年5月にHeroku→AWSに移行。RailsアプリケーションのコンテナをCircleCIでビルド、ECRへプッシュ、ECSへデプロイをしています。構成管理はTerraformを利用しています。

アプリ側では、iOS14のApp Clip対応、店頭のメニューをiPadで電子化など、リアル店舗ならではの開発も行いました。また、最近ではRailsに密結合だったフロントエンドを疎結合化したり、アプリとのインターフェースをRESTful APIからGraphQLへの移行を進めたりもしています。

年明けには初のラウンジ業態店舗をオープンする予定で、こちらの開発も進行中です。

GOOD EAT CLUB

GOOD EAT COMPANYが運営する食のマーケット&ファンクラブです。

goodeatclub.com

代表松本がCPOとして経営に参画しており、カンカクとしても2020年秋からECサイトの開発を担当しています。

2021年1月にリリースしたパブリックβ版ではプラットフォームにShopifyを利用しました。Multi Vendor MarketplaceというShopify app を利用することで、ショッピングモールとして運用することが可能になります。その他にもShopifyには便利なアプリが揃っており、Shopify + Shopify app を活用し短期間でECサービスの立ち上げを実現しました。

Shopifyは非常に便利なサービスですが、ショッピングモール形式のECサイトとしてより使いやすく、大規模にスケールさせていくためには多くの制約がありました。特にサイトデザイン、管理画面、配送会社等の外部システムとのAPI連携などはユーザビリティ、オペレーションの効率化の面で早急な改善が必要であったため、2021年7月にフルスクラッチの正式版へリニューアルを実施しました。

正式版では

  • Ruby on Rails
  • Vue.js
  • AWS

という構成で開発を行いました。spreeというマーケットプレイスの基本機能を提供してくれるGemを利用することで、約半年でリニューアルをすることができました。同時に配送会社とのAPI連携を行い、パブリックβ版では人力で配送会社のシステムにデータ入力をしていた作業が正式版では自動化をすることができました。デザインも大幅に変更し、使い勝手や商品を選ぶ楽しさなどが大きく改善されました。

EC

COFFEE App、GOOD EAT CLUB以外にも、カンカクでは複数のECブランドを立ち上げています。ECプラットフォームはShopify、ecforceを利用しており、サイトごとにデザインをカスタマイズしたり、好みの味を診断できる「コーヒー診断」のような機能のカスタマイズを行っています。

おわりに

初回なので簡単に振り返ってみました。今後、カンカクの開発に関する取り組みをこのブログを通じてより深く発信をしていきます。 また、来年に向けてラウンジの他にも「Building the next Ordinary. 新しいライフスタイルを作る」大きなプロジェクトが既に走り出しており、一緒に作り上げていく仲間を募集しています。少しでも興味がある方は是非一度お話しましょう。