『アオアシ(14)』を読んだ

福田監督に発破をかけられた冨樫や黒田といったAチームの1年は夜練を始めるわけだが、その姿はBチームの1年にも影響を与え、巻き込んでいく。自分より先を行っているライバルのそんな姿を見せられたら、堪えるし燃えるだろうな。そして、予想だにしなかった福田監督の登場は胸熱だった。

葦人・花・杏里の三角関係も目が離せない状況になってきた。杏里が意外と積極的でグイグイ来る。かと思えば過保護な一面も見せたり。最近のウジウジしている花はイマイチなので、個人的には杏里推しなのだが、三人の関係はどうなることやら。

そして物語は2ヶ月とんで、いよいよプレミアの天王山へ。上位3チームの直接対決に入ると思われる。2ヶ月で葦人たちはどれくらい成長したんだろうな。続きが楽しみで仕方ない。

それにしても青森星蘭の10番、この後姿はどう見ても柴崎がモデルだろ。青森の高校だし。栗林に匹敵する選手だったりするんだろうか。

Cloud Functions as a Frontend for Firestore

Web から Firestore を使うと、アクセスキーが開発ツールから丸見えになってしまうのが不安。いやまぁ、ちゃんとアクセスルールを設定すれば問題ないんだろうけど、自分の Firestore スキルはまだそこまでではない。うっかり設定し忘れもあり得る。

いっそ、Cloud Functions for Firebase で Web API を実装し、その中から Firestore にアクセスすれば、アクセスキーを覗かれずに済むんじゃないか?というわけでやってみた。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as express from 'express';

const serviceAccount: admin.ServiceAccount = {
  "projectId": "FIrebase プロジェクト ID",
  "privateKey": "サービスアカウントに発行したアクセスキーの JSON の privateKey",
  "clientEmail": "サービスアカウントに発行したアクセスキーの JSON の clientEmail",
};

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
});

const db = admin.firestore();

const app = express();

app.get('/items', async (req, res) => {
    const snapshot = await db.collection('items')
        .orderBy('createdAt', 'desc')
        .get();

    const items = [];
    snapshot.forEach((doc) => {
        const item = doc.data();
        items.push(item);
    });

    res.writeHead(200, {
        'Content-Type': 'application/json',
    });
    res.send(JSON.stringify(items));
});

app.get('/items/:id', async (req, res) => {
    const id = req.param('id');
    const doc = await db.collection('items')
        .doc(id)
        .get();
    if (doc.exists) {
        const item = doc.data();

        res.writeHead(200, {
            'Content-Type': 'application/json',
        });
        res.send(JSON.stringify(item));
    } else {
        res.status(404);
    }
});

export const api = functions.https.onRequest(app);

せっかく Firestore 使っているのに、Cloud Functions for Firebase を使って Web API を実装するというのは本末転倒ではあるけど、Firestore を使いこなせるようになるまでの繋ぎってことで。

兼虎 天神店

つけ麺を出す店が福岡に増えてきたとはいえ、その数はまだ少ない。そんな希少なつけ麺屋で一番のお気に入りが『兼虎』。赤坂にあったんだけど、先月天神南に移転したみたいなので行ってきた。

先月からずっと、昼はいつも外に行列ができていた。平日昼は無理かなと半ば諦めていたところ、今回は運よく店内の待ちのみ。5分くらいで席に着けた。食券機で購入したのは、いつも食べている『濃厚つけ麺』。

普通盛から中盛に増やしても無料なのが嬉しい。もちろん中盛。国産小麦の太麺はしっかり締めてありツルツルで、麺だけ最初に味わっても美味いやつ。

この麺を魚粉たっぷりでドロリ高濃度なつけ汁にくぐらせて食べると、もう最高オブ最高。チャーシューも肉厚でトロトロで良い出来だった。やっぱり兼虎のつけ麺が自分の中で今のところ一番だな。

天神に移転してからようやく食べることが叶った。ただ、今回はホント運が良かったので、やはり平日昼は難しいだろうな。どうも席は1階だけみたいだったし。前の店舗より席数減ってないか?アクセスは圧倒的に良くなったので、その点は嬉しい。客足が落ち着いてきたらちょくちょく食べに行きたい。落ち着くことがあるのか怪しいけども。

兼虎

食べログ 兼虎

『渡くんの××が崩壊寸前(6)』を読んだ

恋心を自覚したマキナが攻勢に出るも、今のところどれも空振りに終わっている。危うさがあるキャラしかいない本作において、マキナも例外ではないんだけど、ケジメを付けつつ渡くんのことは諦めないと宣言した態度は好感が持てる。石原さんより読んでて面白い。

石原さんの危うさはあいかわらず。 石原さんが性急な理由は紗月の存在だろうけど、渡くんの腰が引けてるのにもちゃんと理由があったのか。自身の出生にそんな背景があるなら、前向きでないのは仕方ないと思う。結局は流されちゃうんだけどね!

石原さんの母親も登場。最初は絵に描いたような理想的な家族かと思ったが、所々に描写される違和感。やはりまともな登場人物は出てこないのか。予想はしてた。まだ本性を表しておらず、今回はその片鱗だけ。娘を持つ親として気持ちは、まぁ理解できる。次巻ではこの母親がいろいろとちょっかい出してきそう。

『ぼくたちは勉強ができない(8)』を読んだ

複数のヒロインを絡ませた話が増え、新しい面白さの境地を開拓している。今回は、うるかと桐須先生という、今までなかったのが不思議な組み合わせ。桐須先生は元フィギュアスケート選手だったので、アスリート同士、馬が合いそうだとは思っていた。

そもそも、うるかは文乃や理珠と違って、才能を無駄にしようとはしていないから、2人とは前提が違うんだけどね。成幸に教育係をしてもらっているのは、推薦入試に英語が必要だからで、いわば才能を活かすために弱点を克服しようとしている。才能の味方である桐須先生が冷たく当たる理由がない。

そして、8巻のメインは学園祭。6話にもわたる長編シリーズは本作初めてだろう。学園祭のクライマックスで、成幸と結ばれる相手がついに発表!

…シルエットだけというオチだった。とはいえ「全員に触れていました」という方向で濁したわけではないのは好印象。読者にはまだ相手がわからないけど、ヒロインの誰かと結ばれるのはハッキリさせたわけだから。

本命文乃、対抗うるか、大穴桐須先生ってとこだろうか。1話から登場していて、父親エピソードを残している文乃は最有力。うるかはなんだかんだいって、成幸が一番意識している異性に思える。ホント、何度も結ばれるチャンスがあったのにもったいない。グッドルーザーになってしまいそうな予感がする。桐須先生は可能性かなり低いと思うが、物語の結末は読者の力によって変わることはありえる。人気投票1位だったし。成幸の進路は特別VIP推薦希望以外決まってないけど、教育係の経験を生かして教師になりそう。相手が桐須先生というのもアリでは。だいぶ希望が入ってしまってるな。

.NET で Firestore

Firebase を使ってアプリをサクッと作れるようになりたかったので勉強することにした。まずは Firestore。アプリは Xamarin で作るつもりなので、C# から Firestore を触ってみる。NuGet で専用のパッケージが公開されているので、それを使う。まだベータ版だけど、Firestore 自体まだベータ版だし。

www.nuget.org

Firestore で CRUD をやるサンプルを書いてみた。コードはサクッと書けたけど、GCP 外から Firestore にアクセスするために必要な、アクセスキーファイルを入手するのが最初にして最大の関門だった。GCP の 「API とサービス」の「認証情報」でサービスアカウントを作成して、アクセスキーファイルをダウンロードしておかないといけない。

using Google.Cloud.Firestore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace FirestoreSample
{
    class Program
    {
        const string ProjectId = "your-project-id";

        static void Main(string[] args)
        {
            // API とサービスの認証情報でサービスアカウントを作成し、
            // ダウンロードしたアクセスキーファイルのパスを環境変数で指定
            Environment.SetEnvironmentVariable(
                "GOOGLE_APPLICATION_CREDENTIALS",
                Path.Combine(AppContext.BaseDirectory, "your-key.json"));

            MainAsync().GetAwaiter().GetResult();

            Console.WriteLine("Press Enter Key.");
            Console.ReadLine();
        }

        static async Task MainAsync()
        {
            var db = FirestoreDb.Create(ProjectId);

            // books コレクションにドキュメント追加
            var book1Ref = await db.Collection("books")
                .AddAsync(new Dictionary<string, object>()
                {
                    ["title"] = "かぐや様は告らせたい",
                    ["price"] = 500,
                });

            // books コレクションに ID 指定でドキュメント追加
            var book2Ref = db.Collection("books").Document("bokuben");
            await book2Ref.CreateAsync(new Dictionary<string, object>()
            {
                ["title"] = "ぼくたちは勉強ができない",
                ["price"] = 450,
            });

            // books を価格順にソートして取得
            var querySnapshot = await db.Collection("books")
                .OrderBy("price")
                .GetSnapshotAsync();
            foreach (var doc in querySnapshot.Documents)
            {
                Console.WriteLine($"{doc.GetValue<string>("title")}({doc.GetValue<int>("price")}円)");
            }

            // ドキュメントを更新
            await db.Collection("books")
                .Document("bokuben")
                .SetAsync(new Dictionary<string, object>()
                {
                    ["price"] = 420,
                }, SetOptions.MergeAll);

            // ID を指定して 1 件取得
            var documentSnapshot = await db.Collection("books")
                .Document("bokuben")
                .GetSnapshotAsync();
            Console.WriteLine($"{documentSnapshot.GetValue<string>("title")}({documentSnapshot.GetValue<int>("price")}円)");

            // ドキュメントを削除
            await db.Collection("books")
                .Document(book1Ref.Id)
                .DeleteAsync();
            await db.Collection("books")
                .Document("bokuben")
                .DeleteAsync();
        }
    }
}

実行してみた結果は下の通り。

f:id:griefworker:20180904163909p:plain

Xamarin だとロジックの大部分を iOSAndroid で共有できるので、Firestore を単純にデータベースとして使い、ロジックを .NET Stadard ライブラリとして書いて、iOSAndroid から使う戦略が使えそう。

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

WEB+DB PRESS Vol.106』を読み終わったので、毎度の感想メモ。

特集1: [コードで超わかる!]実践Android/iOSアプリ設計

.NET の WPF で生まれた MVVM が、今では Android /iOS の開発で有力なアーキテクチャになるとは感慨深いものがある。Android はデータバインディングを公式にサポートしているのに対し、iOS は RxSwift を使って頑張らないといけないのがツライが。

MVVM に Flux を組み合わせるのはやり過ぎなのではと思うんだけど、採用事例が多いんだろうか。自分が MVVM をやるなら、モデルはドメイン駆動開発風味のレイヤーアーキテクチャでやるだろうな。もしくは、View と ViewController に ReSwift(Redux)。

特集2: [速習]Spring Boot

Java は 10 から型推論が入ったし、ラムダ式も 8 から使えるし、IntelliJ IDEA という強力な IDE もあって、その書き心地はだいぶ Lightweight になったと言っていいと思う。 SpringBoot は Rails の scaffolding ほどの衝撃では無いにせよ、Java での Web 開発でツラかった部分が改善されて、LL のメジャーな Web Application Framework と遜色ない生産性に思えた。

もともと Java のエコシステムの充実っぷりは頭一つ抜きでているし、自分なら Scala で Play Framework よりは Java + SpringBoot を選ぶな。エコシステムどうにかしないと .NET は勝ち目ないよねぇ。

特集3: 仮想DOM革命

仮装 DOM の差分更新によって、ビューを「状態を受け取り HTML/JSX を返す関数」と見なせるようになったのは革命だと思う。 そこからさらに進んで Flux のような単方向データフローのアーキテクチャが生まれたし、フロントエンドの世界に与えた影響は jQuery と同じかそれ以上かもしれない。

その証拠に、仮装 DOM はブラウザを飛び出し、React Native や Flutter でアプリの世界へ。仮装 DOM を今から初めて遅すぎることはなく、React の他に Vue も仮想 DOM を採用していることからも、jQuery 同様長く生きるのは間違いなさそう。

WEB+DB PRESS Vol.106

WEB+DB PRESS Vol.106