ステーキハウス蜂 中洲店

アクロス福岡に入っていたステーキハウス蜂が路面店になって幾年。 アクロス福岡よりは天神に近くなって、会社のランチで行きやすくなり嬉しい。 そういえば、路面店になってから、平日にワンプレートランチも始めていたっけ。 いつもハンバーグランチを食べていたけど、ワンプレートランチも試してみることにした。

これが今回お目当の、ワンプレートハンバーグ(890円)。 ハンバーグ本体はジューシーかつ、よく練られて口溶けが良い。 そしてなにより、甘口のデミグラスソースが絶品。 この店のハンバーグが自分のハンバーグランキングで1番。 というか殿堂入り。 子供の頃から唐津本店によく食べに行っていたので、 ハンバーグといったら蜂。 もはやソウルフードと言ってもいいくらいだ。

ハンバーグは相変わらず美味で満足。 ただ絵面を見ると、これで890円は割高に感じてしまう人がいるかもしれない。 それでも自分は食べに来るんだけど。 いつも食べていたハンバーグランチは、さらにコーンポタージュスープと、ハンバーグに付け合わせが付いて、1200円+税。 ワンプレートランチの方が 300 円以上安い。 しかしコーンポタージュスープは捨てがたい。 次回どちらにするか迷うねこれは。

アルスラーン戦記(10)

10巻の主役はクバード。腕っ節だけの人物かと思っていたが、戦略や統率にも優れていたとは。兵士ではない民衆を率いて、ルシタニアの軍隊を撃退するなんて、さすが万騎長は伊達じゃないってことか。厄介事が嫌いみたいだが、アルスラーンはどうやって彼を仲間に引き入れるんだろう。楽しみだ。

銀仮面卿もといヒルメスは、アルスラーンほどに将兵が集まらない現状に憤りつつも、それを打開するために自身のトラウマを乗り越えようとする姿は、敵ながらグッとくるものがあるな。

ASP.NET Core と Entity Framework Core を使った GraphQL API の実装が楽になるかもしれないライブラリ GraphQL.EntityFramework

先日 ASP.NET Core で GraphQL API を実装してみた。

tnakamura.hatenablog.com

データベースから Entity Framework Core を使ってデータを取得する部分は自前で書いたわけだが、なかなか面倒だったのでなんとかしたいところ。

例えば Ruby だと graphql-ruby という gem がメジャーで、こいつは Rails をサポートしているので、 ActiveRecord と連携しやすそうだ。Entity Framework Core でも同様のものがほしい。

探せばあるもので、GraphQL.EntityFramework というパッケージがあった。

www.nuget.org

これを使えば ASP.NET Core + Entity Framework Core で GraphQL を実装するのが少し楽になりそう。先日のサンプルを書き変えてみた。

using GraphiQl;
using GraphQL;
using GraphQL.EntityFramework;
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 Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Net;
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 : EfObjectGraphType<Player>
    {
        public PlayerType(IEfGraphQLService graphQLService)
            : base(graphQLService)
        {
            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 : EfObjectGraphType<Team>
    {
        public TeamType(IEfGraphQLService graphQLService)
            : base(graphQLService)
        {
            Field(x => x.Id);
            Field(x => x.Name);
            Field<ListGraphType<PlayerType>>(
                name: "players",
                resolve: context => context.Source.Players);
        }
    }

    // クエリを定義
    public class HelloGraphQLQuery : EfObjectGraphType
    {
        public HelloGraphQLQuery(IEfGraphQLService graphQLService)
            : base(graphQLService)
        {
            AddQueryConnectionField<TeamType, Team>(
                name: "teams",
                resolve: context =>
                {
                    var dbContext = (ApplicationDbContext)context.UserContext;
                    return dbContext.Teams.Include(t => t.Players);
                });
        }
    }

    // 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; }

        public JObject Variables { get; set; }
    }

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

        readonly ISchema schema;

        readonly ApplicationDbContext dbContext;

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

        [HttpPost]
        public async Task<ExecutionResult> Post([FromBody] GraphQLModel model)
        {
            var result = await documentExecuter.ExecuteAsync(new ExecutionOptions
            {
                Schema = schema,
                OperationName = model.OperationName,
                Query = model.Query,
                Inputs = model.Variables?.ToInputs(),
                UserContext = dbContext,
            });

            if (0 < result.Errors?.Count)
            {
                Response.StatusCode = (int)HttpStatusCode.BadRequest;
            }

            return 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 用
            //--------------------

            EfGraphQLConventions.RegisterConnectionTypesInContainer(services);
            using (var dbContext = services.BuildServiceProvider().GetService<ApplicationDbContext>())
            {
                EfGraphQLConventions.RegisterInContainer(services, dbContext);
            }

            // GraphQL 用のクラスを DI コンテナに登録しておく必要がある。
            services.AddSingleton<HelloGraphQLQuery>();
            services.AddSingleton<TeamType>();
            services.AddSingleton<PlayerType>();
            services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
            services.AddSingleton<ISchema, HelloGraphQLSchema>();
            services.AddSingleton<IDependencyResolver>(
                provider => new FuncDependencyResolver(provider.GetService));
        }

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

            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>();
    }
}

デバッグ実行し Web ブラウザで /graqhiql にアクセス。 全チーム取得するクエリを実行してみると、無事データベースからデータを引っ張ってこれた。

f:id:griefworker:20181115153328p:plain

GraphQL.EntityFramework がフィルタやソートのためのパラメータを自動で生成してくれるから、自分で実装する手間が省けるのは助かる。ただ、それよりも追加で覚えることが多いのと、API が好みでないので、実践投入するかと聞かれたら微妙なところ。

黄金の福ワンタン まくり

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

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

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

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

安全食堂

休日に九大学研都市にある『安全食堂』まで遠征してみた。この店はラーメンウォーカー九州版で福岡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件) を見る