Dockerでローカル開発用のMySQL DBを用意する
ローカルで開発を行う際、プロジェクト毎に独立したDBを使いたいのでDockerで立てる
config/database.yml
default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root ? password host: 127.0.0.1 port: 13306
docker-compose.yml
version: "3" services: db: image: mysql:5.7 volumes: - db:/var/lib/mysql ports: - 13306:3306 environment: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" volumes: ? db
$ docker-compose up
で立てて127.0.0.1:13306
として使う- GUIクライアントからも接続可
オンライン同期可能なシーケンサーを作った話 後編
今回作ったもの
詳しい話は前編を参照
Firebase
個人開発の強い味方、Firebase。
HostingやAuthも使っているが、今回はデータの保存と同期のため、Cloud Firebaseを重点的に使った。
Cloud Firestore
Firebaseには従来からあるRealtime Databaseと
新しく追加されたCloud Firestoreという2つのデータベースがあり、今回は後者のCloud Firestoreを使った。
Realtime DatabaseとCloud Firestoreの違いは下記にまとまっている
データベースを選択: Cloud Firestore または Realtime Database | Firebase
Snapshot Listener
Cloud Firestoreではドキュメントへの変更を検知するために onSnapshot
でスナップショットリスナーのコールバックを登録することができる。
const doc = firebase.firestore().doc(`/sessions/${sessionId}`) doc.onSnapshot((changed: firebase.firestore.DocumentSnapshot) => { const data = changed.data() as Session actions.updateSession(data) // Reduxのstoreを更新 })
コールバックは以下の条件で発火する。
- リッスンしているドキュメントに対してリモートで変更が行われる
- ドキュメントをローカルで更新する
これが、Firestoreにおける同期やオフライン対応の基本となる。
データフローの変更
当初は、ローカルで行った操作の結果をリモートに送信する。つまり、Redux Stateにて行われた変更をCloud Firestoreに同期する想定だった。
しかし、Snapshot listenerがローカルへの変更に対し即時に発火することがわかったのでフローを変更し、
- Firestoreドキュメントを変更
- ドキュメントのonSnapShotコールバックが発火
- コールバックからReduxのactionをdispatch
- Redux stateの変更をmapStateToPropsでpresentational componentに伝播させる
- リモートのCloud Firestore DBへの同期を行う
とした。
DBへの書き込みは非同期で行われ、クライアントはドキュメントの更新が成功したものとして動作するため、楽観的UIの実現が可能になる。
追加実装なしでマルチクライアント同期を手に入れる
上記の変更により、マルチクライアント間の同期を追加実装なしで手に入れることができた。
ドキュメントを変更したクライアントから、変更内容がFirestore DBに同期されると、同じドキュメントをlistenしている別のクライアントでもonSnapShotコールバックが発火し(7)、Redux stateの変更(8)を経てpresentational componentに変更内容が伝わる。
ただし、書き込みしすぎに注意
Firestoreでは、ドキュメントへの書き込みに1秒に2回までという制限がありスライダー等、連続的変化を伴うUIによる値の変化を逐次ドキュメントへのupdateで反映するとすぐにrate limitに達してしまう。
そのため、UIへの反映とドキュメントの更新タイミングは、
- 変更を適度に間引く
- 一定期間中の変更を1回のupdateにまとめる
- スライダーを動かしている最中の変更はRedux Stateに直接反映し、スライダーが止まったときの値のみをドキュメントに反映
といった工夫が必要になる。
基本的な戦略としては、永続化される必要のない変更をRedux Stateに直接渡し、永続化が必要な変更のみをドキュメントに反映させることになる。
TypeScript
TypeScriptは create-react-app-typescript を使用して導入した。
User-Defined Type Guard
シンセのプリセットごとに表示させるコンポーネントを切り替えるため、実行時型チェック用にUser-Defined Type Guardを用意した。
export type SynthPreset = OscSynthPreset | FmSynthPreset | DrumsSynthPreset export interface BaseSynthPreset { type: SynthPresetType } export function isOscSynthPreset(v: any): v is OscSynthPreset { return v.type === 'osc' } export interface OscSynthPreset extends BaseSynthPreset { type: 'osc' oscillator: OscillatorType aeg: ADSR cutoff: number resonance: number }
所感
TSLint 普段使っている最小限の設定
プライベートで何か作ったりするときの TSLint config についてまとめておく。
tslint.json
{ "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "linterOptions": { "exclude": [ "config/**/*.js", "node_modules/**/*.ts", "coverage/lcov-report/*.js" ] }, "rulesDirectory": ["tslint-plugin-prettier"], "rules": { "no-default-export": true, "interface-name": false, "ordered-imports": true, "jsx-no-lambda": false, "object-literal-sort-keys": false, "prettier": true } }
rules
create-react-app のデフォルトからあまり触っていないが、以下の rules を弄っている。
- no-default-export: named export を強制するため true にしている
- interface-name: 大文字 I から始まる interface 名を禁止
- ordered-imports: インポート順をアルファベット順にして探しやすいように true
- jsx-no-lambda: Query Component pattern などで使うので false
- object-literal-sort-keys: object の key はアルファベット順よりも重要度で並べたり、分類ごとにまとめたりしたいので false
- prettier: tslint-plugin-prettier をインストール(
$ yarn add -D tslint-plugin-prettier
)し"rulesDirectory": ["tslint-plugin-prettier"]
を追加して true にするとプロジェクトの.prettierrc(.json)
が効くようになる
メンバーの多いプロジェクトでは、さらに必要に応じてrulesを追加したり無効化したりする
Visual Studio CodeでReact Appを開発する際の準備
Visual Studio Codeを使ってReact Appの開発を行う際に、
最低限追加しておきたいextensionsと設定をまとめておく
前提
プロジェクトでは以下を使用するものとする
- create-react-app
- TypeScript
- styled-components
extensions
TSLint
TSlintの結果をインライン表示する。
settingsに以下を追加すると(修正可能なものは)ファイル保存時に自動的に修正されるようになる
"tslint.autoFixOnSave": true,
Prettier - Code formatter
Prettierによるコードフォーマットを行う。
formatOnSave
: 保存時にフォーマットrequireConfig
: プロジェクトに.prettierrc(.json)
が存在する場合のみフォーマットを行う
"editor.formatOnSave": true, "prettier.requireConfig": true,
Jest
コードの変更によって影響を受けたテストを即時実行できるようにする
vscode-styled-components
styled-componentsのCSSをハイライトする
settings
ロケールの変更
TypeScriptの警告は日本語だと読みづらく、検索効率も悪いため英語にしておく
Visual Studio Code の表示言語を英語に戻した
CLI "code" のインストール
以下に記載された手順により code
で起動できるようになる
Running Visual Studio Code on macOS
$ cd project_dir $ code .
以上
思いついたら追記するが、他は好みで導入するのがよいと思っている
オンライン同期可能なシーケンサーを作った話 前編
少し前になるが、また音の出るおもちゃを作ったので記録を残す。
長くなりそうなので前編/後編に分ける。
作ったもの
- ブラウザで動くマルチトラックシーケンサ
- 作成中の楽曲をオンライン保存できる
- URLシェアにより、別のユーザと楽曲の同時編集ができる
- 編集内容は複数クライアント間でリアルタイム同期される
使ったもの
- React/Redux
- Firebase
- Cloud Firestore
- Authentication
- Hosting
- TypeScript
- Web Audio API
発表資料
モチベーションと課題
Reactで音の出るおもちゃを作るのが好きなので、Reactでシンセサイザーを作ったり、Reactでパターンシーケンサを作ったりしてきた。
今まで作ってきたものは音を出したり、パターンの変化を楽しんだりとどちらかといえば即興性の高いものだったが、「曲を作る」となると保存しておき続きを作り進められることや、他者と協力して作曲ができることが求められるだろうと考え、その検証として今回はオンライン保存、同期編集が可能なシーケンサを作るに至った。
- ブラウザ上で音を鳴らせる
- タイミング通りにシーケンス(作成した曲データ)を鳴らせる
という要件については今までの成果物で検証できていたので、今回は
- ブラウザ上でシーケンスをグラフィカルに編集できる
- 編集したものをオンライン保存できる
- 保存したものをシェアして複数のクライアントから同時編集できる
といった点にフォーカスを当てて実装を行った。
完成したものについてはデモを触ってみていただきたいが、実装を進める中で発見のあった箇所について書きたいと思う。
ピアノロールの実装
シーケンス編集の手段としては、一般的なDAW(音楽制作ソフトウェア)なら大抵備えているピアノロールを採用した。
ピアノロールはx軸で時間、y軸で音階を表現するUIだ。
探してみると、Reactでピアノロールを実装している例はいくつかあったものの、そのまま利用できそうなものがない、理想とするピアノロールと違うといった理由から、自前で実装することにした。
ちなみに、私が理想とするピアノロールは、かつて存在したOpcode社のEZ Visionというシーケンサのものなので、今回作ったものもそれに似た挙動になっている。
SVG as JSX
ピアノロールの実装はSVGをReact Componentに内包する形で実装した。
SVGのタグをJSXの中で使うことで、以下のようなメリットが得られた。
- 図形のグループをReact Componentに切り出しやすい
- グリッドのbackground patternも別コンポーネントに切り出した
- svg elementを普通に記述するとそれなりに多くのattributesを渡す必要あるが、Spread Attributesを使うとシンプルに書ける
例):
<rect {...rectProps, x } />
また、クライアント座標とは別の座標系を扱うことができるので、音楽のコンテキストにマッチした座標を使えるといったメリットもある。
- 例: 1小節あたりのclient widthが137pxといった半端な値でも、SVG内ではwidthを480とし、端数を出さずに16分割や12分割を行う(=16分音符や3連8分音符を表現する)ことができる
- client座標 -> SVG座標への変換には以下のようなメソッドを用意した
mouse2svgPoint(e: MouseEvent): SVGPoint { const pt = this.svgElement.createSVGPoint() const matrix = this.svgElement.getScreenCTM() if (!matrix) { pt.x = 0 pt.y = 0 return pt } pt.x = e.clientX pt.y = e.clientY return pt.matrixTransform(matrix.inverse()) }
FirebaseやTypeScriptについては後編で書く
Ant Designのカスタマイズ内容をStorybookに反映するための設定
Ant Design(antd)のテーマをカスタマイズした内容をStorybookに反映する場合、少しややこしい設定が必要になるのでメモ
前提
- プロジェクト本体はcreate-react-appで作成
- TypeScriptを使用
- antdのバージョンは3.7.3
設定方法
例: デフォルトのfont-familyを変更したい場合
antdTheme.js
module.exports = { '@font-family': '-apple-system, Sans-Serif' }
.storybook/webpack.config.js
const genDefaultConfig = require('@storybook/react/dist/server/config/defaults/webpack.config.js') const tsImportPluginFactory = require('ts-import-plugin') const modifyVars = require('../antdTheme') const path = require('path') module.exports = (baseConfig, env) => { const config = genDefaultConfig(baseConfig, env) config.module.rules.push({ test: /\.tsx?$/, exclude: /node_modules/, include: [/src/], loader: 'ts-loader', options: { getCustomTransformers: () => ({ before: [ tsImportPluginFactory({ libraryName: 'antd', libraryDirectory: 'lib', style: true, }), ], }), }, }) config.resolve.extensions.push('.ts', '.tsx') config.module.rules.push({ test: /\.less$/, use: [ { loader: 'style-loader', }, { loader: 'css-loader' }, { loader: 'less-loader', options: { modifyVars }, }, ], include: path.resolve(__dirname, '../src/', '../node_modules/'), }) return config }
Apollo ServerとNowで作るGraphQLモックサーバ #4 モニタリング編
前回の続き。
作成したGraphQLサーバをモニタリング可能な状態にする。
Apollo Engine
Apollo Engine | The GraphQL Gateway with essential features like caching and performance tracing.
- GraphQL caching
- Query execution tracing
- Error tracking
- Trends
などの機能を備える
- モニタリングしたいだけなのでcachingは今回使わない
- アラートは有償版でのみ有効
- 有償版は$99/mo~(割と高い)
導入
最初にGraphQLのスキーマをアップロードする
npm i -g apollo apollo schema:publish --endpoint=<your graphql endpoint here> --key="<your key>"
次に、App Engineを利用するためのセットアップを行う
API Reference: apollo-server | Apollo Server
Provided the ENGINE_API_KEY environment variable is set, the engine reporting agent will be started automatically.
apollo-serverは環境変数 ENGINE_API_KEY
がセットされていれば、
その値を使ってapollo-engineを有効化するようになっている。
環境変数は .env
に記述したものを
nowの --dotenv
オプションに渡してセットする。
ENGINE_API_KEY=service:foo-bar-baz:xxx...
$ now --dotenv=.env.production $ now alias
確認
Usage Metrics
デプロイされた環境にアクセスし、Playgroundでいくつかクエリを実行した後、 Apollo Engineの "Metrics" の項目を見ると実行したクエリのログが確認できる。
- リクエストの頻度
- パフォーマンスの概要
- エラー率
クエリ名ごとにさらに詳細な情報を確認することもできる。
Daily Report
Slack integrationを設定しておくとdaily reportでモニタリングのサマリを受け取ることもできる。
Schema Explorer
Schema Explorerではschemaに含まれるtypeの一覧と、それぞれの使用状況を確認することができる。
例えば、これを見るとPerson.addressは全く使われていないことがわかるので @deprecated
にする、といった判断に使える。
Schema History
schemaの変遷を確認できる。
例えば、Person.addressを @deprecated
に変更した場合、以下のように表示される。
まとめ
- 小一時間くらいあればここまで準備できる
- あとはSchema足していってResolverをひたすら書く
- とりあえずクライアントの動作を確認するためだけのモックサーバならmock書いていくだけでもOK
- GraphQLのサーバ運用するのはそれなりに労力かかる(と聞く)が、モニタリングあればただ闇雲にやるよりはずっと安心