Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
MagicOnionでの共通処理の挟み方
Search
pasta
June 04, 2019
Programming
4k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
MagicOnionでの共通処理の挟み方
MagicOnion勉強会
https://connpass.com/event/127369/
pasta
June 04, 2019
Other Decks in Programming
See All in Programming
ジョジョの世界をXRで再現する
mofurune
0
170
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
260
Workers Cache を知る
syumai
0
370
コードレビューのボトルネックを"する側"と"される側"の両面から解消する
yub0n
2
1.6k
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
230
GemmaをJevのように使ってみる / Use Gemma like Jev
kishida
5
800
Vue Fes Japan の “作る側”に関わってみて
naokihaba
0
160
phperだってパンチカードでコーディングしたい
o0h
PRO
0
350
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
610
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
re:Inventに行く前に知っておきたい現地参加のノウハウ
nokomoro3
0
440
Optunaを作ったときにやりたかったこと、その後 (2026/10/16, Optuna Meetup #3)
iwiwi
1
440
Featured
See All Featured
[SF Ruby Conf 2025] Rails X
palkan
3
1.5k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
360
Context Engineering - Making Every Token Count
addyosmani
9
1.2k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
67k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.9k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
800
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
630
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.6k
How to Think Like a Performance Engineer
csswizardry
28
2.9k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
630
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
420
Transcript
共通処理の挟み方 2019/06/04 MagicOnion勉強会 ぱすた(@p_a_sta) 1
だれ? ぱすた(@p_a_sta) 株式会社Donuts 新規開発部 音ゲーつくったり基盤フレームワークつくったり サーバサイド初心者 2
注意 今回は非ストリーミングAPIしか扱いません。 (ごめんなさい) 3
やりたいこと • サーバー/クライアント ◦ 共通リクエスト/レスポンスの送受信 ◦ 暗号化 • クライアント ◦
共通エラーハンドリング ◦ リトライ ◦ デバッグ出力 などなど…… 4
サーバーサイド 5
サーバーサイド 2通りのやりかた • MagicOnionFilter ◦ MagicOnionに用意された機能 • gRPC Interceptor ◦
gRPC本来の機能 どちらもRPCの前後に処理を挟むもの 6
MagicOnionFilter - 実装方法 MagicOnionFilterAttributeを継承する public class SampleFilterAttribute : MagicOnionFilterAttribute {
public override async ValueTask Invoke(ServiceContext context) { try { /* 前処理 */ await Next(context); /* 後処理 */ } catch { /* エラー処理 */ throw; } finally { /* on finally */ } } } 7
MagicOnionFilter - 適用方法 ①特定のクラス全体・または1つのメソッドに適用する場合 クラスまたはメソッドに属性として付与する // クラスに付けるとクラス全体に適用される [SampleFilter] public class
SampleService : ServiceBase<ISampleService>, ISampleService { // メソッドに付けるとメソッドだけに適用される [SampleFilter] public async UnaryResult<Nil> HogeAsync() { } } 8
MagicOnionFilter - 適用方法 ②全ての通信に適用する場合 MagicOnionOptions で指定する var options = new
MagicOnionOptions { GlobalFilters = new[] { new SampleFilterAttribute() }, }); var service = MagicOnionEngine.BuildServerServiceDefinition(options); 9
MagicOnionFilter - 概要図 10
gRPC Interceptor - 実装方法 Grpc.Core.Interceptors.Interceptor を継承する サーバ側・クライアント側それぞれについて、RPC 種類ごとにメ ソッドが用意されているので、必要なものだけを override
して実 装する 今回は UnaryServerHandler のみ説明 11
gRPC Interceptor - 実装方法 public class SampleInterceptor : Interceptor {
// ※MagicOnionを使っている限り TRequest, TResponse は byte[] 固定 public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>( TRequest request, // 生リクエストデータ ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation ) { /* 前処理 */ var response = await continuation(request, context); /* 後処理 */ return response; } } 12
gRPC Interceptor - 適用方法 MagicOnionServiceDefinition.ServerServiceDefinition から Interceptor を刺した ServerServiceDefinition を生成する
var service = MagicOnionEngine.BuildServerServiceDefinition(); var serverService = service.ServerServiceDefinition.Intercept( new SampleInterceptor() ); 13
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ 14
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ context.Items がリクエストごとに固有 のストレージになっている 15
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ context.RequestHeaders と context.WriteResponseHeadersAsync を使う 16
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ context.CallContext で gRPC Interceptor 側の context にアクセス可能 17
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ 18
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ context.ForceSetRawUnaryResult で書き換えは可能 元の値は取得不可 19
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ 20
比較 MagicOnion Filter gRPC Interceptor 部分適用 ◯ ✕ メソッド本体との値のやり取り ◯
✕ リクエストヘッダの読みとり レスポンスヘッダの書き換え ◯ ◯ リクエストボディの書き換え ✕ ◯ レスポンスボディの書き換え △ ◯ クライアントと共通化 ✕ ◯ 21 要件に合う方を選んで使うのが重要
サンプル - 共通リクエスト/レスポンスの送受信 • 要件 ◦ メソッド本体との値のやり取り ◦ リクエストヘッダの読みとり ◦
レスポンスヘッダの書き換え MagicOnionFilter 22
public class CommonFilterAttribute : MagicOnionFilterAttribute { // ヘッダにバイナリを詰める時は Metadata.BinaryHeaderSuffix を付けないといけない
public const string Key = "base" + Metadata.BinaryHeaderSuffix; public override async ValueTask Invoke(ServiceContext context) { var header = context.CallContext.RequestHeaders.First(x => x.Key == Key); var commonRequest = LZ4MessagePackSerializer.Deserialize<CommonRequest>(header.ValueBytes); /* 共通リクエストに対する処理 */ context.Items[Key] = commonRequest; // メソッドで使うために詰めておく await Next(context); var commonResponse = new CommonResponse { /* 共通レスポンス作成 */ }; await context.CallContext.WriteResponseHeadersAsync(new Metadata { { Key, LZ4MessagePackSerializer.Serialize(commonResponse) } }); } } 23
サンプル - 暗号化 • 要件 ◦ リクエストボディの書き換え ◦ レスポンスボディの書き換え ◦
クライアントと共通化 gRPC Interceptor 24
public class CryptoInterceptor : Interceptor { public const string Key
= "暗号鍵"; public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>( TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation ) { request = Decrypt(request as byte[]) as TRequest; var response = await continuation(request, context); return Encrypt(response as byte[]) as TResponse; } private byte[] Encrypt(byte[] rawData) => 実装略; private byte[] Decrypt(byte[] encryptedData) => 実装略; } 25
クライアントサイド 26
クライアントサイド - 実装方法 • 基本的に gRPC Interceptor 一択 • MagicOnionClient.Create
に Interceptor を挟んだ CallInvoker を渡す var channel = new Channel("localhost", 12345, ChannelCredentials.Insecure); var invoker = channel.Intercept(new HogeInterceptor()); var client = MagicOnionClient.Create<IHogeService>(invoker); 27
クライアントサイド - 実装方法 28 public class SampleInterceptor : Interceptor {
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>( TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation ) { /* 前処理 */ var call = continuation(request, context); return call; } }
クライアントサイド - 実装方法 29 public class SampleInterceptor : Interceptor {
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>( TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation ) { /* 前処理 */ var call = continuation(request, context); return call; } } 戻り値が Task(or task-like) じゃない →async/awaitが使えない →後処理どうするの?
クライアントサイド - 実装方法 30 var call = continuation(request, context); return
new AsyncUnaryCall<TResponse>( call.ResponseAsync.ContinueWith(res => { /* 後処理 */ return res.Result; }), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose ); • 新しい AsyncUnaryCall を作る
クライアントサイド - 実装方法 31 var call = continuation(request, context); return
new AsyncUnaryCall<TResponse>( call.ResponseAsync.ContinueWith(async res => { /* 後処理 */ return res.Result; }).Unwrap(), call.ResponseHeadersAsync, call.GetStatus, call.GetTrailers, call.Dispose ); • 後処理で await したい場合
クライアントサイド - 実装方法 32 • 前処理で await したい場合 ◦ 不明……
エラーハンドリング 33
エラーハンドリング - 落とし穴 • 例外が発生した場合 ◦ gRPC 的には RpcException が発生するはず
◦ 実際は AggregateException が発生 ▪ Task 内部で発生した例外は AggregateException に 集約される ▪ 元の例外は .InnerExceptions で取得 34
エラーハンドリング - 落とし穴 • 例外が発生した場合 ◦ Interceptor を重ねるとその数だけ AggregateException でラップされる
▪ めちゃくちゃ取り出しづらい ▪ 本来は機能単位で Interceptor を分けるべきだが、例 外が扱いづらくなるため1クラスにまとめたほうが良い かもしれない • 結局サーバーと共通化できない 35
エラーハンドリング - リトライ • 一定の例外だったらリトライしたい • Interceptor でリトライをする方法はなさそう(※独自調べ) 拡張メソッドを作って、必ずそこ経由で呼ぶようにするしかなさそう 36
エラーハンドリング - リトライ 37 // InvokeWithErrorAsync 経由で呼ぶ MagicOnionClient.Create<IHogeService>().InvokeWithErrorAsync(x => x.HogeAsync());
// 通常はこう // MagicOnionClient.Create<IHogeService>().HogeAsync(); • イメージ (めんどくさい……)
リトライ - 実装方法 38 // IService からメソッドチェーンしたいので型引数が2つになる public static async
UniTask<T> InvokeWithErrorAsync<T, X>(this X self, Func<X, UnaryResult<T>> connector) where X : IService<X> { while (true) { try { return await connector(self); } // AggregateException から RpcException を抜き取る catch (AggregateException e) when (e.InnerException is RpcException rpc) { // 次のスライドで... } } }
リトライ - 実装方法 39 catch (AggregateException e) when (e.InnerException is
RpcException rpc) { // ここの条件は要件次第 switch (rpc.StatusCode) { case StatusCode.Unavailable: if (await リトライかタイトルに戻るかを聞く() == "リトライ") continue; SceneManager.LoadSceneAsync("Title"); // OperationCanceledException を投げて処理を打ち切ったことを通知 throw new OperationCanceledException("タイトルに戻りました", rpc); } // その他の例外はそのまま投げる throw; }
まとめ 40
まとめ • サーバー ◦ MagicOnionFilter と gRPC Interceptor のうち 要件に合う方を選んで使う
• クライアント ◦ gRPC Interceptor を使う ▪ 複数挟むと例外が扱いづらくなるので1クラスで ◦ gRPC Interceptor でできないことは泥臭く実装 41