Skip to content

Commit

Permalink
add articles
Browse files Browse the repository at this point in the history
  • Loading branch information
sadnessOjisan committed Apr 20, 2023
1 parent 61429f2 commit 9538ec6
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 0 deletions.
89 changes: 89 additions & 0 deletions src/contents/20230421-firestore-schema-with-zod/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
path: /firestore-schema-with-zod
created: "2023-04-21"
title: firestore を zod でバリデーションする
visual: "./visual.png"
tags: [zod, firebase, firestore]
userId: sadnessOjisan
isFavorite: false
isProtect: false
---

[encraft #2](https://knowledgework.connpass.com/event/279962/) までの間、スキーマスキーマした話をたくさん書きたい。

OGP は昨日食べた火鍋だ。Fire 感があるのでこれを使おうと思った。([Firebase の記事を書く時は炎の画像を使っていた](/tags/firebase/)のに、炎系のフリー素材をたくさん使いすぎて似た画像ばかりになりストックがなくなったことは秘密)

## firestore は withConverter で validation できる

なんか似たようなブログを書いた気がしていたのだが、どうやら [firestore の入出力に型をつける](https://blog.ojisan.io/typed-firestore/)`withConverter` を紹介していた。なので詳しくはそれを見てほしい。

```tsx
export const sitemapConverter: FirestoreDataConverter<SitemapSchema> = {
toFirestore(sitemapDto: SitemapSchema): DocumentData {
const record: SitemapSchema = {
origin: sitemapDto.origin,
url: sitemapDto.url,
created_at: sitemapDto.created_at,
updated_at: sitemapDto.updated_at,
};
const parsed = sitemapSchema.parse(record);
return parsed;
},
fromFirestore(snapshot: QueryDocumentSnapshot): SitemapSchema {
const data = snapshot.data();
const parsed = sitemapSchema.parse(data);
return {
url: parsed.url,
origin: parsed.origin,
updated_at: parsed.updated_at,
created_at: parsed.created_at,
};
},
};
```

さて、先の例ではしれっと sitemapSchema という zod schema が登場しているのだが、この fromFirestore で validation すれば firestore からの戻り値を検証して型を付けられる。

## Firebase 独特のものをバリデーションしたい

で、number やら string を firestore に入れている限りでは普通に zod の `z.string()` `z.number()` を使うだけでスキーマを定義できるのだが、firestore の組み込み型には Timestamp や Reference 型といったものが登場する。そしてこれは利用することがほぼ確定しているようなものだ。頑張ってこれのスキーマを定義しよう。

### Timestamp

Firestore における日付表現だ。この型でデータを保存しておくと日付順のソートができるようになるので重宝する。

Timestamp は Date 型とはまた違うので `z.date()` ができない。そこで `z.instanceof` を使おう。

```tsx
z.instanceof(Timestamp);
```

FYI: [https://github.com/colinhacks/zod#instanceof](https://github.com/colinhacks/zod#instanceof)

### DocumentReference

DocumentReference は別 collection の Document に対する参照だ。ドキュメント ID だけ文字列で保存していると、そのドキュメントにたどり着くまでのパスを作らないといけないが、参照を保存しておけば一発でそのドキュメントまで辿れる。それを実現する特別な型が DocumentReference だ。テーブルの正規化などでとくにお世話になるだろう。

これも instanceof なんていう便利なものがあれば DocumentReference に対しても型を付けられそうだ。が、実は使えないのである!

FYI: [https://stackoverflow.com/questions/74346759/use-zod-to-validate-schema-with-firestore-documentreferences-in-it-with-default](https://stackoverflow.com/questions/74346759/use-zod-to-validate-schema-with-firestore-documentreferences-in-it-with-default)

実は private constructor を持っている物に対してはこの手法は使えないのである。どうやら `FieldValue` に対しても同様の問題があるようだ。(え、constructor が private ってなんやねんと思った方は Static Factory Method を調べたり [Effective Java](https://www.maruzen-publishing.co.jp/item/?book_no=303408) の第 1 章を読んでみよう)

FYI: [https://github.com/colinhacks/zod/issues/384](https://github.com/colinhacks/zod/issues/384)

こうなると無理やりに突破するしかない。そこで refine の出番である。僕も最近まで知らなかったのだが篩型のサポートがあった。といってもここでは篩篩したようなことはせず、any 型で一旦型検査をパスさせて、JS 本来の instanceof で検証して通ればユーザー定義ガードで型を付けさせる。refine がそういう API なのでそうする。

```tsx
page_ref: z.any().refine(
(x: object): x is DocumentReference => x instanceof DocumentReference
);
```

便利〜〜〜

zod は最悪の場合に TS の世界で型を付けられるのは便利ですね。Ajv にはできない芸当だ・・・

## 最後に

ここまで書いておいてアレですが、同日に 「[zod 使わない!](/i-use-ajv-instead-of-zod)」というブログ書いているので読んでみてください。
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions src/contents/20230421-i-use-ajv-instead-of-zod/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
path: /i-use-ajv-instead-of-zod
created: "2023-04-21"
title: zod ではなく ajv を使っている話
visual: "./visual.png"
tags: [zod, ajv]
userId: sadnessOjisan
isFavorite: false
isProtect: false
---

[encraft #2](https://knowledgework.connpass.com/event/279962/) までの間、スキーマスキーマした話をたくさん書きたい。好き好きスキーマと言いたいところだが、zod に対しては人気に対して逆張り意見的なのを持っているのでそれを書いていきたい。

OGP は Ajv ユーザーと焼肉をしたときの画像だ。網もスキーマが大事ってことですね。

## 独自性の高いスキーマを使うのは危険だと思っている

zod は便利だ。とても流行っている。その結果 [yup](https://github.com/jquense/yup)[joi](https://github.com/hapijs/joi) で作られたものが負債扱いされているような気までする。だが思い出してほしいのだが、yup だって出てきた当初はとても便利なものとして人気があった気がする。特に [Formik](https://formik.org/) と組み合わせるのは一種のパターンになっていたような気もする。しかし最近はそれらが zod に取って代わられてしまったと思っている。エコシステムの選択や対応を見ていると zod 一強だ。

(ちなみに npm trends でみると joi 一強です。 Server FW から HTTP Client まで hapi family は至る所に生きている。)

だが、zod より便利なものが今後出たらどうなるのだろうか。zod も負債扱いされる未来が来るような気がしている。これまでの傾向からしてユーザーはスキーマライブラリを気軽に乗り換えていく。だが僕はスキーマには本当に長生きしてほしい(後でマジで首絞まるので・・・)。なので長生きするような技術選定をしたい。

## 長生きするスキーマ

zod に限らず正直なところ何を使っても負債扱いされると思っているので、移植性を重きに置いた技術選定をしようと思った。そこで JS/TS に依存しない IDL として

- JSON Schema
- GraphQL
- protobuf

などが良いと思っている。このうち自分は表現力の都合で JSON Schema を使うことが多く、その validator として Ajv を使っている。

**長生きしてほしい、だから僕は JSON Schema を使い続ける。**

## zod が嬉しいとき

一方で zod が得意なことや zod にしかできないこともある。例えば zod は クラスなど、plain でないもののバリデーションがしやすい。また TS first なのを生かして [Brand 型の対応](https://github.com/colinhacks/zod#brand)などもできる。

もしかしたら今は Ajv もできるかもしれない(実際できるけど型推論が大変なことになる)が [zod は再帰型のバリデーションもできる](https://github.com/colinhacks/zod#recursive-types)。そして[Firestore に対する Validation は zod 一択](https://blog.ojisan.io/firestore-schema-with-zod)だと思っている。

zod も最高!なので結局は何を使う時はその時にあったものを使いましょう(完)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 9538ec6

Please sign in to comment.