黄金の福ワンタン まくり

飯倉にある『黄金の福ワンタン まくり』に行ってみた。住宅街で一際目立つ外観。黄色に赤はインパクトある。

看板メニューのワンタン麺を注文した。スープは醤油と塩が選べたので醤油を選択。オーソドックスでありながら、予想していたよりもクオリティが高い中華そばで、醤油ラーメン好きとして満足いく一杯だった。酸辣湯麺ではなくワンタン麺にしてよかったと思うほどに。いや、まぁ酸っぱいの苦手なのもあるけど。

ワンタン麺だけではボリューム的に不安だったので、半チャーハンも注文。さすがパラパラに仕上がっていて、レンゲを口に運ぶ手が止まらなかった。ただ、残念なことに胃袋は半チャーハンでは満足しなかったみたいだ。普通のチャーハンを注文すればよかったのだが、予算オーバーか。

メニュー豊富で、味も文句なかった。ただ、ちょっとお値段高めかも。ラーメン屋として見ているからかもしれない。中華料理屋として見たらむしろ安い部類か。店がターゲットにしている客層から自分が外れている可能性もあるな。

安全食堂

休日に九大学研都市にある『安全食堂』まで遠征してみた。この店はラーメンウォーカー九州版で福岡1位に選ばれていて、その影響か、開店時間前に行ったのに既に並んでいた。みんな考えることは同じってことか。開店して数分で満席。ラーメンウォーカーの影響力凄いな。

相席になったが、すぐ席に座れたので、ラーメンと焼きめしを注文した。ラーメンは白濁してないあっさりとした豚骨スープで、飽きが来なさそう。普段食べるのにもってこいって感じの味。チャーシューは脂身が少なくて自分好みだった。

焼きめしはラーメンよりも値段が高くて、ボリュームがもの凄い。2人前くらいありそう。程よくシットリ仕上がっていたのと、このために朝食を抜いて準備万端だったのもあり、余裕で平らげてしまった。ただ、半分サイズがあれば普段はそちらを選ぶだろうな。家族で来たらシェアするのが良さそう。

文字通り腹一杯になった。なかなか良いお値段になったが、胃袋が満たされているから満足感がある。ラーメンウォーカー九州版は味だけでなく、本店のみ、かつ普段使いできそうな店を評価する傾向がありそうなので、安全食堂が1位に輝いたのは不思議ではないな。

関連ランキング:ラーメン | 九大学研都市駅

天麩羅処ひらお アクロス店

天麩羅のひらおがアクロス福岡地下2階にオープンしていた。大名店はランチで行くにはちょっと遠いけど、アクロス福岡なら余裕の距離。ただし、オープンしたばかりで、ランチは行列必至。なので夜に行ってみた。

ひらおではいつも『あじわい定食』にしている。今回も当然。これが自分的に一番バランスがいい。味噌汁は良い出汁が出ていて美味い。いかの塩辛が食べられない身なので、人生ちょっと損しているかもな。

エビのプリとした食感がたまらない。えび定食にするか毎回悩む。次あたり食べてみようか。でも白身魚の、このホクホクした旨さは捨てがたい。

とり天は安定の旨さ。天ぷらにするなら豚よりは鶏だよな、やっぱ。茄子は噛むと熱々の汁が口に広がって美味いやら熱いやらで二重の意味で悶絶モノ。いつもここで口の中をヤケドしてる気がする。

ピーマンは天ぷらにすると苦いどころか、不思議と甘い。芋は当然のように甘い。ここらへんはデザートみたいなものかな。

最後はイカ。ほどよい弾力があって、ほどよい歯ごたえ。ツルツル過ぎて、衣から飛び出しがちなのがたまにキズだが、これがないと物足りない。

久しぶりに揚げたての天ぷらを味わえて満足。もう少ししたら昼の混雑も落ち着くだろうか。そうなったら、ランチの有力候補になるな。天神周辺には大名と、あともう1店舗できるとは聞いていたが、なんとも良い場所にできて嬉しい限りだ。

関連ランキング:天ぷら | 天神駅天神南駅西鉄福岡駅(天神)

『WEB+DB PRESS Vol.107』を読んだ

読み終わったので感想をメモしておく。

特集1 実践 CircleCI

CircleCI は Docker が使えるから開発環境と CI 環境を同じにできるのは、やはり強み。さらには Docker Compose で PostgreSQL や Redis といったミドルウェアもセットにして実行できるのなんて、最高過ぎて SaaS を使える会社が羨ましい。

CI は環境を用意するのが本当に手間。とくにデータベース。そして簡単に環境壊れるし。コンテナなら毎回クリーンな環境を準備できる。壊さないように気を使う必要もない。Docker コンテナが使えることを知った当時、CI 革命だと思ったね。あぁ、オンプレでも使いたい。でも管理したくないから、やっぱり SaaS がいいや。

特集2 iOS 12 最新活用

iOS 限定とはいえ、こんなに手軽に機械学習が試せるのは衝撃だった。機械学習は環境構築が一苦労。それに悲しいかな、効率を上げようと思うと、何かと金がかかる。この手軽さは、はじめの一歩に良い。

より複雑なことをやりたくなったら次のステップ。TensorFlow とかに進むといいんだろうな。Google Colaboratory が良さげなんだけど、いつまで使えるか分かんないから、直ぐにでも機械学習とりかかりたい。ちょうどネタもあるし。

特集3 UI 改善ビフォーアフター

自分は仕事でまさに業務アプリを開発しているが、さすがに本特集の悪い例ほどは酷くなかった。むしろ、本特集の良い例に近いかもしれない。業務アプリといってもパッケージなので使いにくいと淘汰される。UI の善し悪しは死活問題。

自分たちが感覚的にやっていたことが間違ってはいなかったので一安心といったところ。ただ、感覚的だと再現性がないので論理的にやっていかなければいけないな。

at the front 第1回 Chromiumから見る Webの未来

ようやくインタビュー連載が始まった。インタビュアーはまさかの mizchi 氏。フリーランスだし、ポッドキャストのホストやゲストの経験あるしで、フロントエンド界隈では適任ぽい。

Off-the-main-thread は worker の活用方法としてかなり有力。メインスレッドをブロックするから遅いのなら、メインスレッドをブロックしなければいいじゃない、と。iOS では非同期 UI 描画する強者もいて、プレゼンは個人的にインパクトあった。

speakerdeck.com

ブラウザでも同じようなことができるようになったら面白いので期待したい。

WEB+DB PRESS Vol.107

WEB+DB PRESS Vol.107

  • 作者: 大竹智也,浦井誠人,平野朋也,村田紘司,上野学,末永恭正,久保田祐史,吉川竜太,上野博司,牧大輔,西郡卓矢,桑原仁雄,小林謙太,竹馬光太郎,池田拓司,はまちや2,竹原,長谷川智希,北村壮大,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/10/24
  • メディア: 単行本
  • この商品を含むブログ (1件) を見る

ASP.NET Core で GraphQL API

GraphQL API が必要になったので、ASP.NET Core で実装できるか試してみた。ライブラリは現状 GraphQL 一択。

www.nuget.org

GraphiQL っていう、GraphQL を試すのに便利な Web UI があるので、それも利用した。ASP.NET Core のミドルウェアを使えば組み込むのは簡単。

www.nuget.org

必要になったのはクエリだけで、ミューテーションはまだ。なので今回は、ASP.NET Core MVC で GraphQL のエンドポイントを用意し、Entity Framework Core と組み合わせてデータベースに保存されたデータを取得するところまでをやってみた。

using GraphiQl;
using GraphQL;
using GraphQL.Types;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace HelloGraphQL
{
    //============================================
    // 1. Entity Framework Core で使うクラスを定義
    //============================================

    public class Team
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public IList<Player> Players { get; set; }
    }

    public class Player
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Position { get; set; }
        public int Number { get; set; }
        public int TeamId { get; set; }

        public Team Team { get; set; }
    }

    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public DbSet<Team> Teams => Set<Team>();

        public DbSet<Player> Players => Set<Player>();

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Team>(e =>
            {
                e.Property(x => x.Id).IsRequired();
                e.Property(x => x.Name).IsRequired();
                e.HasKey(x => x.Id);
                e.HasMany(x => x.Players);
            });
            modelBuilder.Entity<Player>(e =>
            {
                e.Property(x => x.Id).IsRequired();
                e.Property(x => x.Name).IsRequired();
                e.Property(x => x.Number).IsRequired();
                e.Property(x => x.Position).IsRequired();
                e.Property(x => x.TeamId).IsRequired();
                e.HasKey(x => x.Id);
                e.HasOne(x => x.Team);
            });
        }

        // テスト用のデータを登録する
        public void EnsureSeedData()
        {
            if (!Teams.Any())
            {
                Teams.Add(new Team
                {
                    Name = "バルセロナ",
                    Players = new List<Player>
                    {
                        new Player
                        {
                            Name = "メッシ",
                            Number = 10,
                            Position = "FW",
                        },
                        new Player
                        {
                            Name = "スアレス",
                            Number = 9,
                            Position = "FW",
                        },
                    },
                });
                Teams.Add(new Team
                {
                    Name = "レアルマドリード",
                    Players = new List<Player>
                    {
                        new Player
                        {
                            Name = "ベイル",
                            Number = 11,
                            Position = "FW",
                        },
                        new Player
                        {
                            Name = "モドリッチ",
                            Number = 10,
                            Position = "MF",
                        },
                    },
                });

                SaveChanges();
            }
        }
    }

    //==============================
    // 2. GraphQL で使うクラスを定義
    //==============================

    // Player に対応する GraphQL の型
    public class PlayerType : ObjectGraphType<Player>
    {
        public PlayerType()
        {
            Field(x => x.Id);
            Field(x => x.Name);
            Field(x => x.Position);
            Field(x => x.Number);
            Field(x => x.TeamId);
        }
    }

    // Team に対応する GraphQL の型
    public class TeamType : ObjectGraphType<Team>
    {
        public TeamType()
        {
            Field(x => x.Id);
            Field(x => x.Name);
            Field<ListGraphType<PlayerType>>("players");
        }
    }

    // クエリを定義
    public class HelloGraphQLQuery : ObjectGraphType
    {
        public HelloGraphQLQuery(ApplicationDbContext context)
        {
            Field<ListGraphType<TeamType>>(
                "teams",
                arguments: new QueryArguments(
                    new QueryArgument<IntGraphType>
                    {
                        Name = "id",
                    }),
                resolve: ctx =>
                {
                    var query = context.Teams.AsQueryable();
                    if (ctx.HasArgument("id"))
                    {
                        var id = ctx.GetArgument<int>("id");
                        query = query.Where(b => b.Id == id);
                    }
                    var teams = query.Include(b => b.Players)
                        .ToList();
                    return teams;
                });
        }
    }

    // GraphQL のスキーマを定義
    public class HelloGraphQLSchema : Schema
    {
        public HelloGraphQLSchema(IDependencyResolver dependencyResolver)
            : base(dependencyResolver)
        {
            // 今回はクエリだけ。ミューテーションは後で。
            Query = dependencyResolver.Resolve<HelloGraphQLQuery>();
        }
    }

    //====================================================
    // 3. ASP.NET Core MVC で GraphQL エンドポイントを実装
    //====================================================

    // クライアントから送信されてきた GraphQL をバインドするモデル
    public class GraphQLModel
    {
        public string OperationName { get; set; }

        public string Query { get; set; }
    }

    [Route("graphql")]
    [ApiController]
    public class GraphQLController : ControllerBase
    {
        readonly IDocumentExecuter documentExecuter;

        readonly ISchema schema;

        public GraphQLController(IDocumentExecuter documentExecuter, ISchema schema)
        {
            this.documentExecuter = documentExecuter;
            this.schema = schema;
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] GraphQLModel model)
        {
            var result = await documentExecuter.ExecuteAsync(new ExecutionOptions
            {
                Schema = schema,
                OperationName = model.OperationName,
                Query = model.Query
            });
            return Ok(result);
        }
    }

    //==============================================================
    // 4. いつものように ASP.NET Core 用の Startup と Program を作成
    //==============================================================

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services.AddMvc()
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            //--------------------
            // ここから GraphQL 用
            //--------------------

            services.AddTransient<IDocumentExecuter, DocumentExecuter>();
            services.AddTransient<ISchema, HelloGraphQLSchema>();
            services.AddTransient<HelloGraphQLQuery>();

            // IDependencyResolver が使うために GraphQL 用のクラスを
            // DI コンテナに登録しておく必要があるみたい。
            services.AddTransient<TeamType>();
            services.AddTransient<PlayerType>();
            var sp = services.BuildServiceProvider();
            services.AddSingleton<IDependencyResolver>(x => new FuncDependencyResolver(sp.GetService));
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ApplicationDbContext context)
        {
            // GraphiQL をホストして手軽に GraphQL を試せるようにする
            app.UseGraphiQl();

            app.UseMvc();

            // テスト用データベースが無ければ作る
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
            context.EnsureSeedData();
        }
    }

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

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

デバッグ実行して /graphql に Web ブラウザでアクセスすると、GraphiQL の UI が表示される。全チーム取得するクエリを実行してみると、ちゃんとデータベースからデータを引っ張ってこれた。余談だけど、GraphiQL は補完が効いて、これを使わないなんてとんでもない、ってレベル。

f:id:griefworker:20181101152856p:plain

次に、指定した id に該当するチームだけ取得するクエリを実行してみると、こちらも上手く動いた。

f:id:griefworker:20181101152916p:plain

クエリの解析は GraphQL.NET がやってくれるけど、データベースからデータを引っ張ってくるところは自分で書く必要があって、なかなかツラミがあった。

あと、今回みたいな簡単なクエリを実装するだけでも結構苦労した。GraphQL 自体の仕様だけでなく、ライブラリの使い方も知る必要があったし、何より、REST API と GraphQL API では考え方が違いすぎて、切り替えるのが大変。GraphQL 脳にならないといけないな。

GraphQL API だと URL を考える必要は無くなったけど、どんなクエリやミューテーションを提供するか考える必要があるので、API 提供側の苦労はさほど変わらない印象だ。

ASP.NET Core のキャッチオールルートパラメーターでハマった

例えば、はてなブログのカスタム URL みたいな、entry/ 以下にすべてマッチするルートを定義する場合、ASP.NET Core MVC だと下記のように書く。

[HttpGet("entry/{*path}")]
public async Task<IActionResult> Details(string path)
{
    // IIS Express は %2F を / に戻してくれるが、
    // Kestrel は戻してくれないので、自分で戻す必要がある。
    path = Uri.UnescapeDataString(path);

    // path を使って何かする
}

path ルートパラメーターの値が %2F を含んでいるとき、デバッグ実行だと %2F/ に変換してくれるのに、ビルドしたものを dotnet コマンドで起動すると変換してくれなくて嵌った。どうも、Kestrel はやってくれないみたいだ。一方、IIS Express はやってくれる。デバッグ実行では IIS Express で動かしていて、dotnet コマンドでは Kestrel で動かしていたため遭遇した問題でしたとさ。

『五等分の花嫁(1)〜(6)』を読んだ

週刊少年マガジンで連載中の『五等分の花嫁』を1巻から6巻まで読んだ。

[まとめ買い] 五等分の花嫁

借金を抱えた貧乏な秀才・上杉風太郎が、一花・二乃・三玖・四葉・五月ら五つ子の家庭教師になって、落第を回避し卒業に導くというストーリー。正直な話、ジャンプのぼく勉と被ってると思った。ただ、絵は違う方向性でどちらも上手いし、登場人物はみんな魅力的だし、ストーリーも面白いしで、そんな難癖をつける気は速攻で無くなった。

五月は五つ子の中で一番真面目。そして頑固。性格的に風太郎と似たところがあるのか、よく衝突している。貧乏な家のカレーをおかわりするメンタルの強さは見習いたい。

四葉は最初から風太郎の味方。五つ子の中で一番素直で、頼まれると断れないお人好し。気持ちのいい性格をしている。そんなキャラクターなので、ラブコメのコメ担当、マスコット的な扱いで、ラブからは遠い。そういうのが案外、秘密兵器になったりするかも。最後まで秘密かもしれないが。

三玖は最初何考えているかわからないミステリアスな印象だった。それが、風太郎のおかげで自信がついてきてからはどんどん積極的になって、最初の頃からは想像できない姿勢を見せるまでに成長した。健気なところがあるので報われて欲しいと思う。

二乃は五つ子の中で一番女子力が高い。風太郎を一番最後まで敵視していて、ツンデレというにはデレが少なくツンが強烈。でも、その行動理由の多くは姉妹のためで、風太郎にツラくあたっても憎めない。むしろデレたら物足りなくなりそうだ。

一花は長女で、風太郎に対して何かとお姉さん風を吹かせてくる。あと、風太郎をからかったりも。大抵の場合、長女らしくしっかりしているんだけど、たまにドジなときがあって、そのギャップが良い。姉妹のために頑張ってたりするから、一番応援しているヒロインかなぁ。

風太郎は貧乏な家だからお金への執着心が強く、勉強ができるゆえにイヤミ、かと思いきや全くそうでもなかった。天上天下唯我独尊ではなく、意外と人の気持ちに立てる。そもそも勉強をするようになった理由が誰かの役に立つためで、五つ子と関わって成長した部分もあるだろうけど、本人の素質も間違いなくあるだろう。向けられる好意に対して鈍感なのは、ラブコメ主人公のお約束。

結婚式の場面から物語が始まったため、五人のうち誰かと結婚するのは確定。その相手は誰かは最後まで判らないんだろう。ただ、一花か三玖のどちらかな気がする。最初に出会ったのは五月だが、最初に出会ったからといって結ばれるとは限らないし。それとも、秘密兵器・四葉か?誰が相手になるのか気になる。先が楽しみ。