らこらこブログ

唐揚げとアニメとプログラミングが大好きです

merge-graphql-schemasを使ってGraphQLのスキーマファイルを結合する

lacolaco.hatenablog.com

^の続きです。 前回はglobとfsを使ってschema/**/*.gqlファイルを結合し、graphdocを使ってドキュメンテーションページを生成する記事を投稿しました。

まったく同じ目的のために merge-graphql-schemas というnpmパッケージがあったので、これを使ってドキュメンテーションのビルドスクリプトを改良しました。

パッケージのインストール

前回の状態から、globをアンインストールして、代わりにmerge-graphql-schemasをインストールします。

$ yarn remove glob
$ yarn add --dev merge-graphql-schemas

schema.jsの変更

前回はschema.jsからエクスポートした文字列配列をスキーマとしてgraphdocに読み込ませましたが、 今回はschema.jsを実行することで静的なschema.gqlファイルを書き出し、これをgraphdocに渡します。

結合されたスキーマが静的ファイルとして存在することで、他のGraphQL周辺ツール(コード生成など)への連携が容易になります。

schema.jsは次のように書きました。fileLoadermergeTypesmerge-graphql-schemasパッケージから提供される関数です。

最終的に結合された文字列がschema.gqlファイルに書き出されます。

やっていることは前回とだいたい同じですが、この関数でマージされたスキーマは内部でスキーマ単位にソートされていて、ファイル名ではなくtypeinterfaceの名前でソートされているようです。

const path = require('path');
const fs = require('fs');
const { fileLoader, mergeTypes } = require('merge-graphql-schemas');

const typesArray = fileLoader('schema/**/*.{gql,graphql}', {
  recursive: true
});

const mergedSchema = mergeTypes(typesArray);

fs.writeFileSync('schema.gql', mergedSchema, {encoding: 'utf8'});

こんな感じで、schemaから順番に何らかのルールでソートされています。

schema {
  query: Query
  mutation: Mutation
}

type Query {
  # Return the hero by episode.
  hero(episode: Episode): Character
  # Return the Human by ID.
  human(id: ID!): Human
  # Return the Droid by ID.
  droid(id: ID!): Droid
}

type Mutation {
  # Save the favorite episode.
  favorite(episode: Episode!): Episode
}

# A character in the Star Wars Trilogy
interface Character {
  id: ID!
  name: String
  friends: [Character]
  appearsIn: [Episode]
  secretBackstory: String
}
...

graphdocでスキーマを読み込む

package.jsonbuildスクリプトを次のように変更します。事前にschema.jsを実行し、吐き出されたファイルをgraphdocに渡します。

  "scripts": {
    "build": "node schema.js && graphdoc -s schema.gql -o docs -f"
  },

これで元どおりドキュメンテーションが生成されます。


schema.gqlを静的に吐き出してGit管理に含めておくと、このリポジトリを外部から参照することで簡単にGraphQLのスキーマが得られるので、 クライアントサイドとサーバーサイドそれぞれから参照して利用するのに便利です。.gqlファイルなので実装の言語も問いません。

この方法で作成したスキーマからTypeScriptの型定義を生成してクライアントサイドで利用するのを仕事で試しているので、いい感じに知見が溜まったらまた記事を書きます。

だんだんAPI仕様中心開発っぽくなってきたぞ!さらばバックエンド実装にブロックされるフロントエンド開発!

GraphQL Schemaをファイル分割してドキュメンテーションする

GraphQLのSchemaから静的なドキュメンテーションページを生成するツールとして、graphdocというものがあります。

github.com

GraphQL Schemaを渡すと、こんな感じのHTMLを生成してくれます。 これはGraphQL公式のサンプルにもあるStar Wars APIの例。

https://2fd.github.io/graphdoc/star-wars/

f:id:lacolaco:20180213222120p:plain

このドキュメンテーションを 複数の*.gqlファイルからなるSchemaから生成するために少し小細工が必要だったので紹介します。

GraphQL Schema Language

今回はGraphQL Schema Languageを.gqlという拡張子のファイル(便宜的に以降 gqlファイル と呼びます)で管理します。 TypeScriptやJavaScriptファイル中でテンプレートリテラルとして管理するよりも余計なコードがなくて見やすく、エディタによっては入力支援も得られます。

gqlファイルの欠点は、GraphQL Schema Languageに外部ファイルの参照の仕様がないことです。 JSON Schemaでいう$refに相当するものが定義されていないので、エントリポイントとなるSchemaから分割された型定義などを参照できません。

graphdocでgqlファイルをドキュメンテーションする

graphdocはいくつかの方法でSchemaを読み込めます。 代表的なものはエンドポイントURLを渡してHTTP経由でドキュメンテーションを生成する機能ですが、 gqlファイルを渡すこともできます。 しかしgqlファイルを渡す場合は先述の理由により単一ファイルにすべてのSchemaが記述されている必要があるので、 大きなAPI仕様になると管理が大変です。

複数のgqlファイルからなるSchemaをgraphdocで読み込むためには、JavaScript読み込み機能を使います。 graphdocのschemaFileとして.jsファイルを渡すと、CommonJSとして読み込まれ、exports.defaultの値をSchemaとして使用します。 そしてこのとき、エクスポートするのはSchemaの配列なので、複数のgqlファイルを読み込んで文字列として渡してあげれば目的を達成できます。

手順

npmパッケージを揃える

必要なのは@2fd/graphdocglobの2つです

> yarn add --dev @2fd/graphdoc glob

schema.jsファイルを記述する

複数のgqlファイルを読み込んで文字列として渡してあげるためのJavaScriptファイルを用意します。 globfsを使ってファイルを読み込むだけですが、同期的にエクスポートする必要があるのに注意します。

const glob = require('glob');
const fs = require('fs');
const path = require('path');

function loadSchemas() {
  const files = glob.sync('schema/**/*.{gql,graphql}');

  return files.map(file =>
    fs.readFileSync(path.resolve(file), { encoding: 'utf8' })
  );
}

const schemas = loadSchemas();
exports.default = schemas;

npm scriptを追加する

graphdocコマンドを実行するnpmスクリプトを追加します

  "scripts": {
    "build": "graphdoc -s schema.js -o docs -f"
  },

schemaディレクトリにgqlファイルを配置する

あとはひたすらSchemaを書いていくだけです。

実際にStaw Wars APIのSchemaを分割してgraphdocにしたリポジトリを用意したので、詳細はそちらを参照してください。

github.com

docsディレクトリに出力しているので、GitHub Pagesで簡単にAPIドキュメントをホストできます。

https://lacolaco.github.io/graphdoc-example/

課題

  • gqlファイルに外部参照の仕組みがないので、分割すると参照が途切れる(リネームとかできない)

サーバーサイドの実装に依存しないAPI仕様Centricな開発をしたいのだけど、 Open API Specificationみたいな感じでGraphQL Schemaを使う文化はあまり育っていないようで、苦労しそうな感じがあります。

結局のところひとつのgqlファイルにドカドカ型定義書いていくほうが楽なのかもしれないけど、しばらく分割管理を試してみてから考えましょう。

Angular用ライブラリをnrwl/nx + ng-packagrのmonorepoで管理してみた

表題のとおりです。結論からいうといろいろ問題があり、このmonorepoは失敗ということでそのうち解散するつもり。

Nx workspaceを使ったmonorepo

Angular用ライブラリのmonorepoには、Nrwlが提供するNx Workspaceを使うのが早いし便利だ。

nrwl.io

Nxとは何かというのはリンク先に任せるとして、次のコマンドでNxのインストールとワークスペースの作成をおこなう。 今回作ったmonorepoは ngx というリポジトリ名なので、ワークスペース名も ngx とした。

github.com

# install CLIs
> yarn global add @nrwl/schematics
> yarn global add @angular/cli

# create a workspace
> create-nx-workspace ngx

create-nx-workspaceコマンドを実行すると、デフォルトのアプリケーションがひとつ含まれたワークスペースが作成される。 プロジェクト全体はng newを拡張したもので、.angular-cli.jsonファイルがあるのでng generateコマンドによるコード生成なども可能だ。

ライブラリ開発用のワークスペースとして必要なのは、サンプル用のアプリケーションがひとつと複数のライブラリを管理することだが、これ自体はNxが最初からサポートするユースケースなので簡単にできる。 ドキュメントの Create an Angular module lib を読めば、 nx generate libコマンドでライブラリが1モジュール作れるのがわかる。言われたとおりにやるだけで、ワークスペース内のアプリケーションからアクセス可能なライブラリができあがる。

ng-packagrを使ったnpmパッケージ化

問題はここから。Nxで作られるライブラリはあくまでもワークスペース内のアプリケーションから呼び出すためのもので、npmパッケージとして切り出して公開するためにいろいろと作業が必要だ。

Angular用ライブラリのパッケージングには、ng-packagrというツールを使う。Angular本体と同じようにUMDやESM、FESMなど、Angular Package Formatに則った複数の形式のバンドルを自動で作成してくれる優れもの。

github.com

ng-packagrはnpmモジュールとして切り出したいディレクトリのpackage.jsonを指定して使う。Nxのワークスペースであっても、libsディレクトリ内のライブラリごとのディレクトリに、それぞれのpackage.jsonを配置してあげればよい。ngxでは次のようなコマンドでパッケージをそれぞれビルドしている。

{
  "name": "ngx",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    ...
    "build:libs:store": "ng-packagr -p libs/store/package.json",
    "build:libs:ngx-store": "ng-packagr -p libs/ngx-store/package.json",
    "build:libs:ngx-grid-layout": "ng-packagr -p libs/ngx-grid-layout/package.json",
    ...
  },
  ...
}

ライブラリのディレクトリはこのようになっている。package.jsonがあるディレクトリのファイルはコピーされるので、README.mdもここに置く必要がある。

f:id:lacolaco:20180202212045p:plain

実はng-packagrの作者がNxとのインテグレーションのサンプルを作ってくれてるので、そっちを参考にするとよい。

github.com

publishとバージョニング

ng-packagrを使ってパッケージができあがるところまではうまくいったが、ここから先がいい感じにならなかった。

ビルドされたパッケージをnpmにpublishするには、それぞれのパッケージがバージョンを持たなければならない。 ご存知のとおりnpmパッケージのバージョンは重複が許されないので、publishするにはバージョンを変えないといけないのだけど、ここで既存のツールとうまく噛み合わなくなる。

普通の単パッケージのリポジトリだと、バージョンを上げるのはnpm versionyarn versionのコマンドを使うとよくて、バージョニングと同時にgitのコミットとタグも打ってくれる。 しかし今回のmonorepoの場合、バージョンを上げるにはpackage.jsonを手書きで編集するしかない。 yarn publishコマンドはpublish時にバージョンを上書きする機能があるけれど、これで編集しても書き換わるのはビルド後のdistの中のpackage.jsonなので、元のファイルには影響しない。 package.jsonが二重に存在する状態になるとこの辺が面倒。いいアイデアが浮かばない。

CHANGELOG

バージョニングはまあなんとかなるかと思ったけど、CHANGELOGはどうにもならなかった。 conventional-commitlogに従ってstandard-changelogCHANGELOG.mdを生成したいのだけど、バージョンごとのgitのタグがないので、全バージョンが過去分すべてのcommitをCHANGELOGに出してくる。これはどうしようもない。

今は重複のコミットを手で消しているけどすぐに破綻するので、これがmonorepo断念の一番の理由。CHANGELOG自動生成と相性が悪い。 Lernaとかはmonorepo側とインテグレーションしていい感じにできるっぽいけど、Nxには期待できなさそう。

ライブラリ間の依存

あとmonorepoのビルドプロセスを作っててつらいのが、ライブラリ間に依存関係ができてしまったとき。具体的にはngx-storestoreに依存しているんだけど、Nxのワークスペース内モジュール解決はNxでビルドするときにしか使えなくて、ng-packagrでビルドしようとするとnode_modules内に自分のライブラリがなくてエラーになる。そのため、ライブラリのビルド前にnpm linkでsymlinkを貼っておく必要がある。 まあなんかこの辺見てもらえればわかると思う。依存関係があるからライブラリのビルドが並列にできないのもつらい。

f:id:lacolaco:20180202214345p:plain

まとめ

  • Nxは便利。複数アプリから呼び出される共通自前モジュールがあるときにはめっちゃいいと思う
  • ng-packagrは便利。これ無しでまともなAngular向けパッケージビルドするのつらい。
  • CHANGELOG.mdの自動生成を考えるとmonorepoはgitタグが付けられないのでつらい。

ngxのリポジトリ見ればいろいろわかると思うけど、聞きたいことがあったらng-japanのslackとかtwitterとかで聞いてください。

もし英語がGitHubで開発されていたら

電車とか暇な時はよくこういう意味のない空想をする。空想なので何か伝えたい事があるわけでもなく、破綻していても怒らないでほしい。

 

もし新しい自然言語を定義するときにGitやGitHubがあったらどうなるか、あるいは英語がGitHubで開発されていたらどういうプロセスがあっただろうか。

 

自分がいまから自然言語を新しく作るとしたら、まずは基本的な品詞の定義から始めると思う。ここはたぶんひとりのauthorが決めてしまう部分。

 

品詞ができたら、次に文型の定義に進む。文型はSV, SVOだけが最初は定義されるだろう。しかしSVだけではVの責務が大きくなるため、VをパラメタライズするSVCが提案され、おそらく認可される。("大人になる"という動詞を具体な単独のVではなく"become an adult"という形にVを抽象化してCに具体を逃がす)

 

問題はSVOOだ。誰かがSVOOのProposalを提出してくるが、ここでたぶん保守派と革新派でさんざんIssueが炎上する。なぜならSVOOはSVOに前置詞+名詞の副詞句を付与すれば互換できるからだ。(give me a penはgive a pen to meで互換できる)同じ動詞でSVOとSVOOどちらにも対応するために再びVの責務が大きくなる。しかし結局動詞が複数の目的語を取ることができるようにパッチが入る。

 

いちどSVOOの前例を認めているので、SVOCのProposalはあっさりとコミュニティに受け入れられ、またバージョンアップする。Vによって取りうる文型が変わることになりせっかく品詞で抽象化したはずが文型と単語が密結合する。言語全体の抽象レベルがここで下がる。

 

品詞と文型が決まったらここからはとにかく単語を追加していく。PRが飛び交い、並行してどんどん単語が生まれていく。ただしレビュアーが統一されていないため、同じ表記で違う意味の単語が追加されていく。仕方なくコンフリクトを解決して同じ単語が名詞、動詞、様々な用法に対応するようになる。そして品詞による抽象の設計は破綻し、単語と密結合した言語になる。

 

飽きたのでここまでで終わる。英語をフォークしたらまずは品詞と文型による抽象の設計からやり直したい。英語ってある程度までは暗記で、それからもう少し潜ると抽象が見えてきて少しシステマティックになるんだけど、根っこの部分でやはり抽象が破綻していて難しい。

 

 

 

 

「Angularデベロッパーズガイド」の監修をしました!

僕が監修した 「Angularデベロッパーズガイド」が12/15に発売となります!

f:id:lacolaco:20171214124215p:plain

Angularデベロッパーズガイド  高速にかつ堅牢に動作するフロントエンドフレームワーク

Angularデベロッパーズガイド 高速にかつ堅牢に動作するフロントエンドフレームワーク

上のリンクはアフィリエイトではないので何万回踏まれても僕に利益はないですが、買ってくれたら印税が回ってくるので気になったら買ってください!

監修

まだ発売前ですがこの本の監修をする上で気をつけたことを軽くまとめておこうと思います。

今回監修としてやったことは、個々の原稿のレビューと、全体の内容の設計のレビューです。 監修は初めてだったので最初は何をするんだろうという感じでしたが、ただただひたすらレビューをしました。 本のレビューするときは毎回思うのですが1000行を超えるPull Requestのレビューはつらいですね。「ここまで読んだ」マークが欲しいです。「ここからここまではLGTM」とか。

1. できるだけ寿命を延ばす

一度買われた紙の本はアップデートできないので、できるだけ寿命の長い本になるように心がけました。 Angular自体はv4が1年間のLTSにもなっていますし、既存のAPIが削除・破壊されるまでには原則として1年間の猶予があります。(1回目のメジャーアップデートでdeprecatedになり、2回目のメジャーアップデートで破壊される) なので、この本は最低限1年以上は寿命があるようにしようと思い、現在最新はv5ですが、v8くらいまでは普通に通用してほしいなと思います。 そのためには単なる公式ドキュメントの日本語訳とならないようにする必要があります。ドキュメントは変わりますからね。

Angularデベロッパーガイドでは個別のAPIの詳細な仕様を解説するのではなく、それぞれのAPIをどのように使うと何ができるのか、というユースケース主体の本になるようにレビューを心がけました。リファレンスは公式ドキュメントを読めばいいのです。

また、APIが存在しているものの今後のアップデートで破壊されることが目に見えているもの、experimentalなものはあまり詳細には解説しないようにしました。 そういったアドバンスドな内容を自力で学習できる下地を作るための基礎教養として、この本が使われるとよいと考えています。

2. "リアル"なサンプルコードに

たまに見かけるのですが、APIの説明を詳細にやろうとするあまり、「そんなコードいつ書くんだよ」というような謎ユースケースのサンプルコードが書かれることがあります。 そういったサンプルコードは知る必要のないことを知るコストもあり、またそういったコードが正しいのかもしれないという初心者への間違った植え付けにもつながります。(「本にこう書いてあったので…」事案)

そういったことを避けるため、サンプルコードはなるべく "リアル" なユースケースに沿って書かれるようにレビューしました。読みながら自分のアプリケーション中でどう使うか、という想像ができるようなサンプルコードを紹介できるように心がけたつもりです。

まとめ

ぜひ来年の新人研修用にAngular利用企業のみなさんよろしくお願いします。買って!

今年の資産運用KPT

どうも、lacoです。

投資・資産運用アドベントカレンダー 10日目です。

adventar.org

mzsmさんが面白そうなことをやっていたので混ぜてもらいました。

資産運用はじめた

なんやかんやで順調に収入が増えてきてそこそこ貯金が溜まり始めたんですが、円で預金しててももったいないので、今年の晩夏ごろから資産運用始めました。 いい機会なのでKPTで振り返りします。

KPT

Keep

  • 毎月の投資に回す額と日にちを決める
  • FOLIOよさそう

投資に使う金額と、投資のことを考える日を毎月きまった日に固定しました。 その日に前月の運用の評価と、次にどうするかを考えて、決まった額を投資に使う、というのをルーチン化したので、月の途中での高騰、下落に一喜一憂しなくなりました。 今は月末を投資デーにして、毎月10万円投資に使ってます。投資前は毎月20万ちょいくらい貯金に回ってたのでだいたい貯蓄の半分投資するようにしてます。ゆとりのある投資。

今年手を出して自分にマッチしてるのはFOLIOです。だいたい基本単位が1口10万円なのでルーチンともフィットしてます。 リアルタイムの資産額はだいたい20分おきに更新されるのだけどそれはグラフにはなってないのもいい。 分足のドル円チャートとか見始めちゃうと止まらなくなるので、グラフが日次でしか見れないことで日々の 高騰、下落に一喜一憂 がなくなっている気がします。

運用成果としても今のところ悪くないので、来年もしばらくはFOLIOやっていこうと思います。 来年はマネーフォワードに対応してくれたらいいなあ。

Problem

投資をはじめようとして一番最初にやったのはドル定期でした。ソニー銀行を使っているのでソニー銀行のドル定期をやってみたんですが、定期のはずがドル円のチャートが気になってしまい1ヶ月位で解約してしまいました。性に合わなかった。

次に同じくソニー銀行でつみたて式の投資信託をやってみたんですが、ファンド選びで散々悩んだあげくあんまりいい動きをしなかったので2ヶ月くらいで解約してしまいました。性に合わなかった。

Try

  • TATETU FUNDING
  • THEO

来年やってみたいとおもってるのは不動産のクラウドファンディングができるTATERU FUNDINGです。

www.tateru-funding.jp

ユーザー登録はもう済ませていて1回応募したけど落ちたので、来年はぜひやりたいです。

アドベントカレンダーの2日目で紹介されてたTHEOも知らなかったので検討してみます。考えなくていい投資を続けていきたいです。


ざっくりKPTおわったので今日はこれでおしまいです。11日目の人いなさそうですが、次の日をお楽しみに。

今月も結婚できなかった

mstdn.jp

P.S. 500円でスヌーピーの鍋つかみを買ったらデカイ本がついてきた。アドい。