個人開発で使用した技術やサービスについてまとめ

個人で開発していたスマホアプリを先日ストアで公開しました。
アニソン好きな方はぜひプレイしてみてください。

AnitroQ

AnitroQ

  • Yuto Ota
  • Games
  • Free

play.google.com

この記事ではアプリを開発する上で使用した技術や外部サービスについて書きます。何かしら参考になれば幸いです。

赤字サービスなので、出来るだけケチって月20$くらいに収めました。所々妥協すればもうちょっと安くなると思います。
ケチりつつも何かあった時にすぐ対応できるようにエラー検知できるようにしたり、サービスの利用状況を分析できるようにはしています。

システムの概要は以下のようになっています。 スマホアプリとAPIサーバー、Redashサーバー、DB、管理画面用のSPAを動かしています。

f:id:kinakobo:20190517002430p:plain
システム概要図

サーバーサイド関連

Rails + GraphQL

Railsは使い慣れているのと、GraphQLは使ってみたかったから使いました。
興味のある技術をエイヤで採用できるのが個人開発の良いところです。
GraphQLはRailsREST APIにするよりは使い勝手が良かったので、次に同じような構成にするときも使おうと思っています。
APIドキュメント的なものやTypeScriptの型を自動生成してくれるおかげで、クライアント側の実装が特に楽になりました。

GraphQLの学習には以下のサイトを利用しました。

www.howtographql.com

これさえ読めばGraphQLを完全に理解した気になれます。複数のプログラミング言語に対応していてとてもわかりやすいのでおすすめです。

エラーハンドリングや認可など、どうすれば良いか悩んだらgraphql-rubyの公式ドキュメントを読むとだいたい解決します。

graphql-ruby.org

Sentry

エラートラッキングサービス

sentry.io

Railsで起きたエラーを検知してSlackに通知してもらっています。
アプリ側のエラーもSentryに検知してもらおうと思っていたのですが、ReactNativeでSentryのパッケージとreact-native-configというパッケージの相性が悪かったので、アプリ側は後述するBugsnagというサービスを使いました。

Dependabot

リポジトリが依存しているパッケージに更新があったらプルリクエストを作ってくれるサービスです。

dependabot.com

プルリクエストのコメントにリリースノートなども載せてくれて親切です。
また、更新の種類によって security や bug などのラベルもつけてくれるので、急いで更新すべきかが一目でわかります。

Redash

データ可視化ツール

redash.io

DBに溜まったデータを分析したかったのでHerokuにRedashサーバーを立てました。
見たい時だけ動いてくれれば良いので、無料プランで運用しています。

Redashサーバーはこのリポジトリを参考にして30分ほどでできました。

github.com

インフラ関連

Heroku

www.heroku.com

開発を始めた当初はAWSのECSを使おうと思っていたのですが、最低限の構成でもお高くつきそうだったので、Herokuを使いました。特にLBがお高い・・・
デプロイパイプラインとかStaging環境とか自分で構築すると恐ろしく面倒くさいことが簡単にできてしまうので、ついついHerokuに甘えてしまいます。

Staging環境とRedashサーバーでは無料のFreeDyno、本番環境では今のところ7$のHobbyDynoを使用しています。

Heroku Postgres

elements.heroku.com

Staging環境とRedashサーバーで無料のHobbyDevプランを使っています。
HerokuPostgresのHobbyDevプランと9$のHobbyBasicプランはメモリキャッシュしてくれないので、本番環境では代わりにAWSのRDSを使いました。
Herokuのサーバーはアメリカにあるので、RDSのリージョンはバージニアを選択しています。インスタンスタイプはt3.microです。

出来るだけStaging環境と本番環境で構成を変えたくないのですが、Staging環境にお金はかけたくないので妥協しました。

CloudFlare

CDNサービス

www.cloudflare.com

DNSファイアウォールの機能を使用しています。
ファイアウォールの設定が、IP制限できたり日本以外のアクセスを制限できたりと結構柔軟に設定できます。

New Relic

アプリケーションのパフォーマンス監視サービス

elements.heroku.com

Herokuのアドオンで入れています。
無料プランだとログが1日までしか保存されないですが、普段ダッシュボードを見ることはほぼなく、異常が起きた時に知らせてくれればそれで良いので、閾値を超えたらSlackにアラートを飛ばしてくれるように設定しています。

Timber.io

ロギングサービス

elements.heroku.com

Herokuのアドオンで入れています。
似たようなロギングサービスはいくつかあったのですが、どれでもできることはそう変わらなそうだったので、あまり調べずに使いやすそうなものを選びました。
無料プランだとどのサービスも数日しかログを残してくれないので、念の為S3とかにログを永続化しようかなと思っています。
LogDNAというサービスを使用してS3にログを永続化できるようです。

Archiving Log Files

Uptime Robot

HTTP監視サービス

uptimerobot.com

apiサーバーのヘルスチェック用のURLに定期的にリクエストを投げて死活監視してくれています。
正常なレスポンスが帰ってこなかったら、slackに通知してくれます。

フロントエンド関連

React

管理画面をSPAにするためReactを使っています。
material-component や parcel を使って手を抜けるところは抜いています。
SEOを気にする必要がないのでSSRもしていません。

TypeScript

TypeScriptがあるのとないのとではコードの変更し易さが段違いなので、ここ1年くらいは必ず使っています。

Apollo Client

GraphQL用のクライアント

www.apollographql.com

RelayというGraphQLクライアントもありますが、そちらは試していません。
確かhowtographqlでApolloがオススメされていたので、そのままApolloを選んだ記憶があります。

こちらもGraphQLと同様にhowtographqlで入門して、足りない部分を公式ドキュメントで補うやり方で十分でした。

Netlify

静的サイトのホスティングサービス

www.netlify.com

静的サイト版Herokuみたいな感じで簡単便利です。
GitHubリポジトリに連携させると、ビルドとデプロイをシュッと行なってくれます。

アプリ関連

ReactNative

去年仕事でReactNativeを書いていて、使い心地も気に入っていたのでそのまま使用しました。
ただ、アプリのビルドの設定やリリース周りの作業だけは何度やっても面倒で嫌いです。
Expoを使えばその辺よしなにやってくれるのですが、代わりにネイティブモジュールを自由に使えないデメリットがあるので、Expoは使いませんでした。

AppCenter

azure.microsoft.com

アプリ関連のことを色々やってくれるサービスです。
ReactNativeもサポートしていて、公式のドキュメントも充実しています。
以下の機能を使いました。

  • アプリのビルド
  • アプリのストアへのリリース
  • CodePush

課金の形式は、合計ビルド時間が一月に240分を越えると有料になるといった形で、1ビルド5〜10分ほどかかるので無駄にビルドしているとあっという間に無料分がなくなります。
リリース前はどうしてもビルド回数が増えてしまい、ギリギリ240分を超えてしまったので、1月分だけ課金しました。4,480円もかかるのでお高いです。
お金払ってでもAppCenterを使った理由は、使い慣れているからというだけなので、無料で使えそうなBitriseを使うとか手元でビルドするとかでも良いと思います。

Firebase

firebase.google.com

色々と機能がありますが、AnalyticsとDynamicLinksだけを使いました。react-native-firebaseモジュールを使って設定しています。
Analyticsはそのまま使っても画面遷移の情報が取れないので、ReactNativeの画面遷移で使っているreact-navigationモジュールを使って遷移情報をAnalyticsに送信するようにしています。

Screen tracking for analytics · React Navigation

GoogleAnalyticsを使った例ですが、トラッキング部分のコードを差し替えればFirebaseAnalyticsでも使えます。

Bugsnag

www.bugsnag.com

前述の理由でSentryを使わずにBugsnagを使いました。
ReactNativeがサポートされているという理由で選んでいます。
SentryよりUIが好みなので、今後はこっちを使うかもしれません。使ってみた感じだと、できることは大差なさそうです。
CodePushで配信した.jsbundleのsource mapをアップロードする方法について、公式で解説されているのが地味に助かりました。

https://docs.bugsnag.com/platforms/react-native/react-native/showing-full-stacktraces/

CircleCI

circleci.com

定番のCIサービス。 テストを実行したり、codepushさせたりしています。

その他

Zoho Mail

www.zoho.com

独自ドメインのメールホスティングを無料で利用できるサービス。
この手のサービスはGoogle Appsをはじめ有料なイメージがあるので、無料なのは驚きました。最初は怪しいサービスかと思った

AppFollow

appfollow.io

アプリのレビューやランキングの表示、ASOをサポートしてくれるサービス。
無料プランでは2アプリまで登録できるので、とりあえず試しに使ってみています。
アプリのレビューがあったりキーワードの順位が変動したらslackに通知してくれるように設定しています。

Expo + React Native Debugger でデバッグする

React NativeでDOM要素をデバッグするとき、Web開発のようにChromeデベロッパーツール風の画面でデバッグする方法があったので書きます。 Expo XDEとAndroidiOSのシミュレータが必要になるので、インストールしておいて下さい。

react-native-debugger のインストール

github.com

$ brew update && brew cask install react-native-debugger

Expo XDE でプロジェクトを開く

起動したら、歯車アイコンをクリックして、Host -> LAN を選択。Development Modeにチェックが入っていることを確認。

f:id:kinakobo:20180121022038p:plain

DeviceアイコンからiOSAndroidのシミュレータを起動。

React Native Debugger を起動

Expoはport19001でデバッガを実行するので、React Native Debuggerにportを指定して起動する必要があります。

$ open "rndebugger://set-debugger-loc?host=localhost&port=19001"

デバッグする

シミュレータでExpoアプリの設定画面を開き「Debug Remote JS」を押すと、デバッグができるようになります。

f:id:kinakobo:20180121023010p:plain

f:id:kinakobo:20180121023613p:plain

参考:

www.gravitywell.co.uk

Cloud Function から Cloud SQL を使う方法

Cloud Functions for Firebase から Cloud SQLMySQL)を使いたかったのですが、現状公式でトリガーが用意されていませんでした :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
  })
}

参考:

stackoverflow.com

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パッケージは以下で探せます

TypeScript Types Search

設定ファイルを書く

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に入門してみました。 この記事の内容は、開発環境構築から実機で動作確認するところまでです。

環境

  • macOS Sierra
  • Node.js v9.2.1
  • Android / iOS の実機 expoアプリをインストールしておきます

今回は試してませんが実機の代わりにシミュレータでも動作します。

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コードが表示されます。 iOSAndroidの実機を用意し、expoアプリから上記のコードを読み込むと、実機で動作確認することができます。

f:id:kinakobo:20171225004317p:plain:w200

コードの変更もすぐに反映されます。お手軽すごい。

ついでにコンポーネントライブラリを入れて遊んでみます。 NativeBaseをインストール

Getting Started · 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
f:id:kinakobo:20171225003249p:plain:w200 f:id:kinakobo:20171225003258p:plain:w200

いい感じにフッターが iOS / Android のUIで表示されました。

【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