Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
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
AtCoder Conference 2025「LLM時代のAHC」
imjk
2
570
Navigating Dependency Injection with Metro
l2hyunwoo
1
170
これならできる!個人開発のすゝめ
tinykitten
PRO
0
130
Context is King? 〜Verifiability時代とコンテキスト設計 / Beyond "Context is King"
rkaga
10
1.4k
Combinatorial Interview Problems with Backtracking Solutions - From Imperative Procedural Programming to Declarative Functional Programming - Part 2
philipschwarz
PRO
0
110
実は歴史的なアップデートだと思う AWS Interconnect - multicloud
maroon1st
0
250
GoLab2025 Recap
kuro_kurorrr
0
780
TestingOsaka6_Ozono
o3
0
170
生成AIを利用するだけでなく、投資できる組織へ
pospome
2
400
Findy AI+の開発、運用におけるMCP活用事例
starfish719
0
1.7k
宅宅自以為的浪漫:跟 AI 一起為自己辦的研討會寫一個售票系統
eddie
0
530
Flutter On-device AI로 완성하는 오프라인 앱, 박제창 @DevFest INCHEON 2025
itsmedreamwalker
1
140
Featured
See All Featured
Large-scale JavaScript Application Architecture
addyosmani
515
110k
My Coaching Mixtape
mlcsv
0
13
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
37
2.7k
The Invisible Side of Design
smashingmag
302
51k
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
69
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
980
GraphQLとの向き合い方2022年版
quramy
50
14k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
0
2.2k
AI: The stuff that nobody shows you
jnunemaker
PRO
1
12
Automating Front-end Workflow
addyosmani
1371
200k
SERP Conf. Vienna - Web Accessibility: Optimizing for Inclusivity and SEO
sarafernandez
1
1.3k
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