アンケート機能で検討した6つの設計

2024/08/27に公開

概要

この記事ではアンケート機能を実装するまでに検討した6つの設計を紹介します。
アンケート機能の開発時によくある設計検討の流れだと思いますので参考になれば幸いです。

内容

マイベストで開発したみんなでお買い物サポートクラブ(以下みんサポ)では、アンケート機能の実装をしました。
アンケートを大量に用意し、一問一答方式でさくさくと回答することができ、回答するとユーザーはポイントを獲得することができます。実装したアンケート機能は以下のような画面です。



このアンケート機能の開発において、実装前の要求の変更や優先事項の変化に応じてどのような設計を考えてきたかを共有します。

アンケート機能初期の要求

みんサポ全体の要求を決めていく中で、ユーザー情報を集めるためにアンケート機能を実装することになりました。最初の要求はシンプルで、「できるだけ早くリリースして、ユーザーの回答率や継続率を検証したい」というものでした。プロジェクト全体で高速な開発スピードが要求されていたため、要求が変わるたびに設計を考え、工数感をチームに共有していました。

[設計1] 開発スピード重視のGoogle Formによる実装

最初の要求

  • リリースを急ぐ
  • ユーザーの回答率や継続率を検証したい
  • ユーザーが1画面ずつ選択肢に回答する、一問一答方式

この時点では、開発スピードを最優先とし、Google Formを用いた実装を検討していました。Google Formを使えば、フォーム作成からデータ収集まで開発なしで行えるため、短期間でリリースが可能です。
以下のようなフォームになる想定でした。

この方法では、システムとの連携が難しかったり、どのユーザーが回答したかわからないという問題がありましたが、回答率や継続率を検証するのが要求でしたので、この設計で考えていました。

[設計2] ユーザーへのポイント付与の要件追加

要求の変更

  • アンケート回答者にポイントを付与する

新たな要求として、回答者にポイントを付与する機能が要求に追加されました。ポイントを付与するためにユーザーを特定するために、ユーザーIDが必要になります。Google FormではユーザーIDをURLで渡すことができるので、その方法を検討していました。

具体的には、自動で入力したいユーザーID用の質問を作成しておき、「事前入力したURLを取得」をクリックします。

次の画面で自動で入力したい質問に入力例を入力し、「リンクを取得」をクリックすることで、自動で入力させたいURLを取得できます。

以下はその例です。&entry.484560394=someIdがそのクエリパラメータになっており、Google FormへのリンクにsomeIdを適切なユーザーIDを設定すれば遷移時に自動でユーザーIDを入力することができます。

https://docs.google.com/forms/d/e/1FAIpQaLScJMsq7lehmWnZlpARADyYhiuBExftWG6ZnOpyadl1XrbosGQ/viewform?usp=pp_url&entry.484560394=someId

このフォームを利用すれば、ユーザーがIDを変更できてしまうリスクはありますが、ユーザーIDを取得することができます。(
別のシステムやプロジェクトでの個人的な経験ですが、問い合わせフォームでよく利用してきました)
ポイント付与はアンケートが一定数に達したら手動で行うことを想定しており、運用コストを懸念していましたが、開発スピードを優先するということでこの設計で考えていました。

[設計3] デザイン重視 Typeformの検討

要求の変更

  • デザインを改善したい

必須の要求ではなかったのですが、Google FormはデザインがどうしてもGoogle Formが提供するデザインそのままになってしまい、UXが良くないという点が懸念されました。
そこで、Typeformの採用を検討しました。Typeformのデザイン性はGoogle Formと比較して優れており、アンケート結果をGoogle Sheetに送信する機能も備えています。またGoogle Formと同様にURLでユーザーIDを渡すことができるHidden Fieldsという機能も備えています。これにより、Google Formと同様の利便性を保ちながら、デザインの改善が可能と考えていました。


[設計4] マイページ連携 Webhookの利用

要求の変更

  • アンケート結果をマイページに即時表示したい

アンケート結果をユーザーのマイページに即時反映させる要求が追加されました。そこでTypeformのWebhook機能を検討しました。
アンケート回答時に、自社のシステムにリクエストを送信することで連携可能ですが、この時点で内製する方向へ舵を切ることにしました。連携する機能の開発コストが低くない上、今後の要求の変化に対応するために外部ツールでは柔軟性が担保できないと判断したからです。

[設計5] 開発スピード重視のシンプルな設計

内製での設計では、アンケートの質問の追加や管理を簡素化するために、以下のテーブル設計を考えていました。
ユーザーのテーブルはcustomersというテーブルです。surveysは質問を保存するテーブルで、titleに質問内容、choicesにはカンマ区切りで選択肢を入れ、choice_typeに単一選択か複数選択かを入れます。
survey_responsesは回答を保存するテーブルで、bodyにカンマ区切りで回答を入れます。
カンマを使った設計はアンチパターン(SQLが複雑になる、インデックスが使えない、順番の保証ができないなど)ですが、スピードとのトレードオフで仕方なくこの設計を考えていました。

GraphQL

type Survey {
  id: ID!
  title: String!
  choices: [String!]!
  # 単一選択か複数選択かでradioかcheckboxかを切り替える
  choiceType: SurveyChoiceEnum!
}

type Customer {
  # アンケートを取得
  # 回答可能なアンケート
  surveys: [Survey!]!
}

type ResponseToSurveyPayload {
  errors: [Error!]
}

type Mutation {
  # アンケートに回答
  # 選択した回答はカンマ区切りで送信する
  responseToSurvey(surveyId: ID!, body: String!): ResponseToSurveyPayload!
}

enum SurveyChoiceEnum {
  MULTIPLE_CHOICE
  SINGLE_CHOICE
}

とにかくシンプルなテーブル構造により、開発が早く進められる一方、機能が追加されると設計を見直す必要があるリスクが高いと考えていました。

[設計6] 拡張性を考慮した素直な設計

要求の変更

  • アンケート結果をクチコミに表示する

更に一部のアンケート結果を同じユーザーが投稿したクチコミに合わせて表示することになり、システムの中でのアンケートの位置付けが密接なものになってきたため簡易な設計では今後の変更に耐えられず、かえって修正コストが大きくなるリスクがあると判断し、テーブル設計を以下のように見直しました。

アンケートというよりも、ユーザー属性情報を集めるためのものとして位置付け、名前にattributeが利用されています。profile_attributesに質問を、profile_attribute_choicesに選択肢を保存しています。customer_profile_attributesにユーザーが回答した質問を入れ、customer_profile_attribute_choicesに回答した質問の選択肢を入れています。
これにより、アンケートデータを柔軟に取り扱えるような設計になりました。

この設計では、インデックスを使って高速に取得でき、特定の属性に基づくユーザー分析をSQLクエリだけで簡単に行うことが可能です。さらに、アンケートの表示順序も、テーブルのフィールドを更新するだけで柔軟に対応できます。将来的に非表示したい選択肢もカラムを追加することで、データを失わずに対応することができます。

現在のみんサポでは、このテーブル設計でアンケート機能が運用されています。特別な工夫を要するものでもなく、運用や将来の拡張性を考慮した素直な設計に落ち着きました。
要求を詰めていく中で簡易な設計では耐えられなくなり、開発スピードの優先度を下げることができたのでこの設計を採用することができました。

結論

みんサポ全体の要求・仕様を決めて行く過程で、アンケート機能の要求も変化し、その過程でさまざまな実現方法を検討していきました。長期的な拡張性やデータ活用の観点から、最終的には素直な設計にしました。このように、プロジェクトの初期段階では開発スピードを重視し外部ツールの活用を検討し、進行に伴う要件の変化に柔軟に対応するために内製を選択するケースはよくあります。参考になれば幸いです。

Discussion