データサイエンスの始め方

この記事は 個人Qiita と同じ内容です

qiita.com/sekiyaeiji

プロダクトのグロースにデータを活用したい...

サービスグロースにデータを活用したいと思ったとき、わたしはまず最初にどこに着目し、ナニから始めればいいのだろう?

よくわからなかったので調べ始めてみた。

小さなテーマからはじめよう

データサイエンスは、最初、できるだけコンパクトに小さく始めて、その成果をもとに大きく波及させるのが、うまくいきやすいポイントらしい。

小さなテーマを100個ぐらい仕込んで、大きく化けるテーマを探すらしい。

小さく始めると、失敗しても影響が小さく、軌道修正も容易で、何度でもチャレンジできるメリットがあるとのこと。

...で、ナニをやるんだろう

始め方はわかったけど、小さくナニを始めるのか?

活用目的と、活用ストーリーと、分析ストーリー

このデータ分析は何のためにやるのか、を明確にするのが、活用目的を決めることだが、 データサイエンスの世界では、目的を決めるだけでは不十分らしく、 その目的に対して、分析データの活用ストーリーを明確にする必要がある。

分析結果をただ提示しても利用してもらえない

「分析結果がAの場合、1の施策を、また結果がBの場合は、2の対策を実践する」、のような、 結果と活用方法の対応が分かる情報を、 「活用ストーリー」として分析結果とセットで提示すれば、 分析結果は上手に活用してもらえるらしい

また、分析結果と「活用ストーリー」を事前にしっかり設計することにより、 提供すべき分析結果の精度や、確実に活用してもらえるデータ提供が可能になると思われる

そしてさらに、一元的な分析結果ではなく、いくつかの中間データを経て分析結果を得る場合に、 その各ステップや全体の構造を説明したものを、「分析ストーリー」と呼ぶことができる

多層構造を要する分析においてはこの分析ストーリーを明確にすることが 保守の面においても「活用ストーリー」の設計においても大切になってくる

筋のいいテーマ

以上の3つの要素、

  • 活用目的
  • 活用ストーリー
  • 分析ストーリー

が明確で、さらに

  • 成果が大きい
  • やりやすい

テーマは、"筋のいいテーマ"らしい

"筋のいいテーマ"を見つけることが、 確実かつスピーディーに成果を出せるテーマを選択できるコツ、と言ってよさそうだ

よって、必須3要素である、活用目的、活用ストーリー、分析ストーリーを 上手に設計するトレーニングを重ねることが データ分析上達の鍵な気がする

モデルの選び方

チートシート

分析を設計する際に必要になる統計解析・機械学習モデルについて、
世にはチートシートというサンプルモデルも出回っているらしい

モデルの種類と特徴

採りたいデータごとに分析方法を選択して利用する

  • フィッシュボーンチャート 特性要因図
    • 目的変数と複数の説明変数からなる魚の骨状の図
    • わかりやすく、要素を組み立てやすくてかなり便利
  • 線形判別モデル
  • ロジスティック回帰モデル
  • クラスター分析
  • 主成分分析
    • 似たような傾向を持つデータ項目(変数)を集約する
  • グラフィカルモデリング
    • データ項目間(変数間)の構造を描く

分析の実践に役立ついくつかの手法 メモ

小さく始める際に、ジョハリの窓における「開放の窓」を狙うことで、 現場の感覚とズレのない項目から着手する

選択肢の分岐点となる閾値を算出するために、決定木(ディシジョンツリー)を利用する

ニーズ(needs) より ウォンツ(wants) 変革(change) よりも 改善(improvement) つまり、ウォンツ ✕ 改善 の象限から着手する

痛み よりも 楽になること 全体最適 よりも 個々の部署のメリット(メリットの平準化) つまり、楽になる ✕ メリットの平準化 の象限、 できるだけ多くの関係者がメリットを感じられるテーマから着手する

「実験計画法」(少ない実験で効率的にデータを取得する方法論)で、 データ取得計画を作り、実験しデータを取得し、 取得データから「応答曲面法」で設計変数、品質特性の関係性を数式化し、 その数式を使い「数理計画法」により最適な「設計変数値」を算出する

改善・変革系データサイエンスと、データエコノミー系データサイエンス

日本で成果が出ているデータサイエンスはSQCのような改善・変革系データサイエンス

GAFA系が実現したデータエコノミー系データサイエンスへの発展を目指す場合、 各社がふつうのタスクとしてあたりまえに取り組むことと、 社内で始めて自社外への拡大を実践することで、市場向けのデータサイエンスは可能になる

まとめ

データサイエンスに着手するための基本情報を以上の通りピックアップしてみた

大枠として以下を意識してまずは実績を作成してみるのがよさそうだ

  • 小さなテーマを100個作成して、大きく成長するテーマを探す
  • 活用目的、活用ストーリー、分析ストーリーの3要素で設計する
  • 用途に応じて使えるツールと手法のテンプレートが多く存在する

ネクストアクション

次はこれを読んで情報をまとめる予定

それともう一冊、

データサイエンスにおいて『孫氏』は必読の書、らしいが、

どういうことかよくわからないので、とりあえず読まなければならない

参考図書

本稿では以下の書籍を参考にさせていただきました

Amazon API Gateway エンドポイント(REST API)のカスタムドメイン設定・認可・アクセス制限

この記事は個人ブログと同じ内容です

www.ritolab.com


サーバレスアーキテクチャ構築の第二弾です。

今回は Amazon API Gateway で作成したエンドポイントを使いやすくしたり制限をかけたりしていきます。

開発環境

今回の開発環境は以下になります。

Terraform v1.0.2
なお、今回は前回の続きになるので、操作する Amazon API Gateway については、AWS Lambda / Amazon API Gateway の連携・エンドポイント作成 で作成したものをベースに行っていきます。

API のタイプは REST API です)

カスタムドメインを設定する

デフォルトでは API Gateway で作成されるエンドポイントは以下のようになります。

https://<REST_API_ID>.execute-api.<REGION>.amazonaws.com/<LAMBDA_FUNCTION_NAME>/<RESOURCE_NAME>...

これをカスタムドメインを設定することで以下のように短くする事ができます。

https://<CUSTOM_DOMAIN>/<RESOURCE_NAME>...

以下、カスタムドメインを設定していきます。

ACM 証明書発行

ACM で証明書を発行します。ネイキッドドメインは Route 53 に登録済みの前提です。

main.tf

locals {
  api_gateway_sub_domain_name = "${var.sub_domain_host_name}.${var.domain_name}"
}

# 作成済みホストゾーン情報の取得
data "aws_route53_zone" "main" {
  name = var.domain_name
}

# ACM 証明書作成
resource "aws_acm_certificate" "APIGateway" {
  domain_name       = local.api_gateway_sub_domain_name
  validation_method = "DNS"

  tags = {
    Name = var.domain_name
  }
}

## ACM 検証用 CNAME レコード
resource "aws_route53_record" "api_gateway_acm_c" {
  for_each = {
    for d in aws_acm_certificate.APIGateway.domain_validation_options : d.domain_name => {
      name   = d.resource_record_name
      record = d.resource_record_value
      type   = d.resource_record_type
    }
  }
  zone_id         = data.aws_route53_zone.main.zone_id
  name            = each.value.name
  type            = each.value.type
  records         = [each.value.record]
  ttl             = 60
  allow_overwrite = true
}

## ACM 証明書 / CNAME レコード 連携
resource "aws_acm_certificate_validation" "APIGateway" {
  certificate_arn         = aws_acm_certificate.APIGateway.arn
  validation_record_fqdns = [for record in aws_route53_record.api_gateway_acm_c : record.fqdn]

  depends_on = [
    aws_acm_certificate.APIGateway,
    aws_route53_record.api_gateway_acm_c
  ]
}

API Gateway カスタムドメイン登録

API Gateway でカスタムドメインを適用します。

main.tf

# カスタムドメイン
resource "aws_api_gateway_domain_name" "main" {
  domain_name              = local.api_gateway_sub_domain_name
  regional_certificate_arn = aws_acm_certificate.APIGateway.arn
  security_policy          = "TLS_1_2"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

## API マッピング
### ドメイン名から API ステージへのパスをマッピング
resource "aws_api_gateway_base_path_mapping" "main" {
  api_id      = aws_api_gateway_rest_api.to_lambda_node.id
  stage_name  = aws_api_gateway_stage.hello_world.stage_name
  domain_name = aws_api_gateway_domain_name.main.domain_name
}

# route53 A レコード作成
resource "aws_route53_record" "APIGatewayCustomDomain" {
  name    = aws_api_gateway_domain_name.main.domain_name
  type    = "A"
  zone_id = data.aws_route53_zone.main.id

  alias {
    evaluate_target_health = true
    name                   = aws_api_gateway_domain_name.main.regional_domain_name
    zone_id                = aws_api_gateway_domain_name.main.regional_zone_id
  }
}

カスタムドメイン名を登録したら APIマッピングを行い、カスタムドメイン名を A レコードで登録(ルーティング先に API Gateway ドメイン名を指定)します。

これでエンドポイントがカスタムドメインを用いた URI になります。

https://<CUSTOM_DOMAIN_NAME>/hello_world こんな感じです。

IAM 認証を導入してエンドポイントに認可制限を設ける

前回までの時点ではこのエンドポイントはどこから・誰からでも叩ける状態なので、必要に応じて何らかの制限を掛けてあげる必要があります。

サーバレスという事で今回のアプリケーションを静的サイトと想定した場合、S3 にホスティングして CloudFront で配信の構成が多いと考えます。

その場合 VPC や IP で制限を掛けるといった事が難しいので、IAM 認証を導入して API Gateway で作成したエンドポイントに認可制限を掛けたいと思います。

IAM ユーザー作成

まずはエンドポイントにリクエストする際の IAM ユーザーを作成します。

main.tf

# IAM Role for ApiGateway
## AWS管理ポリシー
data "aws_iam_policy" "AmazonAPIGatewayInvokeFullAccess" {
  arn = "arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess"
}

# IAM User for ApiGateway execution
resource "aws_iam_user" "api_gateway_requester" {
  name = "api_gateway_requester"
}

resource "aws_iam_user_policy_attachment" "api_gateway_requester" {
  user       = aws_iam_user.api_gateway_requester.name
  policy_arn = data.aws_iam_policy.AmazonAPIGatewayInvokeFullAccess.arn
}

今回はテストなのでポリシーは AWS 管理ポリシーである AmazonAPIGatewayInvokeFullAccess を使用しています。

サービスやエンドポイントによって細かくリクエストを制限したい場合は管理ポリシーではなく個別にリソースを指定します。

メソッドの authorization を IAM 認証へ変更

次に、作成したメソッド(GET)の authorization を IAM 認証へ変更します

main.tf

### メソッド設定
resource "aws_api_gateway_method" "hello_world" {
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id   = aws_api_gateway_resource.hello_world.id
  http_method   = "GET"
  authorization = "AWS_IAM" // <-  ここを NONE から AWS_IAM へ変更
}

これまで NONE としていた部分を AWS_IAM へ変更します。

動作確認

AWS 側の設定はこれだけなので、動作確認を行なってみます。

まずはローカルのターミナルから curl コマンドを叩いてみた結果です。

% curl -IX GET  https://<CUSTOM_DOMAIN_NAME>/hello_world
HTTP/2 403
content-type: application/json
content-length: 42
x-amzn-requestid: xxxx-xxx-xxx-xxxx
x-amzn-errortype: MissingAuthenticationTokenException
x-amz-apigw-id: xxxxxxxxxxxxx

認可エラーを示す HTTP ステータスコード 403 が返ってきたので、しっかり制限がかかっている事が確認できます。

一方で、IAM 認証を行なった上でリクエストを行えば、認可で弾かれずにエンドポイントにアクセスする事ができます。

SigV4 - API リクエストに認証情報を追加する

AWS API Gateway で作成したエンドポイントのリクエストで IAM 認証を通すためには、Signature Version 4 (SigV4) を用いて AWS API リクエストに認証情報を追加する必要があります。

docs.aws.amazon.com

docs.aws.amazon.com

ここはアプリケーション側の実装になりますが、JavaScript 及び PHPAWS SDK を用いて実装してみたので参考までに記します。

AWS SDK for JavaScript v2(SigV4 部分を抜粋)

import aws from 'aws-sdk'
import core from  'aws-sdk/lib/core'

-----------------------------------------------

const awsAccessKey = '<AWS_ACCESS_KEY>';
const awsSecretKey = '<AWS_SECRET_KEY>'
const awsRegion  = '<AWS_REGION>';
const awsComponentServiceName    = "execute-api";
const awsAPiGatewayHelloWorldUrl = 'https://<CUSTOM_DOMAIN>/hellow_world';

const splits = awsAPiGatewayHelloWorldUrl.split('?');
const host   = splits[0].substr(8, splits[0].indexOf("/", 8) - 8);
const path   = splits[0].substr(splits[0].indexOf("/", 8));
const query  = splits[1];

const options = {
  url: awsAPiGatewayHelloWorldUrl,
  region: awsRegion,
  method: 'GET',
  headers: {
    host: host,
  },
  pathname: () => path,
  search: () =>  query ? query : '',
};

const signer = new core.Signers.V4(options, awsComponentServiceName);

signer.addAuthorization(new aws.Credentials(awsAccessKey, awsSecretKey), new Date());

const response = await axios.get(awsAPiGatewayHelloWorldUrl, {
  'headers': {
    'authorization': options.headers['Authorization'],
    'x-amz-date': options.headers['X-Amz-Date']
  }
});
// => Hello from Lambda!

こちらについては以下の記事を参考にさせていただきました

dev.classmethod.jp

API Gateway で生成された JavaScript SDK を使用

var apigClient = apigClientFactory.newClient({
    accessKey: awsAccessKey,
    secretKey: awsSecretKey,
    region: awsRegion
});

apigClient.helloWorldGet()
            .then(function(result) {
                console.log(result.data);
                // => Hello from Lambda!
            })
            .catch(function(result) {
                console.log(result);
            });

docs.aws.amazon.com

AWS SDK for PHP v3

/** @var \GuzzleHttp\Client $client */
$client = new Client();

/** @var \GuzzleHttp\Psr7\Request $request */
$request = new Request('GET', 'https://<CUSTOM_DOMAIN>/hellow_world');

/** @var \Aws\Credentials\Credentials $credentials */
$credentials = new Credentials('<AWS_ACCESS_KEY>', '<AWS_SECRET_KEY>');

/** @var \Aws\Signature\SignatureV4 $signer */
$signer = new SignatureV4('execute-api', 'ap-northeast-1');

// クレデンシャルを使用し必要なヘッダーをリクエストに追加することで指定されたリクエストに SigV4 で署名します。
$requestWithSign = $signer->signRequest($request, $credentials);

// エンドポイントへ HTTP リクエストを送信
$response = $client->send($requestWithSign);

$response->getBody()->getContents()
// -> "Hello from Lambda!"

指定している AWSACCESS_KEY と SECRET_KEY は、API Gateway へのリクエスタとして(このセクションの冒頭で)作成した IAM ユーザーのものになります。

IP Address でアクセス制限を掛ける

API Gateway のエンドポイントに対して IP 制限をかけたい場合は、リソースポリシーを用いることで実現できます。

docs.aws.amazon.com

main.tf

resource "aws_api_gateway_rest_api_policy" "to_lambda_node" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  policy = jsonencode({
    Version : "2012-10-17",
    Statement : [
      {
        Effect : "Allow",
        Principal : "*",
        Action : "execute-api:Invoke",
        Resource : "arn:aws:execute-api:${var.aws_region}:${var.aws_id}:${aws_api_gateway_rest_api.to_lambda_node.id}/*",
        Condition : {
          // IP アドレス制限
          "IpAddress": {
            "aws:SourceIp": "xx.xxx.xx.xxx/32"
          }
        }
      }
    ]
  })
}

CORS の有効化を行う

作成したリソースの CORS を有効化するのなら、terraform モジュール api-gateway-enable-cors が簡単でいい感じに使えます。

OPTIONS メソッドを追加してクロスオリジンリソースシェアリング(CORS)プリフライトリクエストを許可する Terraform モジュールです。

main.tf

## CORS 設定
module "cors" {
  source  = "squidfunk/api-gateway-enable-cors/aws"
  version = "0.3.3"

  api_id          = aws_api_gateway_rest_api.to_lambda_node.id
  api_resource_id = aws_api_gateway_resource.hello_world.id

  allow_headers = ["Content-Type", "authorization", "x-amz-date"]
  allow_methods = ["OPTIONS", "GET"]
  allow_origin  = "*"
}

必要な項目を設定すると、OPTIONS メソッドが作成され CORSプリフライトリクエストが可能になります。

今回はここまでになります。Amazon API Gateway で作成したエンドポイントの方はあらかた使いやすくなったので、次回は DynamoDB を絡めてデータ操作周りをやっていこうと思います。

image-magick-lambda-layer を使ってオリジナル LGTM 画像を作ろう

この記事は個人ブログと同じ内容です

image-magick-lambda-layer を使ってオリジナル LGTM 画像を作ろう


LGTM画像といえば、LGTMoon、いつもお世話になっています。 でも、たまにはオリジナリティ出したいですよね。

そこで、今回は5秒でオリジナルLGTM画像を生成するサービスを作成します。

準備するもの

lambda layer とは

Lambdaレイヤーは、追加のコードまたはデータを含むことができる .zip ファイルアーカイブです。レイヤーには、ライブラリ、 カスタムランタイム 、データ、または設定ファイルを含めることができます。レイヤーを使用すると、コードの共有と責任の分離を促進し、ビジネスロジックの記述をより迅速に繰り返すことができます。

素の lambda にライブラリを追加する方法。layer に分けることで処理速度を上げることができる。今回はAWSで用意されているレイヤーを使用するが、もちろん独自のレイヤーを作成することもできる。関数に追加できるLayerは5つまでで、合計サイズが250MB以下となる必要がある。

lambdaファンクションを作成する

lambda > 関数 に行き、「関数の作成」をします。 関数名は適当に lambda-image-magick(任意)、ランタイムは Node.js 14.x とします。

API Gateway、POST メソッドを作成する

これから作成する API Gateway の POST エンドポイントに画像のURLをリクエストし、 lambda で受け取れるようにしていきます。

{
  url: '画像のアドレス'
}

API Gateway から REST API を作成、アクション > メソッドの作成 から POST を作成。先程作成した lambda 関数名を指定します。

CORS を有効化する

「CORSを有効にして既存のCORSヘッダーを置換」をクリックし、「はい、既存の値を置き換えます」をクリックするとOPTIONSメソッドが作成されます。

image-magick-lambda-layer をデプロイする

image-magick-lambda-layer にアクセスしてデプロイをクリックします。

デプロイしただけでは使用できないので有効化します。

レイヤー > レイヤーを追加。 AWSレイヤーに出てこなかったため、ARN を指定してレイヤーを追加しました。

コードを用意

ImageMagick, GraphicsMagick を node.js で扱えるようにする gm を入れます。axios は画像ダウンロードで使用。

$ yarn init -y
$ yarn add axios gm 

ローカルで動作を確認する場合はインストールをお忘れなく。

$ brew install imagemagick
$ brew install graphicsmagick

重ねる lgtm.png を用意します。

LGTM画像は以下のように、適当に数パターンサイズを用意。 (当初は送られてきた画像のサイズに合わせてリサイズを考えましたが、lambda上に一時的に画像を保存しておくことができないため断念。)

├── assets
│   ├── lgtm.png
│   ├── lgtm100.png
│   ├── lgtm150.png
│   ├── lgtm200.png
│   ├── lgtm250.png
│   ├── lgtm300.png
│   ├── lgtm500.png
│   └── lgtm700.png
├── index.js
├── node_modules
├── package.json
└── utils.js

lambda 関数を作成

  1. 画像パスを受け取り、画像をダウンロード
  2. 画像とあらかじめ用意している lgtm.png と合成
  3. 合成したバッファをリサイズ(出力サイズは width 300px とした)
  4. response 返却
// index.js
const { downloadImage, composite, resize } = require('./utils');

exports.handler = async (event) => {
  try {
    const buf = await downloadImage(event.url); // 1
    const composited = await composite(buf); // 2
    const resized = await resize(composited, 300); // 3
    const base64 = 'data:image/png;base64,' + Buffer.from(resized).toString('base64');
    const response = {
      statusCode: 200,
      headers: {
          'Content-Type': 'image/png',
          'Access-Control-Allow-Origin': '*',
      },
      body: base64,
      isBase64Encoded: false,
    };
    return response; // 4
  } catch(e) {
    console.error(e);
  }
};

処理の詳細はこちら

// utils.js
const GM = require('gm');
const gm = GM.subClass({ imageMagick: true });
const axios = require('axios');

exports.downloadImage = async (url) => {
  const res = await axios.get(url, { responseType: 'arraybuffer' });
  return Buffer.from(res.data);
}

const getBufferSize = (buf) => {
  return new Promise((resolve, reject) => {
    gm(buf)
      .size((err, { width, height }) =>
        err ? reject(err) : resolve({ width, height }))
  })
}

const getLgtmPng = (width) => {
  if (width >= 1000) {
    return './assets/lgtm700.png'
  } else if (width < 1000 && width >= 800) {
    return './assets/lgtm500.png'
  } else if (width < 800 && width >= 500) {
    return './assets/lgtm300.png'
  } else if (width < 500 && width >= 300) {
    return './assets/lgtm150.png'
  } else if (width < 300) {
    return './assets/lgtm100.png'
  }
}

const getPosition = (lgtmPath, width, height) => {
  return new Promise((resolve, reject) => {
    gm(lgtmPath).size((err, size) => {
      const centerWidth = width / 2;
      const centerHeight = height / 2;
      const left = Math.floor(centerWidth - (size.width / 2));
      const top = Math.floor(centerHeight - (size.height / 2));
      const geometry = '+' + left + '+' + top;
      return err ? reject(err) : resolve(geometry)
    });
  });
}

const getCompositedBuffer = (buf, lgtmPath, geometry) => {
  return new Promise((resolve, reject) => {
    return gm(buf)
      .composite(lgtmPath)
      .geometry(geometry)
      .quality(100)
      .noProfile()
      .toBuffer((err, buffer) =>
         err ? reject(err) : resolve(buffer));
  })
}

exports.composite = async (buf) => {
  try {
    const { width, height } = await getBufferSize(buf) 
    const lgtmPath = getLgtmPng(width)
    const geometry = await getPosition(lgtmPath, width, height);
    return await getCompositedBuffer(buf, lgtmPath, geometry);
  } catch(e) {
    throw e;
  }
}

exports.resize = async (buf, width, height) => {
  return new Promise((resolve, reject) => {
    gm(buf)
      .resize(width, height)
      .noProfile()
      .toBuffer('PNG', (err, buffer) =>
        err ? reject(err) : resolve(buffer));
  });
};

デプロイ

コードができたら zip にします。

$ zip -r deploy.zip ./

lambda の画面から .zip ファイルをアップロード。

完成 🎉

ブラウザの任意の jpegpng の画像のURLをエンドポイントにリクエストする bookmarklet を作成する。

Next.js にレイアウトに関するドキュメントが追加されました

この記事は個人ブログと同じ内容です

zenn.dev

 

 

Next.js の Layout 周りのドキュメントが新しく追加されました。

単一のレイアウトを扱う場合

単一のレイアウトで十分な場合はカスタマイズした <Layout/><Component /> タグを囲むだけで実装できます。

// pages/_app.js

import Layout from '../components/layout'

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      // Layout でアプリケーションのコンポーネントを囲むだけ
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </>
  )
}

ページごとに異なるレイアウトを扱う場合

ページごとに異なるレイアウトが必要な場合は getLayout でページごとにレイアウトを定義します。 getLayout を使用してレイアウトの切り替えを行うことで、コンポーネントツリーがページ遷移間で維持されるため永続的にレイアウト内の state を保持することができます。 コンポーネントツリーが維持されると React は state を維持しつつ、必要な要素のみ再レンダリングします。

// pages/index.js

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return {
    /** Your content */
  }
}
// 個別のページごとにレイアウトを定義する
Page.getLayout = (page) => (
  <Layout>
    <NestedLayout>{page}</NestedLayout>
  </Layout>
) 
// pages/_app.js

export default function MyApp({ Component, pageProps }) {
  // ページごとに定義されたレイアウトがある場合はそれを使用する
  const getLayout = Component.getLayout || ((page) => page)

  return getLayout(<Component {...pageProps} />)
}

データフェッチ

Layout 内でのデータフェッチはクライアント側でのみ可能です。 Layout はページではないため、今のところ getStaticPropsgetServerSideProps は使えません。

プレビュー

Next.js 公式リポジトリのサンプルがわかりやすいのでぜひ触ってみてください。

Open in StackBlitz

ROXXに転職して2ヶ月が経ったエンジニアから見た開発チームのあれこれ

2021年6月に開発チームにjoinしたpoisonと申します。これからよろしくお願いいたします。

今回、開発チームのタスク内で自分の価値を発信しよう!という動きがあったので、ブログを書くことに決めました。

はじめに

私はSESで各現場にアサインされJavaScriptFWでよく使われているAngular・Reactを使用したWebアプリケーション製造に携わっておりました。

そしてこの度、Vueを扱っている弊社にjoinすることができ3大制覇したことをきっかけに、その扱い方の違いについて書こうと思いました。

ただ、2ヶ月経った今、開発チームの雰囲気や1週間の流れを入ったばかりのエンジニア視点で書けるのは今しかないと思い、今回はこちらを題材にしました。

(3大制覇に関してのブログは後日書けたら書きます…) 

私のスペック

2017年新卒、ROXXで3社目(1~2社目はSESで現場常駐)

主に扱ってきた技術は、JavaJavaScript(Angular,React)・Redux等

もっと自分の価値市場を上げたい、事業会社に入ってプロダクトを育てる立場になりたいという思いから2020年12月に転職活動を開始し、翌年3月にROXXから内定をもらい今に至る

開発チームの実情

ROXXには「agent bank」「back check」の2つのサービスがあり、私が所属しているのはagent bankのDevチームです。

今は、フルリモートで働いており、業務中は常にDiscordというアプリを使用して、常時通話状態で作業している状況です。

常時通話状態なので、リモートなのに出社している緊張感を感じることができ、家にいるけど職場の雰囲気を感じながら仕事しております。

基本はスクラムチームとして動き、1週間スプリントでタスクをこなし毎週金曜日にスプリントレビュー・振り返り・リファイメント・プランニングを実施しているという流れになってます。

個人的には、この流れがすごく好きで、1週間できちんと終えて、また次の週から始められることでスッキリ週を終えることができます。

チームの人数は執筆時点で12人ほどいて、プロジェクトが複数動いていることから半分ずつチームが分かれ、日々のタスク消化をしている人数は5~6名で回しています。そこから、基本ペアプロを行っているため、2~3レーンで進めてます。

毎日、同じメンバーとペアプロするのではなく、日々シャッフルしながら実施しているためメンバー内で話したことがないというのはないです!日々、和気藹々とやっている反面、真面目に議論するときもあるので個人的にはいい雰囲気で過ごしています。

いろんな現場を見てきた身からすると、技術が強いメンバーもいるため、わからないこととかは気軽に聞くことができる環境となっており、個人的にはとても助かっております。 

おすすめの活動

私がチーム活動の中でおすすめする部分として2点あります。

1つ目は、毎日の振り返りです。

私たちのチームでは、月曜〜木曜までは、朝会・昼会・夕会を実施しており、メンバーの進捗を随時確認してます。

夕会では、「やったこと」「おきたこと」「わかったこと」の頭文字をとりYOWというFWを使用して取り組んでおります。

その日のうちに、実際やったこと・それをしたことで起きたこと・その中で分かったことを振り返りし、週末の振り返りの際にはそれを見返すことで忘れず、1週間の流れを確認することができます。

その後、何がよかったのか・問題点はなんだったのか・今後どうアクションしていくかを決めスプリントを閉じるという流れで実施しております(俗にいうKPTですね)。

デイリーの中でメンバーそれぞれが出し合ったものを確認すると、分かってなかったことその日に起きたことなどを知る機会が増えるので、いろいろと話し合ったりしてお互いに知識を深め合う場としても活用しております。

2つ目は、リファイメントについてです。

スクラムチームの活動は、転職前にも経験していたのですが、その時は、タスクの見積もりをするだけの活動となっていました。

私たちのチームでは、タスクの見積もりはプランニングで行い、リファイメントではPOが何個かの改善点をBizの方々から抽出し、チームに落とし込んで、何をどうすればその状況が解決するかを全員で考える場として使ってます。

これをすることで、受動的ではなく能動的にタスクと向き合えることができる気がしております。

それだけでは終わらず、リファイメントした後に設計タスクと題して、DB構造であったりどんな機能が必要かであったりを複数人で考えるタスクがあり、コーディングだけでなく設計能力も高めることができる場が設けられております。

コーディングだけを主に行ってきた身としてはとても新鮮で、毎度関わることができているので、どんどん価値を出していきたいと思っております!

私が思うチームの課題点

新参者がいきなりチームの課題を出すというものも、大変烏滸がましいと思いつつまだまだここは足りないかもしれないと思う点があります。

それは、属人化についてです。

主に、インフラに関してですが、作業できるレベルの方が少数しかおらず、その方が休んだ暁には、危機的状況になっているのが現状です。

この件に関しては、チームだけでなく開発事業部全体で考えており、属人化を回避するために、2つのサービスの開発チーム合同で勉強会を行ったりしております。そういった行動を積み重ねて、各人が作業レベルで触れるようになるために、まずは基礎知識をつけてその上でわかる方とペアプロする形で学んでいくような体制を作ることができればベストかと思っております。

私もまだまだインフラに関してはど素人なので、とりあえず環境を作ってみようということで、今後触っていこうと思っております。

今後の目標

今後の目標としては、今までは主にフロントエンドしか触ってなく、サーバーエンドやインフラに関してもどんどん積極的にタスクをこなして、自分のものにしたいと考えております。

それを実施することができる、チームにはとても感謝しております。今後も自分の価値を発揮できるように精進していきたいと思います。

終わりに

この記事を読んで、もしROXXや開発チームに興味が出てきた方がいましたら、ぜひご応募ください!お待ちしております!

 

careers.roxx.co.jp

AWS Lambda / Amazon API Gateway の連携・エンドポイント作成(REST API)

この記事は個人ブログと同じ内容です

www.ritolab.com


サーバレスアーキテクチャ構築の第一弾です。

今回は AWS の Lambda と API Gateway を連携させて、エンドポイントを作成していきます。

サーバーレス・コンピューティング

自身でサーバーを運用せずに IaaS 等のサービスを利用して処理のロジックだけをデプロイ。必要な時にだけホストした処理を動作させる一連の仕組み。

例えば、いくつかの処理を行う API サーバを持っているとして、そのサーバは 24 時間 365 日稼働させる必要がある。

サーバレスにすれば処理のロジックをリモートにホストしておき、必要な時にだけ動作させる事ができて、自身でサーバーを運用する手間が省ける。(サーバーに関するセキュリティや可用性をその分気にしなくて良くなる)

さらには動作した時間だけの課金になるので、24 時間 365 日サーバーを稼働させるよりも(大抵は)費用が抑えられる。

サーバーレス・コンピューティングを提供している主な IaaS

余談ですが AWS では、Well-Architected Framework という指針(AWS を用いた設計のベストプラクティスなど)を出していて、その中の「パフォーマンス効率」の章でサーバーレスアーキテクチャを推奨していたりもします。

docs.aws.amazon.com

AWS Well-Architected Framework

wa.aws.amazon.com

AWS Lambda

AWS Lambda はサーバーレスコンピューティングサービスで、サーバーのプロビジョニングや管理、ワークロード対応のクラスタースケーリングロジックの作成、イベント統合の維持、ランタイムの管理を行わずにコードを実行できます。

引用元:AWS Lambda(イベント発生時にコードを実行)| AWS

AWS でサーバレスやるぞってなったらこいつですね。

aws.amazon.com

Amazon API Gateway

フルマネージド型サービスの Amazon API Gateway を利用すれば、デベロッパーは規模にかかわらず簡単に API の作成、公開、保守、モニタリング、保護を行えます。API は、アプリケーションがバックエンドサービスからのデータ、ビジネスロジック、機能にアクセスするための「フロントドア」として機能します。

引用元:Amazon API Gateway(規模に応じた API の作成、維持、保護)| AWS

こちらはサーバレス云々というより、API を作成できるサービスです。今回は API Gateway でステートレス API を作成して、アプリケーションからそのエンドポイントを叩こうと思います。

API Gateway で作成したエンドポイントにリクエストしたら、Lambda の処理が動く。という流れです。

aws.amazon.com

開発環境

今回の開発環境は以下になります。

  • Terraform v1.0.2

ちなみに今回、AWS Lambda は Node でやります。

ホストするコードは JavaScript になるわけですが、今回はこれも terraform で管理します。

実運用だと別々での管理が望ましいと考えますが、それよりもとにかく動かしてみたい欲が先行したので今回はその辺は割愛します。

それと、tf ファイル作成していく中で functions へのパスを書いているのでディレクトリ構成を事前共有しておきます。

.
├── functions
├── src
└── terraform
  • functions(Lambda にホストするコード)
  • src(アプリケーション)

Lambda にホストするコード

まずは、AWS Lambda にホストするコードを作成しておきます。

functions/tf-test-node-hello-world/index.js

exports.handler = async (event) => {
    return {
        isBase64Encoded: false,
        statusCode: 200,
        headers: {},
        body: JSON.stringify('Hello from Lambda!'),
    };
};

「Hello from Lambda!」を返すだけの関数です。

また、API Gateway が Lambda からレスポンスを受け取る場合は形式が決まっているため、そちらに倣った形式にしています。

docs.aws.amazon.com

Lambda の構築

Lambda を構築していきます。

CloudWatch

Lambda の実行ログを CloudWatch に流すのでロググループとロールを作成します。

AWS Lambda の Amazon CloudWatch ログへのアクセス

docs.aws.amazon.com

main.tf

variable "function_name" {
  type    = string
  default = "tf-test-node-hello-world"
}

# CloudWatch Logs for lambda
resource "aws_cloudwatch_log_group" "node_lambda_hello_world" {
  name = "/aws/lambda/${var.function_name}"
}

# Lambda Role for logging CloudWatchLogs
resource "aws_iam_role" "lambda_node_logging" {
  name = "${var.app_name}-lambda-role"

  assume_role_policy = jsonencode({
    Version : "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "lambda.amazonaws.com"
        },
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_policy" "lambda_node_logging" {
  name        = "${var.app_name}-lambda-policy"
  description = "IAM policy for logging from a lambda"

  policy = jsonencode({
    Version : "2012-10-17",
    Statement = [
      {
        Action   = "logs:CreateLogGroup",
        Effect   = "Allow",
        Resource = "arn:aws:logs:${var.aws_region}:${var.aws_id}:*"
      },
      {
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ],
        Effect = "Allow",
        Resource = [
          "${aws_cloudwatch_log_group.node_lambda_hello_world.arn}:*"
        ]
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_node_logging" {
  policy_arn = aws_iam_policy.lambda_node_logging.arn
  role       = aws_iam_role.lambda_node_logging.name
}

Lambda へのデプロイ

今回は terraform で Lambda にデプロイするコードも管理するので、ローカルで ZIP を作成するようにしておきます。

main.tf

data "archive_file" "lambda_function" {
  type        = "zip"
  source_dir  = "../functions/tf-test-node-hello-world"
  output_path = "../functions/upload/tf-test-node-hello-world.zip"
}

これで terraform plan ないし terraform apply 時に ZIP が作成されます。

Lambda function の作成

最後に、メインである関数を定義します。

main.tf

resource "aws_lambda_function" "hello_world" {
  filename      = data.archive_file.lambda_function.output_path
  function_name = var.function_name
  role          = aws_iam_role.lambda_node_logging.arn

  handler = "index.handler"

  source_code_hash = filebase64sha256(data.archive_file.lambda_function.output_path)

  runtime = "nodejs14.x"

  depends_on = [
    aws_iam_role_policy_attachment.lambda_node_logging,
    aws_cloudwatch_log_group.node_lambda_hello_world
  ]
}

docs.aws.amazon.com

docs.aws.amazon.com

Lambda 構築実行

ここまで書いて terraform apply を実行すると、Lambda に関数が出来上がります。

f:id:ro9rito:20210720113408p:plain

API Gateway の構築

次に、作成した Lambda 関数を実行するためのインターフェースを Amazon API Gateway を使って作成していきます。

API 作成

API を作成します。インターフェースの大本を作成するイメージ。

API タイプは用途によって最適なものを選択する必要があります。今回は REST API で作成します。

docs.aws.amazon.com

main.tf

# API Gateway
resource "aws_api_gateway_rest_api" "to_lambda_node" {
  name        = "${var.app_name}-to-lambda-node-api"
  description = "REST API for lambda node test"
}

リソース作成

/hello_world のリソースを作成します。

main.tf

## for Function: hello world
### リソース作成
resource "aws_api_gateway_resource" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  parent_id   = aws_api_gateway_rest_api.to_lambda_node.root_resource_id
  path_part   = "hello_world"
}

メソッド作成

メソッドはリソースに対して GET や POST などの HTTP リクエストメソッドを作成します。ここでは GET で作成します。

main.tf

### メソッド設定
resource "aws_api_gateway_method" "hello_world" {
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id   = aws_api_gateway_resource.hello_world.id
  http_method   = "GET"
  authorization = "NONE"
}

lambda 統合を設定

今回は API Gateway で作成したエンドポイントがリクエストを受け取ったら Lambda function を実行するので、API Gateway と Lambda を紐付けます。

API Gateway では Lambda との連携がスムーズにできるようにこういった設定も用意されているので紐付けが簡単に行えます。

main.tf

### lambda 統合
resource "aws_api_gateway_integration" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id = aws_api_gateway_resource.hello_world.id
  http_method = aws_api_gateway_method.hello_world.http_method

  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.hello_world.invoke_arn
}

ポイントとしては、integration_http_method を POST に指定している点です。

lambda への統合 method は POST で固定となっています。

aws.amazon.com

デプロイメント作成

デプロイメントを作成します。デプロイメントは、REST API 構成のスナップショットのイメージ。作成したデプロイメントを次の、ステージと紐付ける事で API が公開されます。

main.tf

### デプロイメント
resource "aws_api_gateway_deployment" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id

  depends_on = [
    aws_api_gateway_integration.hello_world
  ]
}

再度デプロイメントを行う場合の注意点

新しいリソースやメソッド作成、もしくは既存のリソースやメソッドの変更を terraform から反映する場合は、再度デプロイメントを作成する必要があります。

単純にリソースやメソッドをコード上で追加して反映しても、リソース上で追加されるだけでそれはデプロイされないからです。

そしてデプロイメントは、リソースやメソッドを追加・変更しても apply 時に変更対象とはなりません。(既に作成済みのため)

そのため AWS コンソールから手動でデプロイしてあげる必要があります。

リソースやメソッドの新規作成や変更を検知する、もしくは毎回の apply 時にデプロイメントの作成を強制する仕組みも作れますが、terraform 上での反映でデプロイメントを作成する場合は現在のものを削除した上で作り直す必要があるため、カスタムドメイン名を設定していない場合はエンドポイントのサブドメイン名が変更になってしまうので注意が必要です。

resource "aws_api_gateway_deployment" "hello_world" {
  .
  .
  .
  lifecycle {
    # 変更に対応するため一度削除して作り直す
    # REST API ID の変更によりエンドポイントの URI が変わるので注意
    create_before_destroy = true
  }
}

また、上記の設定を行わなずにデプロイメントの変更を apply した場合は以下のエラーが出力されます。

Error: error deleting API Gateway Deployment (xxxxx): BadRequestException: Active stages pointing to this deployment must be moved or deleted

どうしても仕組みが必要な場合以外はここは記述しないか false にして手動で API デプロイを行うのが良いと思います。(今回は特段必要としていないので lifecycle については記述しない(=false)で進めます。)

ステージ作成

ステージを作成します。ここでデプロイメントを参照して作成する事で API が公開されます。

main.tf

### ステージ
resource "aws_api_gateway_stage" "hello_world" {
  deployment_id = aws_api_gateway_deployment.hello_world.id
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  stage_name    = var.app_name
}

API Gateway 側の設定はこれで終わりです。

Lambda へのアクセスを制限する

最後に Lambda 関数(hello_world)へのアクセス許可を API Gateway に付与します。

main.tf

resource "aws_lambda_permission" "execution_hello_world_for_api_gateway" {
  statement_id  = "test-AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.hello_world.function_name
  principal     = "apigateway.amazonaws.com"

  source_arn = "${aws_api_gateway_rest_api.to_lambda_node.execution_arn}/*/*"
}

source_arn を本 API のみにしたので、今回作成したエンドポイントからでしかこの Lambda 関数は実行できないようになっています。

API Gateway 構築実行

ここまで書いて terraform apply を実行すると、API Gateway が作成され、Lambda 関数にも関連付いた事が確認できます。

f:id:ro9rito:20210720120201p:plain

動作確認

一通りミニマムで作成したので、アプリケーションからエンドポイントを叩いてみます。

f:id:ro9rito:20210720120235p:plain

正常にレスポンスが返ってきました。これで API Gateway 経由で Lambda 関数を実行できました。

今回はここまでになります。作成したエンドポイントはデフォルトの URL であったり制限がかかっていなかったりするので、次回はエンドポイントの設定や制御を行っていこうと思います。

【読書メモ】「成長する企業はなぜ SSO を導入するのか」

この記事は個人ブログと同じ内容です

【読書メモ】「成長する企業はなぜ SSO を導入するのか」

---

最近チームのメンバーが誕生日で、よくバラエティ番組で流れる曲「Happy Birthday - Stevie Wonder」が頭から離れない sekitats です。

今月は個人的に認証について学ぶ月間で、本を3冊読みました。認証周りわからんことばかりだったので。

OAuth については、別記事を書いてみたのでそちらも興味があれば読んでみてください。

ratatatat30.hatenablog.jp

2冊目の Auth0 + Nuxt + Rails のデモも試した。

本記事は3冊目「成長する企業はなぜ SSO を導入するのか」の要約となります。
引用元:日本ヒューレット・パッカード株式会社 (著) . 『成長する企業はなぜSSOを導入するのか 』. 日経BP, 2017

なぜ、この本を読んだのかというと、SSO に関する本を amazon で検索したら、本書しかなかったからです。(あとはミリタリーバックパック

認証はすでに経営課題

以下のような企業における認証の課題があげられている。

  • 社員のパスワードの使い回しによってパスワード盗難リスクが高まる。企業側の危機意識が低い
  • 厳しすぎるパスワードポリシーが返って生産性を低下させる
  • 個々の業務システムの担当者は、自分が担当している業務システムだけを考えて対策を立ててしまう傾向にある → サイロ化
  • サイロ化した中で認証強化してもそれは「部分最適化」でしかない。「全体最適化」とは程遠い。

    ※ サイロ化:企業内にある部門が他部門との連携をすることなく、自らの業務の部分最適化のみを優先するようになった状態をいう。元々サイロとは穀物などを貯蔵するタンクのことだが、英語圏では「窓がなく周囲が見えない」という意味も含んでいる。

出ました。サイロ化。大規模になればなるほどサイロ化の弊害は大きくなっていく。

企業経営にまで影響を与える問題となっているということ。

パスワード認証の課題

ユーザーの本人確認を行う認証にも問題は多い。

  • 他人に知られた場合になりすましが可能
  • 安易なパスワードの場合類推や解読が容易
  • 忘れたり間違えたりしやすい
  • 複雑なパスワードは記憶が困難
  • パスワードの使い回しにより、安全性が低下

パスワード認証は、「知られない」、「推測されない」、「同じものを使い続けない」、「同じものを使わない」といった「努力」をユーザーに強いていて、その「努力」には限界がある。

パスワード認証を強化する他要素認証(ワンタイムパスワード、生体認証)も導入が難しい。

そんな、企業の課題、ユーザーの課題を SSO(シングルサインオン)が解決してくれると言うてます。

SSO 導入メリット

  • SSO とは、業務システムで個別に行っている認証を一元化し、ユーザーがいずれかのシステムを利用する際に認証を一度行えば、連携している全てのシステムが利用できるようになる。ユーザー ID,パスワード、ユーザー属性などのユーザー情報の管理や、問い合わせ対応の負荷が軽減される。
  • SSO が導入されるということは、入口が1カ所で管理されるということ。唯一の入口に防犯対策をを一本化できる。情報漏洩の防止策として SSO は役に立つ。
  • 新規に導入する業務システムだけでなく、既存の業務システムにも SSO は効果を発揮する。既存の業務システムで管理されているユーザー情報に大きな変更を加えないため、導入も用意で短期間かつ少ない労力で導入できる
  • 導入によってユーザーの利便性が向上するだけでなく、認証に関する管理の負荷も軽減される。
  • 今後登場する未知のクラウドサービスの導入と行った変化にも柔軟に対応できる
  • 各業務システム間のサイロ化の課題も、個々の業務システム担当者に大きな負担を強いることなく解決でき、IT 投資を効率化できる。

なんのことはない。一つのユーザーID・パスワードだけ管理できるようになれば問題は解決する。

SSO の 4 つの方式と特徴

方式には「リバースプロキシ方式」、「エージェント方式」、「クライアントエージェント方式」「フェデレーション」の 4 つ紹介されているが、最初の 3 つはオンプレミスでSSOを用意する方式。クラウドサービスを利用するものが「フェデレーション」。SSO = フェデレーションと捉えてしまってよい。

フェデレーションの仕組み

フェデレーションの仕組みの一つとして「SAML」や「OpenID Connect」といった。「標準規約」が用意されている。
SAML とは(Security Assertion Markup Language)の頭文字で、OASIS(Organization for the Advancement of Structured Information Standards)が定めた、クラウドサービスと企業の間で認証の連携を行う標準規格のこと。ほとんどのクラウドサービスがこの SAML に対応しており、クラウドサービスと認証の連携を行う場合は、SAML を利用することが多い。

OpenID Connect つまり JWT を使うこともできるのだろう。が、SSO では SAML を使う。

SSO(フェデレーション) はシンプル

本書では、フェデレーションの仕組みをパスポートの仕組みに例えて説明している。

パスポートは所持する本人の国籍の国で発行され、その発行には比較的厳格な本人確認、つまりユーザーの認証が求められる。 ただし、一旦パスポートが発行されれば、渡航先の国ではパスポート自体が本人確認となる。 入国の際にパスポート以外の本人確認の手続きは必要とされない。

f:id:sekilberg:20210713002050p:plain

  1. IdP(IdentityProvider)で厳格な本人確認をする
  2. IdP がアサーションSAML = パスポート)を発行
  3. アサーションがブラウザを介して SP(Service Provider = アプリケーション)に提示される
  4. アサーションの正当性と信頼した IdP による発行かどうかを SP が確認する
  5. SP がユーザーに利用を許可する

SAMLアサーションがパスポートと異なるのは、一度発行されたアサーションは特定の SP へのログインにしか使えないこと。SP 毎にアサーションを発行することが必要。(これは SAMLの仕様)


IdP による厳格な審査を通過していることとSPとの信頼関係によって成り立っているわけですね。

おまけ

いきなり、IdP(IdentityProvider)、SP(Service Provider)といった用語が出てきました。 OAuth(= 認可)の世界での登場人物は、リソースオーナー、クライアント、認可サーバー、リソースサーバーですが、OpenID Connect や SAML(= 認証)の世界になるとユーザー、SP、IdPといった用語になるところも混乱しがちなところです。(トークン→アサーションとか)

まとめ

そもそも技術書ではないし、SSO導入事例、オンプレ導入方法といった企業の経営者や情報システム担当者向けの内容だったので、OneLogin, okta などの IDaaS に関するお目当ての情報はなかった。
ただ、SSOの概要は理解できました。特にパスポートに例えた説明がわかりやすく、OAuth に比べずっとシンプルに感じました。(どう実装するかは別として。。)SSO=パスポートの仕組みということがわかっただけでも収穫アリですね。

SAML に関してはほとんど説明されていませんが、id token のような個人情報が XML 形式になっただけと捉えれば良いかと思います。

近い将来 SSO による生体認証が当たり前になって、フォームに E メール, パスワードを入力していたことが時代遅れになる未来もそう遠くないのかもしれません。