🎉

【個人開発 アプリ】使用技術・gemの選定理由

2024/09/10に公開

はじめに

こんにちは。プログラミングスクールでRuby on Railsを学習しているMockeyと申します。
個人開発のアプリ「Travel Starter」で使用した技術の選定理由について言語化しておきたいと思い、記事を書くことにしました。
少しでお役に立てることがあれば幸いです。

サービス概要

「Travel Starter」は、海外旅行が不慣れな人に向けて必要な情報を一元管理し、必要な準備のサポートをするサービスです。
Travel Starterを使うことで、海外旅行に行く際の不安を軽減し、旅行のハードルを下げることを目指しています。
https://adventure-travel-starter.com
サービスURL:https://adventure-travel-starter.com/ 
GithubリポジトリURL: https://github.com/miki-ymmt/travel_app

技術スタック

使用した技術の一覧です。

カテゴリー 使用技術
フロントエンド Rails 7.1.3.4(Hotwire/Turbo/Stimulus), JavaScript, TailwindCSS, DaisyUI
バックエンド Rails 7.1.3.4 (Ruby 3.2.2 )
インフラ Render.com, AWS S3
データベース PostgreSQL
認証 Sorcery, Googleログイン, LINE認証
開発環境 Docker
API OpenAI API, LINE Messaging API, OpenWeather API

開発環境

Dockerを使用しています。理由は以下の2点です。

  • スクール内のカリキュラムでDockerを使用していたため

  • 将来的にエンジニアになった際に必須知識だと考えたため

    1点目に関して、スクールのカリキュラムではDockerを用いた環境構築を始めとするwebアプリ開発の課題に取り組んできました。スクール内ではDockerを使用した環境構築イベントや資料のおかげもあり、使用するハードルが下がったことも大きいです。

    2点目ですが、Dockerについて学習を進める中で、将来エンジニアになった際には必要とされる知識だと考えました。現場に出ると、複数名での開発が想定されます。 Dockerを使うことで、アプリケーションの実行環境をコンテナとして管理するため、開発者のOSやローカル環境が違っても同じ環境を再現できるので環境依存のエラーに遭遇しにくいというメリットがあります。
    就職前に少しでも慣れておきたいという思いから採用いたしました。

デプロイ

Renderを使用しています。理由は以下の通りです。

  • 毎月利用できる無料枠があるなど、シンプルな料金体系である
  • デプロイのしやすさと管理
  • Cron Jobsのサポート

1点目に関しては公式サイトの料金表をご覧いただければと思いますが、Webサービスやデータベースを無料で使える枠があります。ただし、ストレージ容量やバックアップの頻度によって追加料金が発生します。($7/月〜)
また、スリープモード対策として、UptimeRobotで定期的にwebアプリの自動監視を行っています。

2点目ですが、RenderはGitHubと連携しており、コードをプッシュし、マージするだけで自動的にデプロイされます。ミニアプリ作成時に環境変数の設定やログの確認、Shellコマンドの実行などがしやすいと感じたため、卒業制作でも引き続き使用することに致しました。

3点目は、LINE通知を送るjobの実行方法としてCron Jobsを使用するためです。月1$ほどで利用できます。バックグラウンドで定期的にタスクを実行する場合にRailsのSidekiqやResqueなどのキュー管理ツールが不要です。スケジュール設定も容易なので、定期タスクの実行が簡素化されてスムーズになります。

フロントエンド

インストールしたものはTailwindcssDaisyUIです。
TailwindcssはHTMLに直接ユーティリティクラスを適用できるので開発スピードが上がると考え採用しました。
また、スマートフォンでの閲覧が多いことを考えてモバイル対応が必須でしたが、sm:, md:, lg:, xl:といったメディアクエリをクラスに付け加えるだけで、異なる画面サイズに対応できるためスムーズな実装に繋がりました。

DaisyUIはTailwind CSSの上に構築されたUIコンポーネントライブラリであり、ボタンやカード、ナビゲーションバー、モーダルなど1からデザインをすると時間がかかるものもある程度完成されたデザインで提供されているので採用しました。カラーや大きさをカスタムするだけで見た目が整うのでアプリの統一感も作れました。

アプリ開発では機能面の実装に時間をかけたいと考えていたため、スピード感を持ちながらデザインもきちんと整えられたので、効率よく開発を進めることができました。
JSなどフロントエンド言語の知識は足りない部分も多く、Rails7に標準搭載されているturboやstimulusを使用して、非同期の実装を行いました。

バックエンド

Ruby on Railsを使用しています。
理由としては、スクールでRuby on Railsをメインで学んでおり、学んだ知識を実際のアプリ開発を通してアウトプットすることで理解を深めたいと考えたからです。
アプリ開発を通じて、カリキュラムでは触れなかったRailsの機能も新たに習得することができました。Railsガイドの読み方にも徐々に慣れ、自分で問題を解決できる力を養うことができた点は大きな収穫です。

gem 'sorcery'

認証機能はSorceryを使用しています。理由は以下です。

  • カリキュラムやミニアプリで何度か使用経験があったこと
  • パスワードリセットやGoogle認証の実装イメージが自分の中でクリアだった

比較したgemはDeviceです。こちらの記事にもあるように、スターの数を見るとDeviceの方が人気でした。それもそのはず、豊富な認証機能がすぐに使えるからです。
しかし、私のアプリはパスワードログイン、パスワードリセット、Googleログインができれば十分だったため、より軽量性のあるsorceryを使用し、必要な機能を追加しました。

gem 'carrierwave'

carrierwaveを写真のアップロード機能のために使用しています。
カリキュラムでも使用しており、比較的に簡単に導入できるので採用しました。CarrierwaveのUploaderで画像の処理に関するコードを記述して、画像はAWS S3に保存しています。

比較したライブラリはRailsの機能であるAvtive Storageです。
比較するにあたり、こちらの記事が分かりやすかったです。
https://qiita.com/w5966qzh/items/510d4c2a3829524b2e64
https://zenn.dev/meimei_kr/articles/571d350dabfc7e


外部ストレージとの連携に関してはさほど差がないように見受けられましたが、Railsガイドによると、

  • キャッシュ機能が装備されていないため、saveが成功しない限り添付ファイルの再アップロードが必要になります。ダイレクトアップロードの設定をすればバリデーションエラーに失敗してもアップロードは失われないものの、別途ファイルの設定が必要です。
  • デフォルトのバリデーションヘルパーがないため、Active Storage Validations gemを利用する必要があります。

また、参考記事に記載があるようにDBの内容がやや複雑であると感じました。

active storageを導入すると、BlobとAttachment の2つのモデルが自動的に生成されて、画像データはBlobに保管してAttachmentで紐づける

gem 'ruby-openai'

https://github.com/alexrudall/ruby-openai?tab=readme-ov-file
AIを搭載したチャットボット機能のために使用しています。OpenAI APIをrubyプログラムで使用するために必要なgemです。導入には以下の公式ドキュメントや記事も参考にしました。

そもそもチャットボットを作成するにあたっては、Googleが提供する自然言語プラットフォームであるDialogflowを使用するか迷いました。Siriのような会話ができるAIチャットbotの開発がシンプルにでき、アプリに組み込めるかもしれないと考えたからです。
しかし、プログラミングを学習している立場上、特徴であるプログラミング不要な点が気になり採用に至りませんでした。
最終的に以下の理由で使用しています。

  • 以前OpenAI APIを使用した経験があり、APIの使用方法や実装手順が明瞭だったこと
  • 新しいモデル(GPT-4o mini)ではコストパフォーマンスが向上され、かつGPT-3.5よりも早くて高性能であること

gem 'httparty', '~> 0.22.0'

https://github.com/jnunemaker/httparty?tab=readme-ov-file
最新バーションのインストールはこちらからできます。
外部API(line-bot-apiやOpenWeather API)との通信をするために導入しました。
外部APIとやり取りする際にはHTTPリクエストが不可欠ですが、HTTPartyを使うことで、短いコードで必要な機能を実装できる点が特徴です。
gemを使わずともHTTP通信はできますが、gemを使用するメリットは以下のようにあると考えています。

  • コードが短く、簡潔なため読みやすくなるので保守性が上がる。
  • HTTParty.postHTTParty.getを使うだけで、GETやPOSTリクエストを送信できるため、HTTPリクエストを簡潔に書くことができ、開発の効率が上がる。
    以下のコードでは、HTTParty.postを使ってLINEのAPIにリクエストを送信して、アクセストークンを取得している部分が該当します。
app/controllers/line_auth_controller.rb
def get_line_token(code)
    client_id = ENV['LINE_LOGIN_CHANNEL_ID']
    client_secret = ENV['LINE_LOGIN_CHANNEL_SECRET']
    redirect_uri = line_auth_callback_url

    response = HTTParty.post('https://api.line.me/oauth2/v2.1/token', {
                               body: {
                                 grant_type: 'authorization_code',
                                 code:,
                                 redirect_uri:,
                                 client_id:,
                                 client_secret:
                               },
                               headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
                             })

    JSON.parse(response.body) # APIからのレスポンスをJSON形式に変換
  end
  • レスポンスの状態コードの確認が簡単にでき、レスポンスの詳細情報が取得できる点。API通信が失敗した理由を特定しやすくなる。
response = HTTParty.get('https://api.example.com/data')
unless response.success?
  puts "Error code: #{response.code}"
  puts "Error message: #{response.message}"
  puts "Response body: #{response.body}"
end



類似gemとしてhttp.rbもあります。メモリ使用量がHTTPartyよりも小さいので、大規模なAPIリクエストやメモリ効率が重要なプロジェクトに向いています。

(参考サイト)10MBのデータをダウンロード及びアップロードしたときのメモリ使用量を計測したもの
Image from Gyazo

こちらの導入も迷いましたが、APIリクエストの量が多くない点とスター数が httpartyが5.8k、http.rbが3kということもあり、より広く使われているhttpartyに決めました。

Googleログイン

アプリのログインには、Eメール・パスワードログインに加えてGoogleログインを導入しました。
理由としては、ボタン一つ押せばログインできるので私自身が普段から"楽"だと感じていたためです。
人によっては、プライバシーな情報(メールアドレスなど)を登録したくない方もいると思うので、そういった観点でも外部認証を入れて良かったなと思います。

他の外部認証も合わせて入れることも考えましたが、ユーザーの選択肢を広げすぎることも迷わせてしまうことに繋がるかと思い、Googleログインのみにいたしました。

OpenWeatherAPI

Image from Gyazo
旅先の天気と気温情報を取得するために導入しました。基本機能が無料で使えます。
導入には以下の公式ドキュメントや記事を参考にしています。

私のアプリでは天気と気温だけの表示ですが、プランによって湿度や大気圧、日の出時刻や日没時刻など多岐にわたる情報がAPIから取得できます。

【Freeプランの特徴】

  • 60回/分、1,000,000回/月のリクエスト
  • Current Weather
  • 3-hour Forecast 5 days(3時間の予報を5日分)

【天気情報の取得に関する工夫】
1分間に60回以上APIを呼び出すことはそう考えられませんが、APIリクエストの制限へのリスク対策から、一定期間内に取得した天気情報を再利用することで不要なAPIリクエストを避けるようにしました。

  • DBに保存されている天気情報があるかどうかを確認し、最近取得した天気情報をデータベースから探す。
  • データベースに最新の天気情報がある場合は、キャッシュを利用して天気情報を返す。
  • 天気情報がない、または情報が古い場合はAPIから新しい情報を取得する。

weatherモデルでは以下のように「指定された trip_id に対応する天気情報を取得し、1分以内に取得された最新の天気情報が存在していればそのデータを返す」旨の定義をしています。

app/models/weather.rb
def self.recent_for_trip(trip_id)
    where(trip_id:).where('fetched_at >= ?', 1.minute.ago).order(fetched_at: :desc).first
end

LINE Messaging API

gem 'line-bot-api'をインストールしています。

公式ドキュメント説明

LINE Messaging API SDKには、ライブラリ、ツール、およびサンプルが含まれています。SDKを使えば、Messaging APIを組み込んだボットアプリの開発を簡単に始めることができます。公式SDKとコミュニティSDKの両方とも、オープンソースとして提供されておりさまざまなプログラミング言語で利用できます。

LINEのMessaging APIを使ってLINE Botを開発するための公式Ruby SDKです。これを使うことで、LINEの公式アカウントと連携してメッセージの送受信ができます。
アプリでは旅行の7日前、3日前、1日前にメッセージが届くLINE通知機能の実装をしています。

一番難しかったことは、アプリのユーザーとLINEのユーザーの紐付けです。LINE Messaging APIのみではアプリユーザーとの紐付けはできず通知が送れないので、LINE Messaging APIとは独立したチャネルが必要です。
紐付け方法として、アプリ内でLINEログインを導入して連携してもらうようにしました。
検証はしていませんが、LINEログインのみでアプリにログインしてもらう場合、この辺りの実装方法は異なっていたと思います。

おわりに

今回は就職活動に向けて整理するために、作成したアプリの技術選定について言語化しました。今後も実装する中で比較検討をしっかりと行った上で、採用の判断をしていきたいと思います。

今回もご覧いただきありがとうございました。

そのほか参考

Discussion