👋

心理学や医療健康のテストに回答できるアプリ「禅問道」を開発

2025/01/27に公開

こんにちは。大学生エンジニアの豆太郎です。
今回は、最近開発を完了した「禅問道」というアプリについて、自分自身の開発の振り返りも兼ねて紹介したいと思います。

アプリの概要

「禅問道」は、心理学や医療健康の研究で使われているテストに回答できるアプリです。

このアプリは以下の人に向けて作られています。

  • ユーザ(心理学や医療健康のテストを受けて、自分自身の状態を知りたい人)
  • 心理学や医療健康の分野の科学者・研究者

ユーザのアプリの使いかた

ユーザは、以下のように心理学や健康科学に関わるいくつかのテストを受けることができます。

そして、テストを受験すると、結果をグラフで見ることができます。受験者の平均点やテスト結果の推移を見ることができ、過去の結果や平均的な値と比較して、自分の状態を知ることができます。

また、各テストで測られる指標についての説明や、その指標がもたらす生活への影響や、その指標の改善点を論文を元に紹介しています。

科学者のアプリの使いかた

科学者はユーザに回答してもらうテストを作成することができます。

また、テストに関連する研究についての説明も記述することができ、自身の行っている研究をユーザに伝えることができます。

以下のようにテストの説明や、ユーザへの改善案はマークダウン式で編集できるようにしています。

また、マークダウンで編集をした説明や改善案については、Previewで確認することができます。

作り始めたきっかけ

もともと、心理学や健康科学に興味があり、論文を読んで記事にまとめたり、研究で使われているテストに回答したりしていました。また、自分自身で掲載されているテストを受けていく中で、以下のような課題を感じました。

  • 掲載されているテストの点数をメモしたり、計算したりするのが面倒
  • 過去にテストを受けたときの記録が残っていないため、自分の進歩が分からない。

上記のような課題感を感じるとともに、少しでも最新の科学を発信し、個人の生活に役立ててもらおうと考え、このアプリを作成しました。

アプリの技術スタック

バックエンド

Ruby(3.1.2)
RailsAPI(7.0.4)

主なGem

  • Devise
  • bullet
  • kaminari
  • Rspec
  • FactoryBot
  • rubocop

フロントエンド

Typescript(5.3.3)

React(18.3.1)

Next.js(15.0.1) PageRouter

主なライブラリ

  • Material-UI(5.14.7)
  • ESlint(8.48.0)
  • pretter(3.0.3)
  • chart.js(グラフ描画)
  • marked(マークダウン表示用)

データベース

MySQL(8.0.32)

CI・CD

GithubActions

ソースコード管理

Github

インフラ

開発環境:

  • Docker(24.0.5)
  • Ubuntu(22.04.3 LTS)

本番環境:

  • AWS(ECS,ECR,VPC,ALB,IAM等)
  • zeroSSL(SSL証明書発行)
  • ドメイン取得(お名前ドットコム)
  • Gmail SMTP(メール送信)

その他開発ツール

  • GoogleChrome
  • Talend API Tester(API動作確認)
  • Chrome検証機能(フロントエンド動作確認)
  • VisualStudioCode

開発した機能一覧

以下が、このアプリで開発した機能の一覧になっています。

  • サインイン・サインアウト機能(ユーザ・科学者)
  • サインアップ機能(ユーザ・科学者)
  • ユーザのメール認証機能
  • テストの一覧・詳細表示
  • テストの受験フォーム
  • ユーザのテスト結果のグラフ作成
  • テストのMarkDown編集・Preview表示

工夫したところ 【バックエンド】

1.スコアを反転できる質問を追加できるようにして、テスト結果を適切に評価できるようにした。

心理学などで使われる選択式アンケートでは、問われている質問に対して、
すべての回答に最大点を選択する等、被験者にバイアスがかかってしまう場合があります。

そこで、逆転項目という選んだ選択肢の結果を反転する質問項目を追加することで被験者が適切な評価を行えるようにすることがあります。

このアプリにおいても、逆転項目を各テストに反映するために、
各質問について変数isRevercedScoreを使って点数を反転する質問かどうかを判断できるようにしました。

そして、ユーザがテストの各質問に回答した時に、
質問のisReversedScoreがtrueの場合に、点数を反転させるように実装しました。

2.N+1問題を解決して、パフォーマンスを改善

発生していたN+1問題について

あるテスト回答結果の一覧を取得する以下のadd_index関数にて、N+1問題が起こっていました。

rails/app/controllers/api/v1/current/tests/test_answers_controller.rb
def all_index
    ...
    @test_answers = TestAnswer.where(test: @test) # ←ここで、N+1問題が発生
    render json: @test_answers, each_serializer: CurrentTestAnswerSerializer
  end

実行結果

 TestAnswer Load (0.8ms)  SELECT `test_answers`.* FROM `test_answers` WHERE `test_answers`.`test_id` = 3
  ↳ app/controllers/api/v1/current/tests/test_answers_controller.rb:39:in `all_index'
[active_model_serializers]   TestAnswerDetail Load (0.7ms)  SELECT `test_answer_details`.* FROM `test_answer_details` WHERE `test_answer_details`.`test_answer_id` = 5
[active_model_serializers]   ↳ app/controllers/api/v1/current/tests/test_answers_controller.rb:39:in `all_index'
[active_model_serializers] No serializer found for resource: #<TestAnswerDetail id: 25, test_answer_id: 5, score: 2, question_id: 21, created_at: "2025-01-29 04:08:36.614623000 +0000", updated_at: "2025-01-29 04:08:36.614623000 +0000">
[active_model_serializers]   TestAnswerDetail Load (0.9ms)  SELECT `test_answer_details`.* FROM `test_answer_details` WHERE `test_answer_details`.`test_answer_id` = 6
[active_model_serializers]   ↳ app/controllers/api/v1/current/tests/test_answers_controller.rb:39:in `all_index'

上記のように、TestAnswer(回答結果)のレコード(id=5,6)ごとに、TestAnswerDetailsの検索クエリが発行されていて、N+1問題が起こっています。

N+1問題とは、データベースから取得した1つのレコードに対して、関連するデータを取得するために、関連するテーブルに対して複数のSQLクエリを発行してしまう問題のことです。

N+1問題が起こると、重複した不要なクエリが何度も発行されるためにメモリの使用量が増えたり、データベースの問合せ回数の増加により、応答時間が増えたりしてアプリケーションのパフォーマンスが下がってしまいます。

今回の場合は、以下の流れでN+1問題が起こってしまいます。

  • ある一つのTest(テスト)に紐づく、すべてのTestAnswer(回答結果)を検索するクエリが発行される。
  • 一件目のTestAnswer(回答結果)に紐づく、すべての質問の回答結果の詳細(TestAnswerDetails)を検索するクエリが発行される。
  • 二件目のTestAnswer(回答結果)に紐づく、すべての質問の回答結果の詳細(TestAnswerDetails)を検索するクエリが発行される。
  • 三件目...(すべての回答結果の件数だけ、内容のクエリが発行される)

上記のように、回答結果の数Nだけ、すべての質問の回答詳細を検索するクエリが発行され、N+1問題が発生してしまいます。

N+1問題の解決策

これを解消するために、以下のようにincludes関数を使ってN+1の問題を解消しました。

rails/app/controllers/api/v1/current/tests/test_answers_controller.rb
def all_index
    ...
    @test_answers = TestAnswer.where(test: @test).includes(:test_answer_details) # ←ここで、N+1問題が発生が発生していたためincludesを追加
    render json: @test_answers, each_serializer: CurrentTestAnswerSerializer
  end

実行結果

TestAnswer Load (0.7ms)  SELECT `test_answers`.* FROM `test_answers` WHERE `test_answers`.`test_id` = 3
  ↳ app/controllers/api/v1/current/tests/test_answers_controller.rb:39:in `all_index'
  TestAnswerDetail Load (1.1ms)  SELECT `test_answer_details`.* FROM `test_answer_details` WHERE `test_answer_details`.`test_answer_id` IN (5, 6)
  ↳ app/controllers/api/v1/current/tests/test_answers_controller.rb:39:in `all_index'

上記の結果のように、2回クエリが発行されるだけで処理が終了しています。

includes関数を追加することで以下のようにクエリが発行されるようになります。

  • ある一つのTest(テスト)に紐づく、すべてのTestAnswer(回答結果)を検索するクエリが発行される。
  • 上記で取得したTestAnswerに紐づく、すべての質問の回答結果の詳細(TestAnswerDetails)を取得する。

上記の流れで本来、取得した回答結果の数Nだけ、クエリが叩かれていたものの、2件に止めることができました。

そして、N+1問題の解決によって、メモリの使用量や実行時間の削減をすることができ、パフォーマンスを向上させることができました。

工夫したところ 【フロントエンド】

1.結果をグラフ化して、直感的にテストの結果が分かるようにした。

フロントエンドでは、chart.jsを使って、テストの結果を

  • 縦軸:点数
  • 横軸:期間

の折れ線グラフで表示しました。

このグラフを表示することによって、ユーザの期間ごとのテスト結果の違いが分かります。

また、実際の研究で使われている平均点も表示することで、自分のテストで測る指標を客観的なデータと比較して、理解することができます。

2.研究論文を紹介して、テストで測った指標のメリット・デメリットや改善案を提示

テスト結果のグラフの下部に研究についての説明やテストで測った指標の改善案を紹介する文を記載しました。

テストの結果の下に研究内容や改善案を提示することで、
ユーザが自分が得たテストの数値的な結果に加えて、さらにその結果のメリットやデメリットやテストで測った指標を改善するための具体的な方法を知ることができます。

改善案例(自己概念)

3.科学者のテストの編集画面にて、MarkDownで編集できるようにした。

MarkDownを使って科学者がテストや研究の説明を編集できるようにすることで、図や表、箇条書きなどを使って見ている人により分かりやすい文章を簡単に書けるようにしました。

以下のように科学者の管理画面では、テストをMarkDown形式で編集することができます。

ただし、写真はアップロードできず、論文で紹介したいグラフデータや分かりやすい図の説明を表示することができていません。

今後のテストのMarkDownでの編集画面の改善点としては以下の2点があげられます。

  • 写真のアップロードを可能にして、論文で使われている図や表をMarkDownで表示できるようにする。
  • 研究論文のPDFを読み込むと、説明・各質問の作成を自動で生成系AIが行うようにする。

参考: [Markdown] 私がMarkdownを使用する理由_yuki325

データベース設計

以下は、このアプリのデータベース設計となっています。

インフラ設計図

終わりに

このアプリの開発にかかった期間は、約1か月(ユーザ周りの開発が2週間、科学者周りの開発が2週間)でした。ユーザ周りの開発は順調にいったものの、のちに科学者のテスト編集ページを開始してからはやや手間がかかりました。

具体的には、科学者の機能を作ってからはユーザと科学者でアカウントを分けて、認証を行う必要があり、バックエンドRailsやフロントエンドNext.jsでのユーザと科学者が行う動作や画面表示を分けて開発するのに思いのほかに手間がかかりました。

しかし、最後まで仕上げることができ、とても充実した気分です。

また、一通り完成したものの、このアプリには以下のような改善点があります。

  • 科学者の管理画面で作成したテストの回答結果をグラフ等で表示する機能を作りたい。

  • テストの説明や、各質問について科学者が手動で編集するようになっていて手間がかかるため、生成系AIが論文のPDFを読み取って自動でテストの説明や各質問を作成する機能を作りたい。

  • 写真をアップロードできるようにして、研究で使われている図や表をMarkDownで表示させたい。

  • ログインがメールアドレスのみになっているため、Google認証を追加して、ログインの利便性を上げたい。

上記のような改善点がまだ残っているため、機会があれば再度開発をスタートしたいと思います。

また、今回作成したアプリでは以下の技術本がとても役に立ちました。とくにMarkDownのつくり方や、テストの状態(公開、非公開、下書き)を管理する方法について参考になりました。

【独学ポートフォリオ開発応援】実務未経験から学べる!Rails×Next.js×AWSハンズオン解説

以上読んでいただきありがとうございました。

Discussion