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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
nakaokat
September 08, 2022
Programming
0
2.4k
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
What Spring Developers Should Know About Jakarta EE
ivargrimstad
0
360
Railsの気持ちを考えながらコントローラとビューを整頓する/tidying-rails-controllers-and-views-as-rails-think
moro
5
390
Claude Codeセッション現状確認 2026福岡 / fukuoka-aicoding-00-beacon
monochromegane
4
410
CSC307 Lecture 14
javiergs
PRO
0
470
AI時代でも変わらない技術コミュニティの力~10年続く“ゆるい”つながりが生み出す価値
n_takehata
2
700
CSC307 Lecture 13
javiergs
PRO
0
320
20260228_JAWS_Beginner_Kansai
takuyay0ne
5
470
AI時代のシステム設計:ドメインモデルで変更しやすさを守る設計戦略
masuda220
PRO
4
700
モジュラモノリスにおける境界をGoのinternalパッケージで守る
magavel
0
3.5k
クライアントワークでSREをするということ。あるいは事業会社におけるSREと同じこと・違うこと
nnaka2992
1
320
コーディングルールの鮮度を保ちたい / keep-fresh-go-internal-conventions
handlename
0
180
RubyとGoでゼロから作る証券システム: 高信頼性が求められるシステムのコードの外側にある設計と運用のリアル
free_world21
0
260
Featured
See All Featured
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
200
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.1k
A Tale of Four Properties
chriscoyier
163
24k
Un-Boring Meetings
codingconduct
0
220
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
1
1.4k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
1.7k
How to Talk to Developers About Accessibility
jct
2
150
Technical Leadership for Architectural Decision Making
baasie
3
280
Code Review Best Practice
trishagee
74
20k
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
210
Fashionably flexible responsive web design (full day workshop)
malarkey
408
66k
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