Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Perl でも GraphQL を使いたい
Search
nakaokat
September 08, 2022
Programming
0
2.3k
Perl でも GraphQL を使いたい
日付:2022/09/07
イベント名:Hatena Engineer Seminar #21
動画:
https://youtu.be/N6iUlz4buTc?t=963
nakaokat
September 08, 2022
Tweet
Share
Other Decks in Programming
See All in Programming
関数実行の裏側では何が起きているのか?
minop1205
1
670
Full-Cycle Reactivity in Angular: SignalStore mit Signal Forms und Resources
manfredsteyer
PRO
0
200
「コードは上から下へ読むのが一番」と思った時に、思い出してほしい話
panda728
PRO
37
25k
非同期処理の迷宮を抜ける: 初学者がつまづく構造的な原因
pd1xx
1
690
SwiftUIで本格音ゲー実装してみた
hypebeans
0
110
LLMで複雑な検索条件アセットから脱却する!! 生成的検索インタフェースの設計論
po3rin
2
640
LLM Çağında Backend Olmak: 10 Milyon Prompt'u Milisaniyede Sorgulamak
selcukusta
0
110
手が足りない!兼業データエンジニアに必要だったアーキテクチャと立ち回り
zinkosuke
0
580
無秩序からの脱却 / Emergence from chaos
nrslib
2
13k
AIコードレビューがチームの"文脈"を 読めるようになるまで
marutaku
0
350
全員アーキテクトで挑む、 巨大で高密度なドメインの紐解き方
agatan
8
20k
connect-python: convenient protobuf RPC for Python
anuraaga
0
380
Featured
See All Featured
Practical Orchestrator
shlominoach
190
11k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
A Tale of Four Properties
chriscoyier
162
23k
Why You Should Never Use an ORM
jnunemaker
PRO
61
9.6k
Java REST API Framework Comparison - PWX 2021
mraible
34
9k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4.1k
The Power of CSS Pseudo Elements
geoffreycrofte
80
6.1k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.3k
BBQ
matthewcrist
89
9.9k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Documentation Writing (for coders)
carmenintech
76
5.2k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
Transcript
Perl でも GraphQL を使いたい id:nakaoka3 / @nakaokat 2022/9/7 はてなエンジニアセミナー #21
1
自己紹介 • はてなのマンガメディア開発チーム所属のアプリケーションエンジニ ア • コミック DAYS アプリなどスマートフォンアプリの開発を経て、現在 は GigaViewer
のサーバーサイドの開発をしている 2
アジェンダ • GigaViewer でも GraphQL を使いたい • Perl でも GraphQL
を使いたい • GraphQL でもキャッシュを使いたい 3
GigaViewer でも GraphQL を使いたい 4
GigaViewer とは > 当社では「はてなブログ」や「はてなブックマーク」などの個人向け Web サービスの提供で培った技 術力を活かし、2017 年に Web サイトとしての魅力を引き出せるマンガビューワである「GigaViewer
(現在の「GigaViewer for Web」)」を開発しました。ユーザーが快適にマンガ作品を楽しむための各 種機能に加え、サービス提供者の運用コストを削減する管理機能、広告によるマネタイズ支援など、機 能とサービスの拡充に継続的に取り組んでいます。2021 年 11 月より、Web マンガサービス向けビュー ワに加えて、マンガアプリに対応したビューワである「GigaViewer for Apps」の提供を開始しました。 https://hatena.co.jp/press/release/entry/2022/08/04/153000 5
GigaViewer の特徴 • ビューワだけではなく、web サイトのトップページや連載ページなども提供 している • 複数の web サイトを提供している(マルチテナント)
• web だけはなく、スマートフォンアプリ用の API もある 6
「GigaViewer for Web」が採用された Web マンガサイト > 「少年ジャンプ+」「となりのヤングジャンプ」(株式会社集英社)/「マガジンポケット」「コ ミック DAYS」「&Sofa」「モーニング・ツー」(株式会社講談社)/「くらげバンチ」(株式会社新潮 社)/「コミプレ」(株式会社ヒーローズ)/「コミックボーダー」(株式会社リイド社)/「コミッ
クガルド」(株式会社オーバーラップ)/「ゼノン編集部」(株式会社コアミックス)/ 「MAGCOMI」(株式会社マッグガーデン)/「web アクション」(株式会社双葉社)/「コミックト レイル」(株式会社芳文社)/「コミックブシロード WEB」(株式会社ブシロードクリエイティブ)/ 「FEEL web」(株式会社祥伝社)/「サンデーうぇぶり」「コロコロオンライン」(株式会社小学館) /「COMIC OGYAAA!!」(株式会社ホーム社) https://hatena.co.jp/press/release/entry/2022/08/04/153000 ※2022 年 8 月時点 7
なぜ GraphQL を使いたいのか • 実装の重複を減らして効率的に開発するため アプリ用 API で GraphQL を使いたい
• web でフロントエンドをモダンな環境に改修できるようにするため、フロントエンドとバックエン ドのインターフェイスとして GraphQL を使いたい 8
GigaViewer のサーバーサイドのアプリケーションは Perl で書かれている。 Perl で GraphQL の実装ができるのか? 9
できます 社内で先行事例あり(カクヨム:株式会社KADOKAWAとの共同開発) 10
Perl でも GraphQL を使いたい 11
どうやって Perl で GraphQL を使うのか • graphql-perl というライブラリを使う ◦ https://github.com/graphql-perl/graphql-perl
◦ GraphQL::Execution や GraphQL::Schema などのモジュールを提供する • graphql-perl は graphql-js を Perl に移植したもの。graphql-js は Facebook によって作 られた GraphQL のリファレンス実装 12
13 sub execute { # リクエストで渡された GraphQLのクエリ、変数、オペレーションネーム my ($class, $query,
$variables, $operation_name) = @_; # スキーマを読み込みパースする my $schema_file = read_file('schema.graphql', binmode => ':utf8'); my $schema = GraphQL::Schema->from_doc($schema_file); # クエリをパースする my $parsed_query = GraphQL::Language::Parser::parse($query); https://gihyo.jp/dev/serial/01/perl-hackers-hub/007301 サンプルコードより スキーマ、クエリのパースはラ イブラリがやってくれる
14 # Mutation ではキャッシュしない my $cachable = !!0; for my
$query ($parsed_query->@*) { if (($_->{operationType} // '') eq 'mutation' && ($_->{name} // '') eq $operation_name) { $cachable = !!1; } } # Contextは各 Resolver で参照できる共通のストア my $context = App::GraphQL::Context->new();
my $result = GraphQL::Execution::execute( $schema, $parsed_query, # root_value {}, $context,
$variables, $operation_name, # field resolver \&_resolver, # promise code +{ resolve => \&Promise::XS::resolved, reject => \&Promise::XS::rejected, all => \&_promise_all, }, ); # リクエストが完了したタイミングで DataLoader の遅延評価を行う Promise::XS::resolved()->then(sub { $result })->then(sub { $result = shift }); $context->dispatch_data_loaders(); return ($result, $cachable); } 15 各フィールドに対してどの値を返すの かという関数
16 GraphQLの型・フィール ドに対応するパッケージ ・サブルーチン名を動的 に組み立てて解決してい る フィールドのリゾルバをモジュールに分割して動的に解決したい 例えば `Book` 型のフィールドは`App::GraphQL::Resolver::Book`に置きたい
sub _resolver { my ($root_value, $args, $context, $info) = @_; # $root_value: 対象のスコープの型 # $args: フィールドに渡されている引数のHashRef # $context: GraphQL::Execution::execute の第4引数に渡されたオブジェクト # $info: 型の情報やスキーマのASTなどの情報 # 解決したい対象のフィールドの名前 my $field_name = $info->{field_name}; # 単純に HashRef のフィールドであれば,その値をそのまま返す my $is_hashref = (!blessed($root_value) && ref $root_value eq 'HASH'); if ($is_hashref && exists $root_value->{$field_name}) { return $root_value->{$field_name}; } # 個別のResolverモジュールに移譲する my $parent_name = $info->{parent_type}->name; my $resolver_impl = join '::', (__PACKAGE__, 'Resolver', $parent_name); my ($resolver_loaded) = try_load_class($resolver_impl); if ($resolver_loaded && $resolver_impl->can($field_name)) { return $resolver_impl->$field_name($root_value, $args, $context, $info); } return undef; # 解決できずなにも得られなかった! }
GraphQL でもキャッシュを使いたい 17
REST API のキャッシュ • リソースごとに異なるエンドポイント • キャッシュサーバーの Varnish でキャッシュしている GraphQL
API のキャッシュ • GraphQL API だと、違うクエリでもエンドポイントが同じ、クエリの内容によって キャッシュできるかどうかが異なる • そこで Perl のアプリケーション側でキャッシュ可能か判断して、Redis にクエリに対する 結果を保存している 18
どうやってキャッシュ可能なクエリかを判断するのか • Mutation ならばキャッシュしない • Query でもユーザーによって結果が変わるクエリはキャッシュしない 19
再掲 # Mutation ではキャッシュしない my $cachable = !!0; for my
$query ($parsed_query->@*) { if (($_->{operationType} // '') eq 'mutation' && ($_->{name} // '') eq $operation_name) { $cachable = !!1; } } # Contextは各 Resolver で参照できる共通のストア my $context = App::GraphQL::Context->new(); context にキャッシュ可能かのフラグをもたせて、キャッシュ可能なときだけキャッシュを作成 する 20
難しいところ • 実装ミスでキャッシュしたいクエリがキャッシュできてなかったり、キャッシュしてはい けないクエリがキャッシュされるということが起こりうる 21
Context のアクセサーでキャッシュ不可のフラグを立てる # ユーザーデバイスの UUID sub device_uuid { my ($self)
= @_; # キャッシュ不可というフラグを立てる $self->disable_cache; return $self->{device_uuid}; } sub platform { my ($self) = @_; return $self->{platform}; } 22 ユーザーの情報を参照したとき にはキャッシュ不可というフラ グを立てる キャッシュ不可でない情報を参 照したときには何もしない こうすることでキャッシュ不可な クエリをうっかりキャッシュする ことを防いでいる
キャッシュのキー • アプリのプラットフォームが iOS か Android かで結果を変えることができるように実装 してある • 同じキャッシュにしているとダメなので、キャッシュキーにプラットフォームを含める
• このような情報も context に含めている 23
事前にキャッシュを作る仕組み • マンガの公開時刻に公開前のキャッシュが残ってほしくないので、特定の時間にキャッ シュがexpireするようにしている • キャッシュがexpireしたタイミングで大量のクエリが来る問題がある(Cache Stampede) • その問題を回避するためにGraphQLクエリから事前にレスポンスのキャッシュを定期的に 作っている
24
今後の展望 • 事前にキャッシュを作る仕組みでキャッシュの切り替えタイミングのリクエストをさばく ことには成功している • とはいえどのクエリのキャッシュをつくるかという問題は難しい • 今後は Persisted Query
に移行予定 25
まとめ • はてなのマンガビューワ GigaViewer で GraphQL を採用できた • Perl でも
GraphQL は実装できる • GraphQL では REST API とは違うキャッシュ戦略が必要で、 GigaViewer ではアプリケーションのレイヤーでキャッシュ可能かを 判別して Redis にキャッシュを保存している 26