daikiojm’s diary

ブログを書くときがやってきたどん!

Nuxt.jsでEmojiをfaviconに設定する

適当なサイトを作るとき、サイト自体のレイアウトやインタラクションはコンポーネントライブラリやCSSフレームワークがあって開発者一人でも割と迷いなく作業を進められたりする。
最近自分はVueを触ることが多いので、前者だと vuetify、後者だと、tailwindcssを使ったりしている。
折角公開するなら、主要なmetaタグは一通り設定しておきたいと思い、最終的にどうするか迷うのがfaviconだったりする。
何も指定しないよりは、適当なEmojiを指定しておいたほうが見た目が映えるだろうとうのがことの発端。

faviconにはSVGが使える

faviconを用意するとき、適当な.png画像を.ico機械的に変換していたが、Chromeだと80以降ではsvg画像をfaviconとして指定できるらしい。

caniuse.com

faviconにEmojiを指定する

favicon emoji」とかでググると以下のツイートが出てくる。
このツイートがそのまんま答えで、faviconSVGを指定できることを利用してインラインのSVGにEmojiを埋め込むことで、faviconにEmojiを表示させることができるとのこと。

Nuxt.jsのfaviconを固定でEmojiにする

NuxtはSSRの場合、文字列結合で、SPAの場合 vue-meta を使って、head内のmetaタグを構築するようになっている。
どちらにせよ、nuxtConfigのhead内にmetaタグやlinkタグに関する設定を記述してあげれば良い。今回のfaviconの設定も例にもれず、head.link に書けば良い。 この例では、共通の設定になっているが、ページごとに指定したい場合には同様に、pageの head に設定すればよさそう。

create-nuxt-app で作成した直後のプロジェクトでは以下のようになっているはず

const nuxtConfig: NuxtConfig = {
  head: {
    // 略
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  // 略
}

それを、こんな感じで使いたいEmojiを含むSVGをインラインで指定する

const nuxtConfig: NuxtConfig = {
  head: {
    // 略
    link: [
      { rel: 'icon', type: 'image/x-icon', href: 'data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>' }
    ]
  },
  // 略
}

Nuxt.jsのfaviconを動的にEmojiにする

runtimeで切り替えるのもページからmetaの操作がきでれば特に特別なことはない。
以下に例のgistを貼った。この例では、 composition-apiを使っていて、useMetaでmetaの操作をしている。

dynamic emoji favicon + vue + composition-api

忘れていた頃に届いたMoonlander Mark I

久々に新しいキーボードを買った。
とりあえず最低限のセットアップが終わって使い始めたときのツイート。

そんなにキーボードいらなくね?

当時の会社の先輩に即発されて、2年ぐらい前からErgoDox EZを使っている。
もともと肩こりとかはあんまりしない方だったけど、その前に使っていたHHKBに比べて圧倒的に疲れが出なくなった印象がある。

1年ぐらい前からオフィスに出社する機会が減り、そのタイミングで2台持っていたErgoDoxのうち1台は手放してしまっていた。
オフィスに出社して仕事する機会が増えそうだったので、それぞれに置くために追加のErgoDoxを買おうと思ったところ、ErgoDoxのサイトでMoonlanderの広告を見つけ、スパークジョイしてしまった。
親指周りのキー配列以外はErgoDoxEzとそれほど変わらないのと、USB Type-C対応していたのが決め手だった。

Moonlanderが届くまで

ErgoDox EZと同じ Kailh Silver軸で白のモデルを発注した。

  • 8月22日 注文
  • 11月5日 発送
  • 11月12日 受け取り

もともと発送までに時間がかかることはわかっていたけど、9月末ぐらいになって国内で受け取った人のツイートを見るまで注文していたことを忘れていた。
関税は着払いで支払う感じだった。

使ってみて

ErgoDox EZと比較して感じたいこととか。

パームレスト一体型は便利

外出するときにもと出すことは無いけど、机の上で少し移動したときとかにパームレストがあると微妙に位置がずれていたり、机にへばりついていて移動しづらかったり結構ストレスを感じていた部分だった。
パームレストが一体化されたこともあってか、折りたたまれた状態でかなりコンパクトな箱で届いた。ErgoDox EZを買ったときはパームレストと合わせるとバカでかい宅配ピザみたいな箱で届いた記憶がある。

キーを覆う枠がないのはゴミが溜まりにくそう

キーを覆う枠(これがないタイプの名称とかあるのかな?)があるとキーの間に髪の毛やホコリとかのゴミが溜まって、時々ひっくり返して叩きまくるみたいな掃除が必要だった。 Moonlanderの場合枠自体が無いので、ゴミが溜まりにくそう。

(親指部分の赤いキーが)日の丸弁当っぽい

他のキーと同じ色の予備キーキャップを付けてくれたら嬉しかったかもしれない。

焦りポイント

セットアップ中、いくつか焦りポイントがあったので書き残しておく。

バックライトの消し方がわからなくて焦る

ErgoDoxのときはバックライトの有無を注文時に指定できたが、今回のMoonlanderは標準仕様(というか選べるのは色だけ)でもRGBバックライトがついている。
ファームウェアを書き込んだ状態でもこのバックライトが消えず、このまま使い続けなきゃいけないのかとちょっと焦った(ゲーミングキーボード並みのギラギラ)。
焦ってググってみると、以下の回答の通り適当なキーにバックライトのtoggle操作をアサインすればいいだけだった。

https://www.reddit.com/r/ergodox/comments/jnzvv1/help_my_moonlanders_rgb_lights_just_turned_off/

Karabiner-Elementsが動かなくて焦る

USキーのキーボードで、日本語配列と同様に日本語IMEの切り替えを行う&システム全体でviっぽいカーソル操作をするために、Karabiner-Elementsを使ってる。
ErgoDoxのときはときに設定も必要なく、Max本体のキーボードと同様に認識されたが、今回のMoonlanderの場合はそうは行かなかった。

Karabiner-Elements > Preferences に表示されている「Moonlander Mark Ⅰ (ZSA)」の両方にチェックを入れると使えるようになった。(接続した直後は画像2番目のみにチェックが入った状態になる)

f:id:daikiojm:20201114222035p:plain
Karabiner-Elements > Preferences

総評

最高なのでもう一台買ってしまいそう。
前から使っているErgoDox EZのキーアサインをベースに同じ設定にして使っているけど、親指周りのキーや角度の付け方とかが完全に同じとはいかないので、結局全く同じものに揃えた意欲が出てきてしまいそう。
多分、Moonlander Mark2が出たら即買いすると思う。

Nuxt.jsでvuejs-datepickerを使うとdocument is not definedが発生する問題

前提として

Nuxt.js

以下は、Nnux.jsの公式から引用

Nuxtは、モダンな web アプリケーションを作成する Vue.js に基づいたプログレッシブフレームワークです。Vue.js 公式ライブラリ(vue、vue-router や vuex)および強力な開発ツール(webpack、Babel や PostCSS)に基づいています。 Nuxt の目標は、優れた開発者エクスペリエンスを念頭に置き、Web 開発を強力かつ高性能にすることです。

フレームワークによって設計やコーディングルールを矯正される面が大いにあるため、PureなVue.jsの導入を検討する際にも引き合いに出されることが多い印象。
最近開発を手伝っている案件でも、Nuxt.js(TypeScript)が採用されており、ちょくちょく触ることがある。

f:id:daikiojm:20200119214155p:plain
Nuxt.js

vuejs-datepicker

Vue Componentとしてdatepickerを提供するnpmモジュール。
Vue Component提供されるdatepickerの中ではダウンロード数も多く優勢。
個人的にも以前別のプロジェクトで使ったことがあり、シンプルなAPIが使いやすく気に入っていたこともあり、今回もNuxt.jsと組み合わせて導入することにした。
ただし、デフォルトのUIが結構簡素なものなので、カスタムCSSを当てて見た目は自前で作り込んで上げる必要がある。

f:id:daikiojm:20200119214009p:plain
Datepicker Examples

document is not defined

何も考えずvuejs-datepickerのドキュメントに沿って、対象のComponentにdatepickerをimportとして動作確認しようとすると、次のようなエラーが発生する。
どうでもいいが、Nuxt.jsのエラー画面はさすが優れた開発者エクスペリエンスを念頭に置き開発されたアプリケーションフレームワークと歌っているだけのことがある。

f:id:daikiojm:20200119213754p:plain
document is not defined

これは、クライアントサイドJavaScriptで動作することを前提としたモジュールをサーバーサイドレンダリングで動作させようとして、ブラウザオブジェクト(今回の場合documentオブジェクト)にアクセスしようとするも、失敗したことに起因するエラーっぽい。

大抵のVue Componentを提供するnpmモジュールではこの点は考慮されていないので、次で説明するように、Nuxt.jsのpluginを定義したうえで、一手間加えてやる必要がある。

解決方法

現状、上記の問題を解決する方法は次のような手順でdatepickerを使用すればよい。 最初から順を追って説明する。

インストール

yarn add vuejs-datepicker

Nuxt.jsプラグインの定義

/plugins/vue-datepicker.client.js を作成する。

次に、 vue-datepicker.client.js の内容を次のように定義する。

import Vue from 'vue';
import Datepicker from 'vuejs-datepicker';

Vue.component('date-picker', Datepicker);

Nuxt.jsプラグインの定義

コンポーネントで使用する

コンポーネントでdatapickerを使用する際には、次のように <client-only> コンポーネントを利用する。

<client-only>
  <date-picker
    placeholder="MM/DD/YYYY"
    format="MM/dd/yyyy"
    v-model="date"
  />
</client-only>

こうすることで、意図的にクライアントサイドレンダリングでdatapickerを利用するようにすることができる。

API: <client-only> コンポーネント

参考

https://github.com/charliekassel/vuejs-datepicker/issues/147

Slack Appの認証にAPI GatewayのCustom Authorizerを使おうと思ったら使えなかった話

Serverless Frameworkを使うと、API Gateway + Lambdaを使ってCustom Authorizerを簡単に実装することができる。
Slack のSlash Commandsの認証にこれを使おうと思ったけど使えなかった話と、Custom Authorizerを使わないで実装した話。

Slack Appの認証について

最新のSlack API(2019/09/23 現在)では、リクエスト署名方式が使われており、SlackからのPOSTリクエストを受ける側のアプリケーションではこの署名を検証する必要がある。
実際に送信されるリクエストには、 X-Slack-Signature というHTTPヘッダーが付加されていて、この内容が署名になっている。
詳しい検証方法はドキュメントにもあるので触れないが、検証には次の情報が必要になる。

  • APIバージョン番号 (v0)
  • Slack Appの設定ページから取得できる、共有シークレット Signing Secret
  • リクエストbodyの内容
  • リクエスト時のタイムスタンプ ( X-Slack-Request-Timestamp HTTPヘッダーに付加されている)

Custom Authorizerを使おうと思った...

注意: 以下で紹介する方法は動作しません

serverless.yaml

functions:
  hello:
    handler: handler.slashCommand
    events:
      - http:
          method: post
          path: slashCommand
          cors: true
          authorizer:
            name: slashCommandAuthorizer
            identitySource: method.request.header.X-Slack-Signature, method.request.header.X-Slack-Request-Timestamp
            type: request
  slashCommandAuthorizer:
    handler: handler.slashCommandAuthorizer

handler.ts

export async function slashCommandAuthorizer(
  event: APIGatewayEvent & CustomAuthorizerEvent,
  context: Context,
): Promise<CustomAuthorizerResult> {
  const result: CustomAuthorizerResult = {
    principalId: '*',
    policyDocument: {
      Version: '2012-10-17',
      Statement: [
        {
          Action: 'execute-api:Invoke',
          Effect: 'Deny' as Effect,
          Resource: 'arn:aws:execute-api:*:*:*/*/*/',
        },
      ],
    },
  };

  const timestamp = event.headers['x-slack-request-timestamp'];
  const signature = event.headers['x-slack-signature'];
  const body = event.body;

  // verify slack secret.
  const isValidSecret = verifySlackSignature(timestamp, signature, body);

  if (isValidSecret) {
    result.policyDocument.Statement[0].Effect = 'Allow' as Effect;
  } else {
    result.policyDocument.Statement[0].Effect = 'Deny' as Effect;
  }

  return result;
}

serverless.yamlのfunctionsの設定をしているときにドキュメントを見ながら、identitySourceにbodyを設定できないことを知ったあたりから薄々怪しさを感じていたもの、
念の為、 slashCommandAuthorizer が受け取っているevent内容をconsole.logで出力してCloudWatchLog上から覗いてみることに...
しかし、この時点でCustom Authorizerで扱えるのはheaderのみと認識し軌道修正。
どうやら、aws-sdkのインターフェースにも定義されている通り、Custom Authorizerに設定したLambdaには次のようなイベントが渡ってきており、その中にbodyは含まれないようです。

// API Gateway CustomAuthorizer "event"
export interface CustomAuthorizerEvent {
    type: string;
    methodArn: string;
    authorizationToken?: string;
    resource?: string;
    path?: string;
    httpMethod?: string;
    headers?: { [name: string]: string };
    multiValueHeaders?: { [name: string]: string[] };
    pathParameters?: { [name: string]: string } | null;
    queryStringParameters?: { [name: string]: string } | null;
    multiValueQueryStringParameters?: { [name: string]: string[] } | null;
    stageVariables?: { [name: string]: string };
    requestContext?: APIGatewayEventRequestContext;
    domainName?: string;
    apiId?: string;
}

代替案

Custom Authorizerを使わずにそれぞれのhandlerで verifySlackSignature メソッドを呼び出す実装にした。

// handler.ts
export async function slashCommand(event: APIGatewayEvent, context: Context): Promise<any> {
  try {
    // ここで authorizeRequest を直接呼び出す
    authorizeRequest(event);

    // メインのロジック
    // ...

  } catch (e) {
    if (e instanceof AuthError) {
      // 認証エラーのハンドリング
    }

    // サーバーエラーのハンドリング
  }
};

// authorize-request.ts
export function authorizeRequest(event: APIGatewayEvent): void {
  try {
    const timestamp = event.headers['X-Slack-Request-Timestamp'];
    const signature = event.headers['X-Slack-Signature'];
    const body = event.body;

    // verify slack secret.
    const result = verifySlackSignature(timestamp, signature, body);
    if (!result) {
      throw new AuthError('Authentication failed.');
    }
  } catch (e) {
    throw e;
  }
}

まとめ

  • bodyの内容が含まれているタイプの署名の検証にはCustom Authorizerは使えない
  • おとなしくメインのhandlerから署名の検証に必要な情報を取得するのがよい
  • Custom Authorizerが今後対応してくるかは未知(そもそも今回のようなユースケースは想定されていなそう?)

参考

Nest.jsでgRPCサービスのハンドリング

Nest.jsでは microservices パッケージの1機能として、gRPCによる通信をサポートしている。

Nest.jsでgRPCのサーバ実装をされてる方がいて、この記事がすごく参考になった。 https://qiita.com/jnst/items/27b6a0cd3813b34f98e4

microservices パッケージにはクライアント実装のラッパーも含まれてるようなので、今回はこれを試してみる。
Nest.js公式の sampleの実装 をみるとなんとなく雰囲気はわかる。

サーバ

上記で挙げたQiitaの記事のサンプルリポジトリGitHubに公開されていたので、こちらを使わせてもらった。
以下の .proto に定義されたサービスをハンドリングする前提で進める。

https://github.com/jnst/x-nestjs-grpc/blob/master/protos/rpc/rpc.proto

プロジェクトの雛形作成

$ node -v
v10.14.1
$ yarn global add @nestjs/cli

必要なパッケージをインストール

$ yarn add @nestjs/microservices @grpc/proto-loader grpc protobufjs

今回はNest CLIでプロジェクトを作成し、rest moduleというモジュールを定義した。

$ nest new x-nestjs-grpc-client
$ nest g mo rest

main.ts でhttp待受ポートを変更

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // Serverに使うプロジェクトが3000を使っているのでずらした
  await app.listen(3001);
}
bootstrap();

ClientOption

@Client Decoratorに渡すconfigは以下の通り。

import { ClientOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';

const protoDir = join(__dirname, '..', 'protos');
export const grpcClientOptions: ClientOptions = {
  transport: Transport.GRPC,
  options: {
    url: '0.0.0.0:5000',
    package: 'rpc',
    protoPath: '/rpc/rpc.proto',
    loader: {
      keepCase: true,
      longs: Number,
      enums: String,
      defaults: false,
      arrays: true,
      objects: true,
      includeDirs: [protoDir],
    },
  },
};

options.loader 以下のオブジェクトは protoLoader.loadSyncOptions オブジェクト内容として扱われる。

gRPCサーバの機能をREST APIとして提供するコントローラー

今回のサンプルでは、Nest.jsで作成したWebサーバから別のgRPCサーバを呼び出して、REST APIとしてその機能を提供するというユースケースを想定している。

以下は、ControllerからgRPCをハンドリングするサービスの機能を呼び出している部分。 @Client DecoratorでgRPCクライアントを取得して、ライフサイクルフック onModuleInit のタイミングで、実際に使用するサービスを動的に取得している。
(このライフサイクルフックを使わずにConstructor内でサービスの取得を行おうとしたところエラーが発生したので、おとなしく使ったほうが良さそう)

import { Controller, Get, Query, OnModuleInit } from '@nestjs/common';
import { Client, ClientGrpc } from '@nestjs/microservices';
import {
  RpcService,
  Empty,
  GetChampionResponse,
  GetChampionRequest,
  ListChampionsResponse,
  GetBattleFieldResponse,
} from '../../types';
import { grpcClientOptions } from '../grpc-client.options';

@Controller('rest')
export class RestController implements OnModuleInit {
  @Client(grpcClientOptions) private readonly client: ClientGrpc;
  private rpcService: RpcService

  onModuleInit(): void {
    this.rpcService = this.client.getService<RpcService>('Rpc');
  }

  @Get('champion')
  async getChampion(
    @Query() request: GetChampionRequest,
  ): Promise<GetChampionResponse> {
    return this.rpcService.getChampion(request);
  }

  @Get('champions')
  async getChampions(@Query() request: Empty): Promise<ListChampionsResponse> {
    return this.rpcService.listChampions(request);
  }

  @Get('battle_field')
  async getBattleField(
    @Query() request: Empty,
  ): Promise<GetBattleFieldResponse> {
    return this.rpcService.getBattleField(request);
  }
}

実際の利用場面では、gRPCサーバから取得した情報をREST サーバー側で加工してからレスポンスするパターンなどが想定される。
その場合、今回のように直接コントローラーでgRPC Clientを扱うのではなくService層で扱うことになるかと思う。

動作確認

次のように、gRPCサーバーの内容をREST APIとして呼び出せるようになっていることがわかる。

$ curl -s 'localhost:3001/rest/champion?champion_id=1' | jq
{
  "champion": {
    "champion_id": 1,
    "type": 3,
    "name": "Akali",
    "message": "If you look dangerous, you better be dangerous."
  }
}
$ curl -s 'localhost:3001/rest/champions' | jq
{
  "champions": [
    {
      "champion_id": 1,
      "type": "ASSASSIN",
      "name": "Akali",
      "message": "If you look dangerous, you better be dangerous."
    },
    {
      "champion_id": 2,
      "type": "MAGE",
      "name": "Kennen",
      "message": "The Heart of the Tempest beats eternal...and those beaten remember eternally."
    },
    {
      "champion_id": 3,
      "type": "FIGHTER",
      "name": "Tryndamere",
      "message": "Rage is my weapon."
    }
  ]
}
$ curl -s 'localhost:3001/rest/battle_field' | jq
{
  "battle_field": {
    "battle_field_id": 2,
    "name": "The Twisted Treeline",
    "description": ""
  }
}

終わりに

Nest.jsの microservices パッケージ自体、まだ発展途上な感じられる(インターフェースは定義されいるが実装がされていないなど)が、Nest.jsライクな操作(Decoratorでサービスのメソッドを定義するなど)でgRPCのマイクロサービスを構築できるのはかなりのメリットだと思うので、今後も使っていきたい。Server, Clientともにnodeの grpc パッケージのインターフェースを事前に触っていると取っつきやすいように感じる。

リポジトリはこちら
https://github.com/daikiojm/x-nestjs-grpc-client

TypeScriptでgRPCのstreaming RPCを使ったチャットのサンプル

はじめに

Node.jsのgRPCサンプルコードを解説されている方がいたのですが、この記事で触れられているのはUnaryなリクエストのみで、Streem通信に関しては触れられていなかったのでChartを例に試してみた。

tomokazu-kozuma.com

Node.jsは現時点(2018/12/23)でのLTSv10.14.2を使っています。 gRPCの通信方式の違いに関しては、公式ドキュメントの RPC life cycleの項目が参考になります。

Protpcol Buffersの定義

gRPC公式サンプルの中に含まれているstreamを使った例 route_guide.proto を参考にchartアプリケーションを想定した簡単なProtpcol Buffersの定義を作成しました。 https://github.com/grpc/grpc/tree/master/examples/protos

// proto version.
syntax = "proto3";

// package name.
package example;

service Chat {
  rpc join(stream Message) returns (stream Message) {}
  rpc send(Message) returns (Message) {}
}

message Message {
  string user = 1;
  string text = 2;
}

上記.protoファイルのmessageと対応するTypeScriptのInterfaceも作成しました。

// .protoファイルの message定義に合せてtypescriptのinterfaceを用意
export interface Message {
  user: string;
  text: string;
}

Server

今回は、Node.jsの grpc パッケージのインターフェースを一通り眺めながら進めて行きたかったので、protoファイルからコードを自動生成を行うことはしていません。protoLoaderに.protoファイルを渡してサービス定義を読み込みます。
今回のように実行時に動的にコード生成を行う方法を dynamic codegen といい、事前にコード生成(静的にコード生成)をおこなう方法を static codegen と呼ぶようです。

import * as grpc from 'grpc';
import { ServerWriteableStream } from 'grpc';
import * as protoLoader from '@grpc/proto-loader';

import { Message } from './types';

const server = new grpc.Server();
const serverAddress = '0.0.0.0:5001';

// .protoファイルを動的に読み込み
const proto = grpc.loadPackageDefinition(
  protoLoader.loadSync('./chat.proto', {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
  })
);

const users: ServerWriteableStream<Message>[] = [];

function join(call: ServerWriteableStream<Message>): void {
  users.push(call);
  notifyChat({ user: 'Server', text: `new user joined ...` });
}

function send(call: ServerWriteableStream<Message>): void {
  console.log(`send message from ${call.request.user}: ${call.request.text}`);
  notifyChat(call.request);
}

function notifyChat(message: Message): void {
  // すべてのユーザーにメッセージを送信
  users.forEach((user) => {
    user.write(message);
  });
}


// .protoファイルで定義したserviceと上記実装をマッピング
server.addService(proto.example.Chat.service, {
  join: join,
  send: send
});

// Serverのconfig設定 & 起動
server.bind(serverAddress, grpc.ServerCredentials.createInsecure());
server.start();

Client

Client側もServer側と同様に、protoLoaderに.protoファイルを渡してサービス定義を読み込みます。

import * as grpc from 'grpc';
import * as protoLoader from '@grpc/proto-loader';
import * as readline from 'readline';

import { Message } from './types';

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const proto = grpc.loadPackageDefinition(
  protoLoader.loadSync('./chat.proto', {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
  })
);

const remoteServer = '0.0.0.0:5001';

let username = '';

const client = new proto.example.Chat(
  remoteServer,
  grpc.credentials.createInsecure()
)

function startChat(): void {
  let channel = client.join({ user: username });

  channel.on('data', onDataCallback);

  // 標準入力に新しい行が入力されるごとにServerに送信
  rl.addListener('line', (text: string) => {
    client.send({ user: username, text: text }, () => {});
  });
}


function onDataCallback(message: Message): void {
  if (message.user === username) {
    return;
  }

  console.log(`${message.user}: ${message.text}`);
}

rl.question('User name: ', (answer: string) => {
  username = answer;

  startChat();
});

動作確認

今回は、REPLで実行する。

まずはServer側を実行しておく

$ ts-node server.ts

その後、Client側(1)を実行
ユーザー名を入力しEnterを押すと入力待受状態になる

$ ts-node client.ts
User name: daikiojm
Server: new user joined ...

Client(2)も同様に実行

$ ts-node client.ts
User name: test-user
Server: new user joined ...

その後は双方向の通信が確立される

$ ts-node client.ts
User name: test-user
Server: new user joined ...
daikiojm: はらへった
me too

終わりに

gRPCのstreaming RPCはClient - Serverで真意を発揮しそうなので、機会があればgRPC-webでも試してみたい。
従来httpとwsを併用していたような箇所をgRPC1本にまとめつつ型安全を保てるのはかなり実用的な印象がある。
Node.jsでのServer実装の例は少なそうなので、そのうちもう少し大きめのサンプルを作ってみたいかもしれない。

参考

https://tomokazu-kozuma.com/run-grpc-sample-code-with-nodejs/
https://grpc.io/docs/guides/concepts.html
https://github.com/improbable-eng/ts-protoc-gen

フロントエンドエンジニアのAWSとの付き合い方について

この記事は、ex-handslab Advent Calendar 2018 16日目の記事です(大分遅れています)。 私が以前在籍していたハンズラボはAWS関連技術に強みを持った会社だったので、今回はAWS関連の小ネタを書きたいと思います。

adventar.org

対象の読者

ざーっと、書いてみて以下のような方が対象の読者になるのではないかと思っています。

  • AWS以外のクラウドサービスを使って開発を行っているフロントエンドエンジニア
  • これからAWSをやっていく気持ちを持っているフロントエンドエンジニア

※ 普段からAWSを使って開発している人にとっては当たり前の内容ばかりかもしれないです

この記事を書こうと思ったきっかけ

フロントエンドエンジニアと一口に言ってもいろいろなバックエンドを持つ人がいる

ここでは、言葉の定義を深掘りすることはしないが、以下のようにフロントエンドエンジニアと言ってもいろいろなバックエンドを持つ人がいるなぁという印象を受けている。

  • サーバーサイドインジニア
  • Webコーダー
  • デザイナー
  • プログラミングスクールを経てフロントエンドエンジニアとして働き始めたばかりの人
  • などなど

自分自身も、新卒で入社した会社ではWindowsのネイティブアプリケーションの開発/インフラの保守のような仕事を行っていた後、exしたハンズラボでは、AWSのマネージドサービス/アプリケーションサービスを使い、主にフロントエンド仕事をしてきた経緯がある。

フロントエンド/バックエンドともに専門性が増している

フロントエンド(特にSPA)フレームワーク/ライブラリのが普及していることや、ツールチェーン周りのエコシステムの発達などで、フロントエンドエンジニアの備範囲が広がっている印象がある。 一方サーバーサイドインジニアもまた、マイクロサービスアーキテクチャAWSなどのクラウドサービスに依存するアプリケーションサービスのキャッチアップなど、それぞれの領域で求められる専門性が増している印象がある。 専門性と同時に多少性もましている気がしていて、これに関してはすごく共感できる記事を書いている方がいた。

note.mu

今回は、上記を踏まえてAWS上に構築されるWebアプリケーションを開発するフロントエンドエンジニアが最低限知っておきたいAWSサービスに関する内容をざっくりまとめていきたいと思う。

関わりが深そうなAWSサービス一覧

フロントエンドエンジニアと関わりが深そうなAWSサービス一覧とユースケースを並べていく。

S3

Cloud Front

  • Webフロントエンド配信CDNとして
    • エッジキャッシュサーバーを利用してオリジンからのレスポンスをキャッシュする
    • オリジンのアクセス削減
    • エッジサーバーを利用するため地理的に離れた環境でも高速な読み込みを実現
    • 上記で挙げたS3と合わせて使われることが多い

参考

http://aws.clouddesignpattern.org/index.php/CDP:Cache_Distribution%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

Lambda (Lambda@Edge)

  • SSRレンダリングサーバとして使う
    • EC2などの常駐型サーバーではCPU負荷が高く同時にさばけるリクエスト数が少ないといった問題が起きることも
    • APIを提供しviewは持たないWebサーバーと、SPAの構成でSSRを実現する際にLambdaを構成に追加するだけなので導入が手軽
  • Lambda@Edgeでコンテンツヘッダーのカスタマイズ
    • CloudFront の Behaviorに対してLambda@EdgeをトリガーすることでCloudFrontのレスポンスヘッダーを操作することができる
    • セキュリティ関連ヘッダー(CSPなど)の不可やキャッシュコントロールなども行える

参考 https://dev.classmethod.jp/cloud/aws/lambda-edge-design-best-practices/ https://tech-blog.abeja.asia/entry/front-engineer-re-invent

Route53

  • S3 + CloudFrontで配信するコンテンツに独自のDNS名を付ける必要がある際に利用する
    • AWSとの親和性が高いのが特徴だが単純なDNSサービスなのでバックエンドが必ずしもAWSである必要はないので他のクラウドサービスなどと組み合わせることも可能

Cognito

  • フロントエンドアプリケーションとAWSだけで完結する認証/認可
    • AWSから提供されいてるCognitoのSDKをフロントエンドアプリケーションに組み込むことで、アプリケーションの認証/認可を行うことができる

API Gateway

  • Lambdaと組み合わせてWebフロントエンドアプリケーション用のAPIの構築に利用
  • サーバーレスアーキテクチャの文脈でLambdaと組わせて使われることが多い

Code Build

  • フロントエンドアプリケーションのビルド・テスト

circle CIを使ったことがあれば、同じ感覚でyamlを書けばAWS上でCodeのビルド・テスト(デプロイ)が可能 ソースコードホスティングまで含めてAWSで行っている場合、AWSに閉じた環境で一貫したフロントエンドアプリの開発・デプロイができるので便利っぽい

AppSync

  • GraphQLでサーバーレスバックエンドを提供するアプリケーションサービス
    • ネイティブアプリの他にWebフロントエンド向けのSDKを提供しているので、最新のSPAフレームワークにも組み込みやすい

Amplify Console

  • WebフロントエンドアプリケーションのCI/CDを簡単に構築出るサービス
    • 今年のre:Inventで発表されたらしい

参考

https://dev.classmethod.jp/cloud/aws/amplify-console/

フロントエンド周りのAWS構築/運用は誰がやるのか問題

上記で挙げたようなフロントエンドアプリケーションを構築するために利用するAWSサービスは様々。 チームの規模感なんかによっても、変わってくるとはいもうけど、AWS特有のアプリケーションサービスが多いので、インフラの管理を行うチームでは対応しきれずフロントエンドエンジニアがここらへんの面倒を見るパターンも多そうというという印象。 AWSに限らず、ある程度の規模感をもって開発しているチームでフロントエンド配信CDNの運用とかをどうやっているかは単純に興味がある。

最後に

ここまで挙げてきたように、AWSではフロントエンド開発にも欠かせないツールを数多く提供している印象。 上記で挙げたようなアプリケーションサービスはサーバーレスアーキテクチャの文脈で話に出ることも多い。 サーバーサイドの開発経験がないフロントエンドエンジニアでもこのようなAWSサービスを組み合わせることで簡単にWebアプリケーション全体を構築することができたりする。

(とりあえず書いてみた感が否めないのとかなり遅れてしまったけど、書けたのでほっとしている😇)