個人開発で使用した技術やサービスについてまとめ
個人で開発していたスマホアプリを先日ストアで公開しました。
アニソン好きな方はぜひプレイしてみてください。
この記事ではアプリを開発する上で使用した技術や外部サービスについて書きます。何かしら参考になれば幸いです。
赤字サービスなので、出来るだけケチって月20$くらいに収めました。所々妥協すればもうちょっと安くなると思います。
ケチりつつも何かあった時にすぐ対応できるようにエラー検知できるようにしたり、サービスの利用状況を分析できるようにはしています。
システムの概要は以下のようになっています。 スマホアプリとAPIサーバー、Redashサーバー、DB、管理画面用のSPAを動かしています。
サーバーサイド関連
Rails + GraphQL
Railsは使い慣れているのと、GraphQLは使ってみたかったから使いました。
興味のある技術をエイヤで採用できるのが個人開発の良いところです。
GraphQLはRailsでREST APIにするよりは使い勝手が良かったので、次に同じような構成にするときも使おうと思っています。
APIドキュメント的なものやTypeScriptの型を自動生成してくれるおかげで、クライアント側の実装が特に楽になりました。
GraphQLの学習には以下のサイトを利用しました。
これさえ読めばGraphQLを完全に理解した気になれます。複数のプログラミング言語に対応していてとてもわかりやすいのでおすすめです。
エラーハンドリングや認可など、どうすれば良いか悩んだらgraphql-rubyの公式ドキュメントを読むとだいたい解決します。
Sentry
エラートラッキングサービス
Railsで起きたエラーを検知してSlackに通知してもらっています。
アプリ側のエラーもSentryに検知してもらおうと思っていたのですが、ReactNativeでSentryのパッケージとreact-native-configというパッケージの相性が悪かったので、アプリ側は後述するBugsnagというサービスを使いました。
Dependabot
リポジトリが依存しているパッケージに更新があったらプルリクエストを作ってくれるサービスです。
プルリクエストのコメントにリリースノートなども載せてくれて親切です。
また、更新の種類によって security や bug などのラベルもつけてくれるので、急いで更新すべきかが一目でわかります。
Redash
データ可視化ツール
DBに溜まったデータを分析したかったのでHerokuにRedashサーバーを立てました。
見たい時だけ動いてくれれば良いので、無料プランで運用しています。
Redashサーバーはこのリポジトリを参考にして30分ほどでできました。
インフラ関連
Heroku
開発を始めた当初はAWSのECSを使おうと思っていたのですが、最低限の構成でもお高くつきそうだったので、Herokuを使いました。特にLBがお高い・・・
デプロイパイプラインとかStaging環境とか自分で構築すると恐ろしく面倒くさいことが簡単にできてしまうので、ついついHerokuに甘えてしまいます。
Staging環境とRedashサーバーでは無料のFreeDyno、本番環境では今のところ7$のHobbyDynoを使用しています。
Heroku Postgres
Staging環境とRedashサーバーで無料のHobbyDevプランを使っています。
HerokuPostgresのHobbyDevプランと9$のHobbyBasicプランはメモリキャッシュしてくれないので、本番環境では代わりにAWSのRDSを使いました。
Herokuのサーバーはアメリカにあるので、RDSのリージョンはバージニアを選択しています。インスタンスタイプはt3.microです。
出来るだけStaging環境と本番環境で構成を変えたくないのですが、Staging環境にお金はかけたくないので妥協しました。
CloudFlare
CDNサービス
DNSとファイアウォールの機能を使用しています。
ファイアウォールの設定が、IP制限できたり日本以外のアクセスを制限できたりと結構柔軟に設定できます。
New Relic
アプリケーションのパフォーマンス監視サービス
Herokuのアドオンで入れています。
無料プランだとログが1日までしか保存されないですが、普段ダッシュボードを見ることはほぼなく、異常が起きた時に知らせてくれればそれで良いので、閾値を超えたらSlackにアラートを飛ばしてくれるように設定しています。
Timber.io
ロギングサービス
Herokuのアドオンで入れています。
似たようなロギングサービスはいくつかあったのですが、どれでもできることはそう変わらなそうだったので、あまり調べずに使いやすそうなものを選びました。
無料プランだとどのサービスも数日しかログを残してくれないので、念の為S3とかにログを永続化しようかなと思っています。
LogDNAというサービスを使用してS3にログを永続化できるようです。
Uptime Robot
HTTP監視サービス
apiサーバーのヘルスチェック用のURLに定期的にリクエストを投げて死活監視してくれています。
正常なレスポンスが帰ってこなかったら、slackに通知してくれます。
フロントエンド関連
React
管理画面をSPAにするためReactを使っています。
material-component や parcel を使って手を抜けるところは抜いています。
SEOを気にする必要がないのでSSRもしていません。
TypeScript
TypeScriptがあるのとないのとではコードの変更し易さが段違いなので、ここ1年くらいは必ず使っています。
Apollo Client
GraphQL用のクライアント
RelayというGraphQLクライアントもありますが、そちらは試していません。
確かhowtographqlでApolloがオススメされていたので、そのままApolloを選んだ記憶があります。
こちらもGraphQLと同様にhowtographqlで入門して、足りない部分を公式ドキュメントで補うやり方で十分でした。
Netlify
静的サイトのホスティングサービス
静的サイト版Herokuみたいな感じで簡単便利です。
GitHubのリポジトリに連携させると、ビルドとデプロイをシュッと行なってくれます。
アプリ関連
ReactNative
去年仕事でReactNativeを書いていて、使い心地も気に入っていたのでそのまま使用しました。
ただ、アプリのビルドの設定やリリース周りの作業だけは何度やっても面倒で嫌いです。
Expoを使えばその辺よしなにやってくれるのですが、代わりにネイティブモジュールを自由に使えないデメリットがあるので、Expoは使いませんでした。
AppCenter
アプリ関連のことを色々やってくれるサービスです。
ReactNativeもサポートしていて、公式のドキュメントも充実しています。
以下の機能を使いました。
- アプリのビルド
- アプリのストアへのリリース
- CodePush
課金の形式は、合計ビルド時間が一月に240分を越えると有料になるといった形で、1ビルド5〜10分ほどかかるので無駄にビルドしているとあっという間に無料分がなくなります。
リリース前はどうしてもビルド回数が増えてしまい、ギリギリ240分を超えてしまったので、1月分だけ課金しました。4,480円もかかるのでお高いです。
お金払ってでもAppCenterを使った理由は、使い慣れているからというだけなので、無料で使えそうなBitriseを使うとか手元でビルドするとかでも良いと思います。
Firebase
色々と機能がありますが、AnalyticsとDynamicLinksだけを使いました。react-native-firebaseモジュールを使って設定しています。
Analyticsはそのまま使っても画面遷移の情報が取れないので、ReactNativeの画面遷移で使っているreact-navigationモジュールを使って遷移情報をAnalyticsに送信するようにしています。
Screen tracking for analytics · React Navigation
GoogleAnalyticsを使った例ですが、トラッキング部分のコードを差し替えればFirebaseAnalyticsでも使えます。
Bugsnag
前述の理由でSentryを使わずにBugsnagを使いました。
ReactNativeがサポートされているという理由で選んでいます。
SentryよりUIが好みなので、今後はこっちを使うかもしれません。使ってみた感じだと、できることは大差なさそうです。
CodePushで配信した.jsbundleのsource mapをアップロードする方法について、公式で解説されているのが地味に助かりました。
https://docs.bugsnag.com/platforms/react-native/react-native/showing-full-stacktraces/
CircleCI
定番のCIサービス。 テストを実行したり、codepushさせたりしています。
その他
Zoho Mail
独自ドメインのメールホスティングを無料で利用できるサービス。
この手のサービスはGoogle Appsをはじめ有料なイメージがあるので、無料なのは驚きました。最初は怪しいサービスかと思った
AppFollow
アプリのレビューやランキングの表示、ASOをサポートしてくれるサービス。
無料プランでは2アプリまで登録できるので、とりあえず試しに使ってみています。
アプリのレビューがあったりキーワードの順位が変動したらslackに通知してくれるように設定しています。
Expo + React Native Debugger でデバッグする
React NativeでDOM要素をデバッグするとき、Web開発のようにChromeデベロッパーツール風の画面でデバッグする方法があったので書きます。 Expo XDEとAndroidかiOSのシミュレータが必要になるので、インストールしておいて下さい。
react-native-debugger のインストール
$ brew update && brew cask install react-native-debugger
Expo XDE でプロジェクトを開く
起動したら、歯車アイコンをクリックして、Host -> LAN を選択。Development Modeにチェックが入っていることを確認。
DeviceアイコンからiOSかAndroidのシミュレータを起動。
React Native Debugger を起動
Expoはport19001でデバッガを実行するので、React Native Debuggerにportを指定して起動する必要があります。
$ open "rndebugger://set-debugger-loc?host=localhost&port=19001"
デバッグする
シミュレータでExpoアプリの設定画面を開き「Debug Remote JS」を押すと、デバッグができるようになります。
参考:
Cloud Function から Cloud SQL を使う方法
Cloud Functions for Firebase から Cloud SQL(MySQL)を使いたかったのですが、現状公式でトリガーが用意されていませんでした :cry:
Cloud Functions for Firebase | Firebase
調べたところ、nodejsのmysqlライブラリを使えばできました。
const mysql = require("mysql"); exports.handler = function handler(req, res) { const c = mysql.createConnection({ socketPath: "/cloudsql/" + "$PROJECT_ID:$REGION:$DBNAME", user: "$USER", password: "$PASS", database: "$DATABASE" }); c.connect(); c.query(`SELECT * FROM table`, (e, results) => { // callback }) }
参考:
ReactNavigationで遷移先に渡した値を this.props.hoge で参照したいとき
ReactNavigationで遷移先の画面に値を渡したいとき、
navigate('SecondScreen', { user: 'Hoge' })
のように指定すれば渡せますが、遷移先で値はthis.props.navigation.state.pramas.user
に入ります。
そうではなく this.props.user
に入っていてほしかったので調べたところ、以下のようにすれば解決しました。
const mapNavigationStateParamsToProps = (ScreenComponent) => { return class extends React.Component { static navigationOptions = ScreenComponent.navigationOptions; render() { const { params } = this.props.navigation.state; return <ScreenComponent {...this.props} {...params} />; } }; }; const MainNavigator = StackNavigator({ firstScreen: { screen: mapNavigationStateParamsToProps(FirstScreenComponent) }, secondScreen: { screen: mapNavigationStatePramasToProps(SecondScreenComponent) } });
参考: github.com
CRNAで作成したReactNative開発環境にTypeScriptを導入する
前回、CRNAでReactNativeの開発環境を構築したので、その続きでTypeScriptを導入していきます。
TypeScriptをインストール
$ yarn add --dev typescript yarn add v1.3.2 [1/4] 🔍 Resolving packages... [2/4] 🚚 Fetching packages... [3/4] 🔗 Linking dependencies... warning "jest-expo > babel-jest@21.2.0" has unmet peer dependency "babel-core@^6.0.0 || ^7.0.0-alpha || ^7.0.0-beta || ^7.0.0". [4/4] 📃 Building fresh packages... success Saved lockfile. success Saved 1 new dependency. └─ typescript@2.6.2 ✨ Done in 10.84s.
expoのTypeScript型定義ファイルが足りないため、@typesパッケージをインストール
$ yarn add --dev @types/expo yarn add v1.3.2 [1/4] 🔍 Resolving packages... [2/4] 🚚 Fetching packages... [3/4] 🔗 Linking dependencies... warning "jest-expo > babel-jest@21.2.0" has unmet peer dependency "babel-core@^6.0.0 || ^7.0.0-alpha || ^7.0.0-beta || ^7.0.0". warning "react-native-scripts > xdl > glob-promise@3.3.0" has unmet peer dependency "glob@*". [4/4] 📃 Building fresh packages... success Saved lockfile. success Saved 4 new dependencies. ├─ @types/expo@24.0.3 ├─ @types/fbemitter@2.0.32 ├─ @types/react-native@0.52.1 └─ @types/react@16.0.34 ✨ Done in 19.22s.
ちなみに@typesパッケージは以下で探せます
設定ファイルを書く
tsconfig.json
{ "compilerOptions": { "target": "ES2017", // tsではなくbabelのasync/awaitを使うため、targetを高く設定する "module": "ES2015", "jsx": "react-native", // 成果物を読みやすくするため、jsxで生成する "outDir": "./dist", "noImplicitAny": true, // 暗黙のany型の宣言や式を警告する }, "exclude": ["node_modules", "dist"] }
ビルド
$ tsc -w -p .
以上でTypescriptの導入完了です
ReactNativeの開発環境をサクッと構築
アプリを作ってみたくなり、Web開発者としてとっつきやすそうなReactNativeに入門してみました。 この記事の内容は、開発環境構築から実機で動作確認するところまでです。
環境
今回は試してませんが実機の代わりにシミュレータでも動作します。
XDEをインストール
以下のリンクからExpo SDKをDLしてインストールします。 起動するとアカウント登録を促されるので登録します。
CRNAをインストール
面倒な環境構築をすっ飛ばしてくれるすごいやつ。 好みでyarnを使ってインストールしていますが、もちろんnpmでもできます。
$ yarn global add create-react-native-app yarn global v1.3.2 [1/4] 🔍 Resolving packages... [2/4] 🚚 Fetching packages... [3/4] 🔗 Linking dependencies... [4/4] 📃 Building fresh packages... success Installed "create-react-native-app@1.0.0" with binaries: - create-react-native-app ✨ Done in 28.44s.
インストールしたら早速アプリを作成しましょう。
名前は何でもいいのでtest-app
としました。
$ create-react-native-app test-app Creating a new React Native app in /Users/yuto/practice/react-native/test-app. Using package manager as yarnpkg with yarn interface. Installing packages. This might take a couple minutes. Installing react-native-scripts... ... Success! Created test-app at /Users/yuto/practice/react-native/test-app Inside that directory, you can run several commands: yarn start Starts the development server so you can open your app in the Expo app on your phone. yarn run ios (Mac only, requires Xcode) Starts the development server and loads your app in an iOS simulator. yarn run android (Requires Android build tools) Starts the development server and loads your app on a connected Android device or emulator. yarn test Starts the test runner. yarn run eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd test-app yarn start Happy hacking!
作成されたディレクトリに移動し、開発サーバーを起動します。
$ cd test-app/ $ yarn start See https://git.io/v5vcn for more information, either install watchman or run the following snippet: sudo sysctl -w kern.maxfiles=5242880 sudo sysctl -w kern.maxfilesperproc=524288
watchman
を入れてねと言われたのでインストールします。
watchman
はファイルシステムの監視ツールです。
$ brew install watchman
気を取り直して再度起動します。
$ yarn start
うまくいくとビルドがはじまり、ターミナルにQRコードが表示されます。 iOSかAndroidの実機を用意し、expoアプリから上記のコードを読み込むと、実機で動作確認することができます。
コードの変更もすぐに反映されます。お手軽すごい。
ついでにコンポーネントライブラリを入れて遊んでみます。 NativeBaseをインストール
$ yarn add native-base $ yarn add @expo/vector-icons
App.js
を以下のように変更しました。
NativeBaseにはカスタムフォントが含まれるため、コンポーネントをレンダリングする前にフォントをロードするようにしました。
他にはrender内に公式のサンプルコンポーネントのコードを適当に追加しています。
import React from 'react'; import { Container, Header, Content, Footer, FooterTab, Button, Icon, Text } from 'native-base'; import Expo from 'expo'; export default class App extends React.Component { state = { fontLoaded: false }; async componentWillMount() { await Expo.Font.loadAsync({ 'Roboto': require('native-base/Fonts/Roboto.ttf'), 'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'), }); this.setState({fontLoaded: true}); } render() { return ( <Container> <Header /> <Content /> { this.state.fontLoaded ? ( <Footer> <FooterTab> <Button vertical> <Icon name="apps" /> <Text>Apps</Text> </Button> <Button vertical> <Icon name="camera" /> <Text>Camera</Text> </Button> <Button vertical active> <Icon active name="navigate" /> <Text>Navigate</Text> </Button> <Button vertical> <Icon name="person" /> <Text>Contact</Text> </Button> </FooterTab> </Footer> ) : null } </Container> ); } }
実機で確認 👀
iOS | Android |
---|---|
【Scala】型パラメータを指定せずに呼び出せるメソッド
Option型に対して中身があるかどうかを判定するメソッドを書いている時に、Optionの中身の型は気にしないで判定する共通のメソッドがほしくなりました。
以下のように型パラメータを指定してあげれば、メソッドを呼ぶ時に型を指定しなくても型推論が効いてくれて動作しました。
scala> def require[T](param: Option[T]): T = { | param match { | case Some(x) => x | case None => throw new Exception | } | } require: [T](param: Option[T])T
メソッドを呼び出してみる
scala> val hoge: Option[Int] = Option(123) hoge: Option[Int] = Some(123) scala> require(hoge) res0: Int = 123 scala> val fuga: Option[String] = Option("aaa") fuga: Option[String] = Some(aaa) scala> require(fuga) res1: String = aaa scala> val none: Option[String] = Option(null) none: Option[String] = None scala> require(none) java.lang.Exception at .require(<console>:14) ... 29 elided