WEB+DB PRESS Vol.114

特集1 AWS/GCP セキュア化計画

本業でクラウドサービスを開発することが増えてきて、 今までは Azure App Service や Azure SQL Database といった、SaaS やPaaSにのっかっていたが、 これからはガッツリと VPC を構築しそう。 セキュリティは最重要。 本格的な勉強を始めるところだったので、 できれば Microsoft Azure も扱って欲しかった。 Microsoft Azure ならこうだろうな、と読み替えながら参考にしよう。

特集2 作って学ぶ WebAuthn

WebAuthn の名前だけは mozaic.fm で聞いていたが、 どういった技術なのかまでは把握していなかった。 ちょうど本業の方で認証基盤を開発をすることになりそうで、 要件として多要素認証の対応は求められており、 2要素目として WebAuth は有力だな。 特集のサンプルは Java だったが、 WebAuthn のライブラリは C# にもあるみたいなので、 ASP.NET Core 製のWebアプリに組み込めるか調べてみよう。

特集3 Kyash 開発ノウハウ大公開

カード発行・決済サービスとなると、 最上級のセキュリティが求められるので、 その対応の大変さをうかがい知れた。

Kyash Direct の設計では、 クリーンアーキテクチャ・マイクロサービス・CQRS・Sagaパターン・イベントソーシングと、 盛りだくさんの攻めたアーキテクチャで、 かなりチャレンジングなことやっていて熱かった。 部分的に Go のサンプルコードも載っていて、 貴重な実例として参考になった。

過去の自分へコードレビュー

最近はコンパイラや OS といった低レイヤーにも興味出てきたので、 まずはコンパイラや CPU の気持ちを考えたコードが書けるようになりたい。 最近の C# もパフォーマンス志向だし。

コードレビューで強い言葉を使う人は自分の周りにいないけど、 言われた凹むのは容易に想像できる。 書いた人の批判ではなくコードの批判だとはいえ、 そのコードはいわば自分の作品なわけで、 分けて受け止めるのは難しい。 そこまでドライにはなれないかな。 だから、自分がレビューするときは強い言葉は避けている。 強い言葉を使わずとも、より良いコードに導けるようになりたい。

まるきん亭

木の葉モールの隣にある『まるきん亭』がラーメンウォーカーに載っていたので行ってみた。食べログや Retty の評価はまぁまぁなので、存在は認識していたけど、今までスルーしていた。まさかラーメンウォーカーに載るくらいの店だったとはね。

ラーメンとチャーハンと餃子ぜんぶ食べたい欲張り向けに、『スペシャルセット』というのがあったので注文。 ラーメンは博多ラーメン系のような、白濁が薄めのあっさりした豚骨ラーメンだった。

半チャーハンはパラパラに仕上がっていて、味付けも丁度いい塩梅。 ラーメンとチャーハンの相性の良さは間違い無いので、 一緒に注文しない理由があるだろうか。いや無い。

餃子は皮がしっかり焼かれてパリッっとしていて、 中は具材から滲み出たスープがたっぷりでジューシー。 これは白米が進みそうだ。 それでも白米よりチャーハンが食べたいので悩ましい。

ラーメンに半チャーハンに餃子と、食べたかったもの全部のセットが存在していて満足した。他の、チャーハンと餃子をメニューに載せている店は見習って欲しい。このような郊外にある店でもクオリティが高くて、福岡市の食のレベルの高さがうかがえる。

コードファースト ASP.NET Core gRPC ストリーミング編

はじめに

先日、WCF から gRPC に移行するときの手段として、.proto ファイルを書くのではなく、protobuf-net.Grpc を使って C# のコードから gRPC のクライアントとサーバーを動的に生成する方法を紹介した。コードファースト ASP.NET Core gRPC と命名

tnakamura.hatenablog.com

試したのは 1 リクエストで 1 レスポンスを返す、gRPC でいうところの Unary 方式。 WCF には使いにくいけど Streaming がある。 マジ使いにくいけど。 一方で、gRPC には Client Streaming、Server Streaming、Duplex Streaming がある。 コードファーストで gRPC をやる場合に、Streaming はどうやるのか試してみた。

サービスコントラクトとデータコントラクト

.NET Core ライブラリプロジェクトで、WCF のときみたいにサービスコントラクトとデータコントラクトを定義する。 Streaming は IAsyncEnumerable<T> で表現。 Client Streaming は引数、Server Streaming は返り値、Duplex Streaming はその両方で型が IAsyncEnumerable<T> になる。

using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Threading.Tasks;

namespace CodeFirstGrpc.Shared
{
    [ServiceContract]
    public interface IGreetingService
    {
        ValueTask<HelloReply> HelloAsync(HelloRequest request);

        IAsyncEnumerable<HelloReply> ServerStreamingHelloAsync(HelloRequest requests);

        IAsyncEnumerable<HelloReply> DuplexStreamingHelloAsync(IAsyncEnumerable<HelloRequest> requests);
    }

    [DataContract]
    public class HelloRequest
    {
        [DataMember(Order = 1)]
        public string Name { get; set; }
    }

    [DataContract]
    public class HelloReply
    {
        [DataMember(Order = 1)]
        public string Message { get; set; }
    }
}

gRPC サービスの実装

async なメソッド内での yield returnawait foreach を使って、Server Streaming と Duplex Streaming を実装。 C# らしい書き味で筋が良いと感じる。

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using CodeFirstGrpc.Shared;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ProtoBuf.Grpc.Server;

namespace CodeFirstGrpc
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCodeFirstGrpc();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGrpcService<GreetingService>();
            });
        }
    }

    public class GreetingService : IGreetingService
    {

        public ValueTask<HelloReply> HelloAsync(HelloRequest request)
        {
            return new ValueTask<HelloReply>(new HelloReply
            {
                Message = $"Hello, {request.Name}.",
            });
        }

        public IAsyncEnumerable<HelloReply> ServerStreamingHelloAsync(HelloRequest request) =>
            ServerStreamHelloAsyncImpl(request, default);

        async IAsyncEnumerable<HelloReply> ServerStreamHelloAsyncImpl(
            HelloRequest request,
            [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            for (var i = 0; i < 5; i++)
            {
                if (!cancellationToken.IsCancellationRequested)
                {
                    yield return new HelloReply
                    {
                        Message = $"Hello, {request.Name}.",
                    };
                    await Task.Delay(TimeSpan.FromSeconds(1));
                }
            }
        }

        public IAsyncEnumerable<HelloReply> DuplexStreamingHelloAsync(IAsyncEnumerable<HelloRequest> requests) =>
            DuplexStreamHelloAsyncImpl(requests, default);

        async IAsyncEnumerable<HelloReply> DuplexStreamHelloAsyncImpl(
            IAsyncEnumerable<HelloRequest> requests,
            [EnumeratorCancellation] CancellationToken cancellationToken)
        {
            await foreach (var request in requests)
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                if (!cancellationToken.IsCancellationRequested)
                {
                    yield return new HelloReply
                    {
                        Message = $"Hello, {request.Name}.",
                    };
                }
            }
        }
    }
}

gRPC クライアントの実装

Server Streaming を await foreach で処理できるのが実に自然。 Duplex Streaming の Client から Server に送る方、つまりは Client Streaming だけど、 IAsyncEnumerable<T> を作って渡すところが馴れない。 自分が IAsyncEnumerable<T> を触り始めたばかりなのもあるが、 もうちょっと上手く書けないか試行錯誤の余地がありそう。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CodeFirstGrpc.Shared;
using Grpc.Net.Client;
using ProtoBuf.Grpc.Client;

namespace CodeFirstGrpc.Client
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            var client = channel.CreateGrpcService<IGreetingService>();

            Console.WriteLine("=== Unary Test ===");
            var reply = await client.HelloAsync(new HelloRequest
            {
                Name = "Kubo"
            });
            Console.WriteLine(reply.Message);

            Console.WriteLine("=== Server Streaming Test ===");
            try
            {
                await foreach (var x in client.ServerStreamingHelloAsync(new HelloRequest { Name = "Minamino" }))
                {
                    Console.WriteLine(x.Message);
                }

            }
            catch (TaskCanceledException) { }

            Console.WriteLine("=== Duplex Streaming Test ===");
            try
            {
                await foreach (var x in client.DuplexStreamingHelloAsync(GenerateRequests()))
                {
                    Console.WriteLine(x.Message);
                }

            }
            catch (TaskCanceledException) { }

            Console.ReadLine();
        }

        static async IAsyncEnumerable<HelloRequest> GenerateRequests()
        {
            yield return new HelloRequest { Name = "Douan" };
            await Task.Delay(TimeSpan.FromSeconds(1));
            yield return new HelloRequest { Name = "Osako" };
            await Task.Delay(TimeSpan.FromSeconds(1));
            yield return new HelloRequest { Name = "Nakajima" };
            await Task.Delay(TimeSpan.FromSeconds(1));
            yield return new HelloRequest { Name = "Sibasaki" };
            await Task.Delay(TimeSpan.FromSeconds(1));
            yield return new HelloRequest { Name = "Tomiyasu" };
        }
    }
}

実行

Streaming の様子がわかるように、アニメーション GIF を作成してみた。

f:id:griefworker:20191217115009g:plain

まとめ

protobuf-net.Grpc は、gRPC の Client Streaming・Server Streaming・Duplex Streaming を、 IAsyncEnumerable<T> を使って表現していて、実に C# らしくて筋が良いと感じた。

Unary だけでなく Streaming が思いのほか簡単に実装できてしまい、 WCF から gRPC に移行するための道筋がだいぶ見えてきた。 あとはヘッダーで認証に関する情報をやり取りできるかどうかを試せば、 調査終了にできそうだ。

3月のライオン(15)

学園祭フィナーレのファイヤーパーティで、零がひなたに気持ちを伝える場面は、もう眩しすぎて直視出来ない。ていうか、今まで好きって伝えてなかったんかい。零の気持ちがひなたに伝わって、肝心なところのシーンは意図的に省かれていたけど、その後 2 人で離れたところからファイヤーパーティを眺める姿から、おそらくそういうことなんだろうな。

零が戦うことが増えてきたA級は、人間性を捧げなきゃ生きていけないような、 天才や怪物の住む世界。 棋戦でのアウトプットに対し、棋譜や研究のインプットが追いつかない、黒い部屋と自身で表現していた状態に陥りやすくなっている零だが、現状から抜け出すには、リソースのほとんどを将棋に費やす必要があるんだろう。

ただ、生きていくために将棋を選んだ零が、ようやく手にした居場所を犠牲にしてタイトルを獲得しても、はたしてハッピーエンドと言えるのかどうか。 そんな零に、ようやく手にした居場所を手放してはいけないと訴えた先生は教師の鑑だな。 零の数少ない理解者。 あかりの相手ということは零の家族になるかもしれないわけなので、 案外、 相応しいのかもしれないと思えた。 総合力的な意味で。

3月のライオン 15 (ヤングアニマルコミックス)

3月のライオン 15 (ヤングアニマルコミックス)

謹賀新年

今年の抱負として、またOKRを設定したいと思う。OKR は次の通り。

  • 個人開発
    • Write code every day
    • プロダクトを 2 つ以上リリースする
  • 英語
    • Study english every day
  • コミュニティ
    • Fukuoka.NET
    • JXUG
    • ふくあず

個人開発は引き続き Write code every day を続ける。 プロダクトは今年こそ 2 つ以上リリースしたい。 1 つは 1 月中に OSS として公開予定で、 それとは別にもう 1 つ作っているやつがあるので、 それはリリースする。絶対に。

英語は何か教材を見つけて、勉強する習慣をつけたいと思う。 Write code every day ならぬ、Study english every day。 Duolingo とか良さそうだ。

コミュニティ活動も今年こそ再開したい。 子供が大きくなってきて、 自分が子供の面倒見て奥さんがライブとか飲み会に出かけられるくらいには余裕出来てきたので、 自分の方も勉強会に参加していこうかなと思う。 自分の主戦場が C# とか Azure とか Microsoft テクノロジーだから、 Fukuoka.NET や JXUG やふくあずに参加しようかなと考えている。 ひとまず月1回くらいのペースで。

という感じで 2020 年もやっていくぞ。

2019 年ふりかえり

2019 年も終わりなので、毎年恒例 1 年の振り返りをしようと思う。 元日に 1 年の抱負として OKR を設定していたので、それを 1 つずつ振り返って評価していく。

tnakamura.hatenablog.com

OKR の Objectives は 2 つ設定していて、1つが個人開発を頑張る。もう1つは英語を頑張る。

  • 個人開発を頑張る
    • Write Code Every Day
    • プロダクトを 2 つリリースする
  • 英語を頑張る
    • TOEIC を受ける
    • コミットコメントやドキュメントを英語で書く
    • 英語のポッドキャストを聴きまくる

個人開発の Key Results には Write code every day と、 プロダクトを 2 つリリースすることを挙げていた。 コードを書く方は、毎日とまでは行かないけど結構書いたんじゃないかなと思う。 GitHub の草を見た感じ、7 割くらいは埋まってるんじゃないか。 いや、言い過ぎか。6 割ぐらいかな。 プロダクトをリリースする方は 1 個もリリースできていないので、これは果たせていない。 実際のところ、公開直前までいってたアプリが 1 つあったんだけど、ちょっと事情が変わって OSS にしようとして、 その作業が間に合わなかった。 1 月の間に公開したいと思う。

次に英語の方だけど、 TOEIC は受けてない。 コミットやドキュメントに関しては、 public にしているリポジトリでは英語で書くよう頑張っているが、 そんなに長文を書いているわけじゃないので、これはやったと言えるものじゃない。 英語の Podcast は、日本語の Podcast のストックが切れた時に、 The Changelog Podcast をちょこちょこ聴いていた。

設定した OKR はほとんど達成できなかったが、 2019 年はデータベーススペシャリストを取ったり、 MCSD をゼロから取ったりと、 資格の勉強を頑張った 1 年間だった。 就職してからおそらく一番勉強した気がする。 それを加味して 2019 年を採点するなら、まぁ 50 点ぐらいはつけておこう。 それでも優良可不可では不可なんだけどね。 満足行く 1 年ではなかった。 来年はもうちょっと英語とか何とかしたい。

それでは良いお年を。

波よ聞いてくれ(7)

引きこもりの更生に自身のラジオ番組の企画として挑むミナレ達なんだけど、最後の手段と思われていた暴力沙汰に開始1分でなってしまうのは、観てる分には痛快で面白いが、当事者だったらたまったもんじゃないだろうな。まぁ、引きこもりの男とその家族みんなワケありで、個性的なキャラクターばかりだから、それでも成り立っちゃうんだが。中原はミナレのどこに惚れたんかね。

そんな風なマンガなので、作中で北海道の地震を扱ったのは驚いた。北海道が舞台で、かつラジオの社会的役割を考えれば、扱わない方がむしろ不自然か。リアルな地震の描写は、当時のニュースでは伝わってこなかった臨場感というか、現場感が伝わってきた。

波よ聞いてくれ(7) (アフタヌーンコミックス)

波よ聞いてくれ(7) (アフタヌーンコミックス)