なんて美しい夕陽だ🌇

プログラミング、日常、忘れないように書いていきます

SaaS立ち上げに関わる全ての人へ捧ぐ - 「All for SaaS」を読んでみた!

SaaSの開発に関わる機会があり、積読していた「All for SaaS」を読んでみることにした!

前職はアドテク系のSaaS開発に関わっていてこの本は購入していたのだが、積読になったまま埃をかぶっていた。

Amazonで確認したら2年前に購入したままになっていたw

今回は開発前に実施する、事前調査やプロトタイプを作る方法を知りたかったため、Part1「SaaSを取り巻く環境」〜 Part3「事前/深掘り調査とプロトタイプ」をメインに読み進めた。(という前提のもと本記事を読んでください)

本書を読んで、事業として破綻しないSaaS事業を作るために参考となる指標 や、事業を企画するための事前調査 について学ぶことができた。SaaSを立ち上げる際に読む本ではあるが、プロダクト開発共通で言えそうなことも書いてあったため、私のように業務の中で新規事業の企画をしたことがないエンジニアにも大変役立つ内容だった。もちろん、SaaSに特化した話も含まれているため、普段企画をしているがSaaSの企画をしたことがない人にとっても役立つ内容だと思う。

目次

■ Part 1|SaaSを取り巻く環境

  • Chapter 1|SaaSの概要
  • Chapter 2|SaaSの優位性
  • Chapter 3|SaaSの評価方法
  • Chapter 4|まとめ

■ Part 2|SaaS構築の全体像

  • Chapter 1|SaaSを立ち上げるためのフェーズと体制
  • Chapter 2|目標設定
  • Chapter 3|プロダクトマネージャとは
  • Chapter 4|まとめ

■ Part 3|事前/深掘り調査とプロトタイプ

  • Chapter 1|事前/深掘り調査とプロトタイプの概要
  • Chapter 2|事前調査
  • Chapter 3|深掘り調査
  • Chapter 4|プロトタイプ
  • Chapter 5|開発投資判断
  • Chapter 6|まとめ

■ Part 4|開発

  • Chapter 1|開発の概要
  • Chapter 2|デザイン
  • Chapter 3|機能要件の開発
  • Chapter 4|非機能要件の開発や対応
  • Chapter 5|QA
  • Chapter 6|まとめ

■ Part 5|ゴー・トゥ・マーケット戦略

  • Chapter 1|ゴー・トゥ・マーケット戦略の概要
  • Chapter 2|プライシング
  • Chapter 3|事業計画
  • Chapter 4|販売戦略
  • Chapter 5|販売戦略実現に向けた準備
  • Chapter 6|リーガル対応
  • Chapter 7|コミュニケーションのデザイン
  • Chapter 8|まとめ

■ Part 6|リリース

  • Chapter 1|リリースの概要
  • Chapter 2|リリースの事前準備
  • Chapter 3|ベータ版リリース
  • Chapter 4|正式版リリース
  • Chapter 5|プロジェクト全体の振り返り
  • Chapter 6|まとめ

ここからは、個人的に学びを得た部分を抜粋して記載していく。

SaaSを評価する指標

ユニットエコノミクス

SaaSを評価する指標として ユニットエコノミクス というものが一般的に用いられ、ベンチャーキャピタルの中でも度々参考にされているらしい。ユニットエコノミクスは以下の計算式で求められる。

ユニットエコノミクス = LTV(1ユーザが契約から解約までに支払う金額の合計) /  CAC(1ユーザを獲得するまでにかかった金額)

LTV(ライフタイムバリュー)

LTVは「1ユーザが契約から解約まで支払う金額の合計」を表す。

LTV = Average MRR per Customer(1ヶ月の利用額平均) × Average LifeTime of a Cutomer(平均継続月数)

「1ヶ月」としている部分は、事業によって異なるため自分の事業に合わせて読み替える。

CAC(ユーザー獲得コスト)

CACは 「1ユーザを獲得するまでにかかった金額」 を表す。

CAC = Sales and Marketing Costs(セールス・マーケコスト) / Number of Acquired Customers(ユーザ獲得数)

セールス・マーケコストをどこまでをコストとして含めるか、これも事業や求めたい指標によって異なる。

LTV > CAC が成り立たなければ事業として破綻している

この計算式が成り立っていない場合、ユーザを集めると集めるほど赤字になっていく状況である。つまり事業として破綻している状態である。

LTV(1ユーザが契約から解約までに支払う金額の合計) > CAC(1ユーザを獲得するまでにかかった金額)

ベンチャーキャピタルの中では、この数値が3倍以上であることが投資をする上で一つの指標となっているらしい。

新規事業を企画する際には、この数値が3倍以上になることを確認することで、継続的な事業になるかを判断する材料になりそうだ。

調査とプロトタイプ

プロダクトの立ち上げに向けて、本書では以下3つの調査とプロトタイプの手順が紹介されている。

  1. 事前調査
  2. 深堀調査
  3. プロトタイピング

事前調査

まずは事前調査として、自分のデスク上でできる調査(デスクリサーチ)を行う。

5つの方法が紹介されているのだが、中にも印象に残っているのは、インターネット調査や競合調査における「英語での調査を行う」という部分だ。SaaSビジネスは欧米を始めとする海外の方が進んでいるため、参考になることが多いらしい。エンジニアリングの中で英語の文献も参考にするべきとは聞く話だが、SaaSビジネスの調査においても重要であることがわかった。

深堀調査

事前調査を行うことでプロダクトが方向性が見えてくるとは思うが、これではまだ不十分だという。プロトタイピングを行うには、ユーザに対するインタビューを通して業務の進め方を把握する必要がある。

いくつかの方法が紹介されているのだが、中にも印象に残っているのは 「競合プロダクトの調査」 である。HPや資料等からターゲットユーザ等を把握することで、自社のサービスの参考にすることができる。トライアル等で利用できるのであれば、実際に利用してスクリーンショットを撮っておくとよい。このような先行事例を参考にすることが 自社のプロダクトを作っていく上でショートカット となる。

プロトタイピング

開発には多くの時間がかかるため、プロトタイピングという一段階を挟むことで手戻りを減らすことができる。

印象に残っているのは「プロダクトビジョンの作成」である。本書においては以下のように説明されている。

企業全体のミッションに即した形で、プロダクトを通して対象となる業務や市場に対し、どのような価値を提供し、何を実現するのかを簡潔にまとめたものである

多くのプロジェクトで、このタイミングでデザイナーやエンジニアが入ってくるだろう。関係者が増えたタイミングでバチっとハマったプロダクトビジョンがあると、各メンバー間で無駄なコミュニケーションを取らずとも目指す方向性を一致させるられるため非常に良さそうだと思った。

また、プロダクトビジョンを作る上でのポイントとして、以下のポイントが紹介されていた。

正解に近づけるものではなく、正解だと思い込んで伝え続け、そして1人で多く共感してもらい、協働してもらうこと

新規事業は世に出してみるまで正解かわからないので、正解にしてやるくらいの熱狂が必要だと理解した。現在の上司が、「新規事業にはパッションが必要だ」と言っていたことを思い出した。

さいごに

SaaSに限らずプロダクト開発に役立てそうなことを学ぶことができた。エンジニアのメイン業務は開発ではあるものの、いわゆる経営者の視点も持つことも非常に重要だと思っていて、引き続き事業のことも理解できるように学んでいく。

Railsで非同期処理!Sidekiqを構築してみる🦶

はじめに

Railsで非同期処理をする時の選択肢として Sidekiq はよく挙がるかと思います。自分も例に漏れず普段からお世話になっているものの、一から構築したことがなかったためローカル環境に構築してみました。今回は構築手順を残していこうと思います。

なお、Sidekiqの概要については以下の記事がわかりやすいため、こちらをご参照ください。

zenn.dev

Sidekiqを構築する

すでにRailsアプリケーションが作られていることを前提に始めます。

Railsアプリケーションのテンプレートを作っていたので、こちらを元に構築を行いました。今回実装したコードは以下に残してあります。

Comparing main...setup_sidekiq · hideki-okawa/rails-graphql-playground · GitHub

Redisの構築

docker-composeでRedisを構築します。docker-compose.yml を作成し、Redisのサービスを追加します。

version: '3'
services:
  redis:
    image: redis:latest
    ports:
      - "6379:6379"

docker composeを起動します。

$ docker compose up --build

Sidekiqをセットアップする

Gemfile にsidekiqを追加します。

gem 'sidekiq'

インストールします。

$ bundle install

ActiveJobを利用するため、Sidekiqをアダプタとして設定します。

config.active_job.queue_adapter = :sidekiq

Jobクラスを生成する

コマンドでJobを自動生成します。

$ rails generate job Example

jobs/example_job.rb が自動生成されます。Jobが実行されることを確認するため、雑にputsを仕込んでおきます。

class ExampleJob < ApplicationJob
  queue_as :default

  def perform(*args)
    # Do something later
    puts 'ExampleJob is running!!'
  end
end

Jobを実行してみる

あらかじめSidekiqを立ち上げておきます。

$ bundle exec sidekiq

               m,
               `$b
          .ss,  $$:         .,d$
          `$$P,d$P'    .,md$P"'
           ,$$$$$b/md$$$P^'
         .d$$$$$$/$$$P'
         $$^' `"/$$$'       ____  _     _      _    _
         $:    ',$$:       / ___|(_) __| | ___| | _(_) __ _
         `b     :$$        \___ \| |/ _` |/ _ \ |/ / |/ _` |
                $$:         ___) | | (_| |  __/   <| | (_| |
                $$         |____/|_|\__,_|\___|_|\_\_|\__, |
              .d$$                                       |_|
      

2024-01-24T13:20:21.537Z pid=3769 tid=5m5 INFO: Booted Rails 7.0.6 application in development environment

別のターミナルでJobを実行してみます。

> ExampleJob.perform_later
Enqueued ExampleJob (Job ID: ea2b30a8-e8b1-4c87-be53-867c34f43cba) to Sidekiq(default)
=> 
#<ExampleJob:0x000000010df33528
 @arguments=[],
 @exception_executions={},
 @executions=0,
 @job_id="ea2b30a8-e8b1-4c87-be53-867c34f43cba",
 @priority=nil,
 @provider_job_id="975407bf932e5097267b8523",
 @queue_name="default",
 @successfully_enqueued=true,
 @timezone="UTC">

Sidekiq側でJobが実行されていることを確認できました!

2024-01-24T13:23:21.444Z pid=3769 tid=7ld class=ExampleJob jid=975407bf932e5097267b8523 INFO: start
2024-01-24T13:23:21.449Z pid=3769 tid=7ld class=ExampleJob jid=975407bf932e5097267b8523 INFO: Performing ExampleJob (Job ID: ea2b30a8-e8b1-4c87-be53-867c34f43cba) from Sidekiq(default) enqueued at 2024-01-24T13:23:21Z
ExampleJob is running!!
2024-01-24T13:23:21.449Z pid=3769 tid=7ld class=ExampleJob jid=975407bf932e5097267b8523 INFO: Performed ExampleJob (Job ID: ea2b30a8-e8b1-4c87-be53-867c34f43cba) from Sidekiq(default) in 0.3ms
2024-01-24T13:23:21.450Z pid=3769 tid=7ld class=ExampleJob jid=975407bf932e5097267b8523 elapsed=0.006 INFO: done

管理画面にアクセスする

管理画面でJobの状況を確認できるため設定を行います。 config/routes.rb に以下の設定を追加します。

require 'sidekiq/web'

Rails.application.routes.draw do
    ...
    mount Sidekiq::Web => "/sidekiq" # mount Sidekiq::Web in your Rails app
end

http://localhost:3000/sidekiq にアクセスすると管理画面が確認できます。

おわりに

今回はSidekiqを構築してみました。Sidekiqについては業務では既存コードを参考にしながら実装すること部分が多く、改めて一から構築することで理解が深まりました。今後もお世話になっていこうと思います。

M1 MacでRetoolのローカル環境を構築してみる

はじめに

最近、ローコードツールである Retool を利用した開発を行っています。RetoolはWebブラウザ上で動作し、UIコンポーネントドラッグ&ドロップすることで画面を構築できるサービスです。さらに、画面に表示するデータとして、自前で作成したAPIやデータベースやGoogle Analytics等のデータと連携することができます。

Retoolのイメージは Retool | The fastest way to develop effective software. 、データソースについては Data sources | Retool Docs をご覧ください。

自前でAPIを構築しRetoolと連携する場合、ローカル環境のAPIクラウド版やセルフホストしたサーバー上に構築したRetoolとの接続ができず、検証が行いづらい状態でした。

今回は、M1 Macのローカル環境にRetoolを構築し、APIとの連携を検証できる状態にしましたので手順を残していきます。

ローカル環境の構築手順

Deploy Self-hosted Retool with Docker Compose | Retool Docs をベースにローカル環境を構築していきます。

事前準備

Retoolの構築にあたって以下の準備が必要になります。

  1. docker-compose の動作環境
  2. Retoolのライセンスキーの取得

ライセンスキーの取得は Retool On-prem Portal から行います。ユーザー登録を行うとライセンスキーが発行されるので控えておきます。

1. オンプレ版のRetoolをclone

オンプレ版のRetoolがGitHubで公開されているのでこちらをcloneします。

github.com

$ git clone git@github.com:tryretool/retool-onpremise.git

2. Dockerのセットアップ

既存のスクリプトを実行し、Dockerを立ち上げるためのセットアップをしていきます。これにより docker.env が生成されます。

$ cd retool-onpremise
retool-onpremise$ ./install.sh

3. 環境変数の設定

docker.env を2箇所修正します。

① 事前準備で控えておいたライセンスキーの設定

## License key
LICENSE_KEY={取得したライセンスキー}

COOKIE_INSECUREのコメント解除

COOKIE_INSECURE=true

4. Dockerイメージのバージョン指定

以下のページから、最新のDockerイメージのバージョンを控えておきます。

https://hub.docker.com/r/tryretool/backend/tags

Dockerfileに控えていたイメージのバージョンを指定します。

FROM tryretool/backend:3.20.13

5. ワークフロー機能の無効化

今回はワークフロー機能は利用しないため無効化します。 docker-compose.yml から該当機能を削除します。

6. docker composeを起動する

docker composeを起動します。ログを確認したい場合は -d を省きます。

$ docker compose up -d

http://0.0.0.0:3000/ にアクセスし、ログイン画面が表示されれば成功です。初回はユーザー登録を行うことで利用できるようになります。

補足: ローカルに立てたAPIサーバーとの接続

別のコンテナで動作しているAPIサーバーと疎通を行いたい場合、同じDocker Networkに含める必要があります。以下の記事が参考になりそうです。

zenn.dev

最後に

今回はRetoolのローカル開発環境を構築してみました。社内用アプリケーションを高速に立ち上げるには良いサービスだと感じています。引き続きRetoolの活用を進めていきます。

都庁で見る初日の出から始まる2024年

2024年、明けましておめでとうございます!本年もよろしくお願いいたします!

幸運なことに都庁の初日の出を見る権利を当てる事ができました。

tokyo-hinode2024.metro.tokyo.lg.jp

 

当日は6時から入場できるという事で、5時45分に着くように向かいました。

なんと、ギリギリ最前列を確保できラッキーでした!

着いたタイミングでは真っ暗だったのですが、ぼーっとしてるうちに明るくなっていきました。

f:id:hideki_okawa:20240101092416j:image

そろそろか?と焦らしに焦らされら実際に見た初日の出はこちら。

f:id:hideki_okawa:20240101092037g:image

日差しが差し込むと歓声が上がり、高揚感を感じました。最高の始まりです。

例年の1日は9時ごろまで寝ている事が多いのですが、早起きして初日の出を見に行くのも良いものですね!

 

ではでは、今年も良い一年で出来ればと思います!

Railsでassociationにクラス名が指定されていないときに警告を出すカスタムCopを作ってみた

とある理由でassociationでクラス名を明示的に指定することをルール付けしたいなんてことがありました。今回は上記ルールを推奨するカスタムCopを作ってみました。

まずは成果物から。

以下のassociationがあったとします。

has_one :sample_association, dependent: :destroy

RuboCopを実行すると以下の警告が出てくるというものです。

rubocopの実行例

クラス名を明示的に指定することで警告は無くなります。

has_one :sample_association, class_name: "Sample", dependent: :destroy

今回はカスタムCopを作る方法を書き残していこうと思います。

目次

1. カスタムCopファイルを作成する

今回はコップ名を EnforceAssociationClassName とし 、lib/custom_cops/ 配下に enforce_association_class_name.rb のファイルを作成しました。

カスタムCopのテンプレートを作成するGemとして、rubocop-extension-generatorも用意されていますが、今回のように1つのルールを追加するような軽いものであれば、手動でファイルを作ってしまっても問題なさそうです。

2. 処理を書く

カスタムCopの処理を書いていきます。実際に書いたコードを以下に掲載します。

# frozen_string_literal: true

module CustomCops
  # This cop ensures `class_name` is specified in ActiveRecord associations.
  #
  # @example
  #   # bad
  #   has_many :items
  #
  #   # good
  #   has_many :items, class_name: 'Item'
  #
  class EnforceAssociationClassName < RuboCop::Cop::Base
    MSG = 'Explicitly specify `class_name` for `%<association>s` associations.'

    def_node_search :association_without_class_name, <<~PATTERN
      (send nil? {:has_one :has_many :belongs_to} (sym _) ...)
    PATTERN

    def on_send(node)
      association_without_class_name(node) do |assoc_node|
        association_method = assoc_node.method_name
        unless class_name_option?(assoc_node)
          add_offense(assoc_node, message: format(MSG, association: association_method))
        end
      end
    end

    private

    def class_name_option?(node)
      node.arguments.each do |arg|
        if arg.hash_type?
          pairs = arg.pairs
          return true if pairs.any? { |pair| pair.key.value == :class_name }
        end
      end
      false
    end
  end
end

いくつかポイントがあります。

  • RuboCop::Cop::Base を継承する
  • MSG
    • 警告時に出力されるメッセージ
  • def_node_search
    • マッチさせるノードパターンを書く
  • on_send
    • sendノードのコールバック関数
    • メソッド呼び出しのたびに実行される

3. Rubocopを実行してみる

以下のようにassociationを設定します。

has_one :sample_association, dependent: :destroy

RuboCopを実行すると以下の警告が出てくれば成功です。

rubocopの実行例

おわりに

思ったよりも簡単にカスタムCopを実装できました。口頭によるルールの伝承というのは時の流れと同時に陳腐化していくものなので、積極的に仕組み化していきたいものです。

参考

docs.rubocop.org

developers.bookwalker.jp

会社のテックブログにGraphQLのエラーハンドリングに関する記事を書きました

久々に会社のテックブログに記事を書きました。 以下の「GraphQLにおけるエラーハンドリングの選択肢と検討」という記事です。

zenn.dev

GraphQLのエラーハンドリングについて他社事例をガッツリ調べたので、これからGraphQLを運用していく方の参考になればと思います。

GraphQL入門の定番書 -「初めてのGraphQL」を読んだ

数少ない日本語のGraphQL入門書である「初めてのGraphQL」を読んだ!

業務でGraphQLは扱っているものの、体系的に勉強したことはなく知識の抜け漏れがあると感じたので読んでみることにした。情報が古くハンズオンを動かすのは大変そうだったため、前半の概念に関する説明は厚く読んで後半は流し読みした。

読んでみて、GraphQLが生まれた歴史的経緯から、基礎的な概念、実践で利用する上で気をつけるなど網羅的に学ぶことができた。網羅的に学ぶことができるため、自分がわかっている部分とわかっていない部分が明確になった。別のサイトや書籍でハンズオンを終えたあと、改めて概念を学ぶために読むと良さそうだと感じた!

目次

  • 1章 GraphQLへようこそ
    • 1.1 GraphQLとは
      • 1.1.1 GraphQLの言語仕様
      • 1.1.2 GraphQLの設計原則
    • 1.2 GraphQLの誕生
    • 1.3 データ通信の歴史
      • 1.3.1 RPC
      • 1.3.2 SOAP
      • 1.3.3 REST
    • 1.4 RESTの課題
      • 1.4.1 過剰な取得
      • 1.4.2 過小な取得
      • 1.4.3 RESTのエンドポイント管理
    • 1.5 GraphQLの実情
      • 1.5.1 GraphQLのクライアント
  • 2章 グラフ理論
  • 3章 GraphQLの問い合わせ言語
    • 3.1 GraphQL APIの便利なツール
      • 3.1.1 GraphiQL
      • 3.1.2 GraphQL Playground
      • 3.1.3 公開GraphQL API
    • 3.2 GraphQLのクエリ
      • 3.2.1 エッジと接続
      • 3.2.2 フラグメント
    • 3.3 ミューテーション
      • 3.3.1 クエリ変数
    • 3.4 サブスクリプション
    • 3.5 イントロスペクション
    • 3.6 抽象構文木
  • 4章 スキーマの設計
    • 4.1 型定義
    • 4.2 コネクションとリスト
      • 4.2.1 一対一の接続
      • 4.2.2 一対多の接続
      • 4.2.3 多対多の接続
      • 4.2.4 異なる型のリスト
    • 4.3 引数
      • 4.3.1 データのフィルタリング
    • 4.4 ミューテーション
    • 4.5 入力型
    • 4.6 返却型
    • 4.7 サブスクリプション
    • 4.8 スキーマのドキュメント化
  • 5章 GraphQLサーバーの実装
    • 5.1 プロジェクトのセットアップ
    • 5.2 リゾル
      • 5.2.1 ルートリゾル
      • 5.2.2 型リゾル
      • 5.2.3 InputとEnumの使用
      • 5.2.4 エッジと接続
      • 5.2.5 カスタムスカラー型
    • 5.3 apollo-server-express
    • 5.4 コンテキスト
      • 5.4.1 MongoDBのインストール
      • 5.4.2 コンテキストへのデータベースの追加
    • 5.5 GitHub認可
      • 5.5.1 GitHub OAuthのセットアップ
      • 5.5.2 認可プロセス
      • 5.5.3 githubAuthミューテーション
      • 5.5.4 ユーザーの認証
    • 5.6 まとめ
  • 6章 GraphQLクライアントの実装
    • 6.1 GraphQL APIの利用
      • 6.1.1 フェッチリクエス
      • 6.1.2 graphql-request
    • 6.2 Apollo Client
    • 6.3 Apollo ClientとReact
    • 6.4 認可
      • 6.4.1 ユーザー認可
      • 6.4.2 ユーザー識別
    • 6.5 キャッシュ
      • 6.5.1 フェッチポリシー
      • 6.5.2 キャッシュの永続化
      • 6.5.3 キャッシュの更新
  • 7章 GraphQLの実戦投入にあたって
  • 付録A Relay各仕様解説
    • A.1 Global Object Identification
    • A.2 Cursor Connections
    • A.3 Input Object Mutations
    • A.4 Mutations updater

GraphQLの誕生について

GraphQLがFacebookによって作られたことは有名な話だと思うが、どのような課題があって作られたかまでを知っている人は多くはないのではないか(自分は知らなかった)。 当時のFacebookのモバイルアプリはRESTfulなAPIサーバーで構成されていたが、パフォーマンスに課題があり度々クラッシュしていた。そのような性能上の課題とデータ構造の要件を満たす解決策としてGraphQLが生まれた。また、いいね数をリアルタイムで取得するためにsubscription機能が生まれた。
このように技術が生まれた背景を知ることは、思想を知る上でも重要かもしれないと感じた。

GraphQLに関する基本的な説明

GraphQLに関する基本的な説明が網羅されていた。クエリに関する説明では「大学向けのスケジュール管理アプリケーション」など具体的な例が示されておりわかりやすかった。読んでみて、union型やinterfaceを上手く扱えていないと感じ、今後の課題として積んだ。

実践投入にあたって

ここまではGraphQLの良い部分が中心に語られてきたが、実践における課題や気を付けるべきことも記載されていた。 課題の1つとして、GraphQLが柔軟であるがゆえにクライアントが自由にデータを取得できてしまい、それが脆弱性に繋がったり大量のデータをリクエストすることでサーバーリソースを圧迫することが挙げられていた。それに対する解決方法として、リクエスタイムアウトの設定、レスポンスデータ量の制限、クエリの深さを制限、クエリの複雑さ制限などいくつかの方法が提案されていた。

最後に

今回は「初めてのGraphQL」を読んだ。賛否両論ある本書だが、自身の不足している知識を把握する上でも大いに役立った。引き続きGraphQLに関するキャッチアップは続けていこうと思う。