Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Perl でも GraphQL を使いたい
Search
nakaokat
September 08, 2022
Programming
0
2.2k
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
PipeCDのプラグイン化で目指すところ
warashi
1
180
datadog dash 2025 LLM observability for reliability and stability
ivry_presentationmaterials
0
190
Railsアプリケーションと パフォーマンスチューニング ー 秒間5万リクエストの モバイルオーダーシステムを支える事例 ー Rubyセミナー 大阪
falcon8823
4
1k
Node-RED を(HTTP で)つなげる MCP サーバーを作ってみた
highu
0
110
Blazing Fast UI Development with Compose Hot Reload (droidcon New York 2025)
zsmb
1
260
第9回 情シス転職ミートアップ 株式会社IVRy(アイブリー)の紹介
ivry_presentationmaterials
1
250
WindowInsetsだってテストしたい
ryunen344
1
200
アンドパッドの Go 勉強会「 gopher 会」とその内容の紹介
andpad
0
270
DroidKnights 2025 - 다양한 스크롤 뷰에서의 영상 재생
gaeun5744
3
330
20250613-SSKMvol.15
diostray
0
100
地方に住むエンジニアの残酷な現実とキャリア論
ichimichi
5
1.4k
Bytecode Manipulation 으로 생산성 높이기
bigstark
2
390
Featured
See All Featured
Code Review Best Practice
trishagee
68
18k
Being A Developer After 40
akosma
90
590k
Rails Girls Zürich Keynote
gr2m
94
14k
Faster Mobile Websites
deanohume
307
31k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.5k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
657
60k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
48
2.9k
Optimizing for Happiness
mojombo
379
70k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
20
1.3k
Why Our Code Smells
bkeepers
PRO
337
57k
Thoughts on Productivity
jonyablonski
69
4.7k
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