余白

https://blog.lacolaco.net/ に移転しました

読後メモ: 「MaaS モビリティ革命の先にある全産業のゲームチェンジ」

いつもの読後メモ。 今回は日高 洋祐他著の「MaaS モビリティ革命の先にある全産業のゲームチェンジ」。

MaaS モビリティ革命の先にある全産業のゲームチェンジ

MaaS モビリティ革命の先にある全産業のゲームチェンジ

本の概要

この本はMaaS(Mobility as a Service)について、基本的な考え方から実践的な事例、今後を見据えたアクションプランまで網羅的に解説している。 海外の事例は実際に体験した生の情報が多く書かれていて、現実感と納得感のある内容だった。

本書には専門的な内容も含まれるが、おのおの興味のある章から読んでいただき、そこから関連する章に興味の赴くまま読み進んでもらえれば幸いだ。読者の皆様が、広く深く「MaaSの世界」に入り、ビジネスを成功させるうえで役立つものとなることを願っている

.

そこで、海外を含めて少しでも筆者たちの見聞きしたこと、感じたこと、考えたことを日本に伝えたい。また、MaaSの「本質」及びその「先」にある交通および社会、あらゆる産業のビジネスモデルの変革が、果たして危機なのか、輝ける未来なのか。モビリティの世界に閉じるのではなく、日本再興を期する全産業のチャンスとして捉え、MaaSのその先にある「Beyond MaaS(ビヨンド・マース)」の答えを、本書をきっかけとして読者の皆様と創り上げていきたい。これこそが、筆者たちが本書を世に問う一番の動機である。

かなり濃厚でMaaSについて全体像から細部まで解説されていて、読み応えがあった。MaaSについて知りたい人はぜひ一冊目に読むと良い本だと思う。

内容の紹介

本の序盤ではMaaSとはいったい何を指し、何を指さないのかについて、観念的な話題を中心にしている。

日本ではウーバーテクノロジーズに代表される配車サービスなどの単一のモビリティサービスを指してMaaSと呼ぶ向きもあるが、それはMaaSを構成する一要素でしかない。利用者視点に立って複数の交通サービスを組み合わせ、それらがスマホアプリ1つでルート検索から予約、決済まで完了し、シームレスな移動体験を実現する取り組みが、グローバルスタンダードで示すところのMaaSである。

MaaSは「理想の移動体験」の実現を目的としたひとつの手段であることが肝要である。 その発祥は北欧、フィンランドにある。およそ30年ほど前から、ヨーロッパでは自動車社会からの脱却を目指すムーブメントが起こっている。

自動車利用に依存した社会からの脱却の1つとしてフィンランドから生まれた新たなサービスがMaaS(Mobility as a Service、マース)であり、世界中で注目されるようになった。  MaaSとは、従来のマイカーや自転車などの交通手段をモノで提供するのではなく、サービスとして提供する概念である

30年、ほとんど平成まるごと出遅れているわけだが、日本でも2018年にようやく政府の戦略の中にはっきりと「MaaS」が提言されている。日本のモビリティ革命はこれから始まる。

一方、日本では、政府の成長戦略として 18 年6月に閣議決定された「未来投資戦略2018」において、初めて「Society 5・0」の実現のためのフラッグシッププロジェクトとして、MaaSが位置付けられた

中盤からは、具体的なMaaSの事例をもとに、どのように社会実装されるのが望ましいのかについて考察している。

キーとなるのは、マイカー依存を脱却することで浮く財源を公共交通機関へ流していくことである。

公共交通の質を高めるためには投資が必要だが、マイカー依存が進んだ社会で、公共交通に投資をするのは難しい。利用者が少ないから利益が出ないし、だからと言って税金を投入しようにも、マイカー利用者からの支持を得ることが難しいから

公共交通への投資が進めばよりマイカー無しで暮らしやすい地域ができていく。MaaSはその好循環を生み出す鍵になる。

つまり、MaaSがビジネスとして成功するほどに、地域でマイカーのエコシステムを維持するために使われていた資金が、公共交通を潤すことになるのだ。  公共交通に資金が回るようになれば、必要な投資ができるようになり、公共交通の質が改善する。それは公共交通の利便性・快適性を高めるから、公共交通へのシフトがより進む。こうした好循環によって公共交通の質が改善していくことが期待される。

日本の地方都市は車への依存度が高すぎて、駅前は自動車用の広大なロータリー、市街地の一等地がどんどん駐車場に変わっている。誰も歩かないから店が寂れ、駐車場に変わり、さらに歩いて訪れる場所が減っていく悪循環に入っている。

完全にクルマ社会になっている日本の地方都市は、一般に、歩いていける範囲に出ていきたくなるような場所がない。中心市街地は寂れているから、休日の過ごし方といえば、特定の趣味がある人を除き、郊外のショッピングセンターに行くのが関の山ということになる

ヨーロッパでは10年以上先行した脱マイカー依存の取り組みにより、歩いて楽しい街づくりが出来上がっている。 これからの地方都市は歩いて楽しいコンパクトな町をどうやって作るかが重要になる。

対する欧州の地方都市は、そんなに大きくなくても中心市街地に常に人の往来があり、にぎわいがある。中心部には路面電車が走り、クルマがなくとも移動ができて、ウィンドーショッピングをしたり、公園やカフェでのんびりしたりできる。休日は広場にファーマーズマーケットが立つから、朝から大勢の人でごった返す。すべての地方都市がそうだというわけではないが、衰退していない欧州の地方都市に共通するのは、歩いて楽しい町、クルマがなくても移動に困らない町になっているということである。

.

歩いて楽しくて、移動に困らない町になっているのは、そういう方向での足づくりとまちづくりの努力を弛まずに続けてきたからだ。クルマ社会になるに任せて無計画にまちづくりをしてきた日本とはそこが大きく異なっている。

終盤からは、MaaSが与えるこれまでの産業、経済構造への影響や、これから各事業者がどのようなアクションを取っていくべきかの提言になっている。 キーとなるのは新たなモビリティを社会に投入していくための規制緩和と、それらが統合されるために官民で取り組むプラットフォームづくりである。

それぞれ独立して企業活動をしてきた鉄道会社や自動車メーカーも、新たに生まれるMaaSの統合プラットフォームの下でのビジネスになりかねない。音楽業界やエンタメ業界、旅行業界、出版業界など、さまざまなコンテンツ業界がプラットフォームビジネスの波に飲み込まれていることと同じだ。 「自分たちの仕事だけ頑張っていればいい」という時代は終わった。既存事業の延長線上でMaaSを捉えないほうがいい。世界がそう変わっていく、実際に変わりつつあることは、MaaSに取り組む理由を考えることと並行して念頭におくべきであろう


他にも紹介したい引用はいっぱいあるが続きは読んでほしい

良い設計と平衡

免責事項: 思考過程のメモです

アプリケーションの設計とは

アプリケーションの設計とは、「空間を定義し」「問題を識別し」「解決手段を選択する」工程である。

空間の定義

システムの中で、設計の対象とする領域とそうでない領域を定義する。 システムへの要求により空間は大きくなる。フレームワークは空間を狭める効果がある。 システムの空間のなかで設計の対象範囲を広く定義するほど「設計の自由度が高い」とみなせる。 設計の自由度が高ければ選択できる解決手段も増えるが、同時に問題の量も増える。

問題と解決

設計空間の中で、問題を識別し、それを解決できる手段を選択する。

f:id:lacolaco:20190317214301p:plain

システムの問題を解決するためにはシステムに作用する必要があり、その作用により別の問題が発生することが常である。 トレードオフと呼ばれ、解決前の問題と解決後の問題の重みを比較し、どちらを受け入れるかを選択する。 結果として、問題と解決、それによる問題を補う解決、というようにネットワークがつながっていく。

f:id:lacolaco:20190317214659p:plain

問題を解決するための手段はほとんどの場合複数ある。 複数の問題を一挙に解決できる手段もある。 そして目先の問題を解決するための手段が連鎖の先で大きな問題を誘起することもある。 良い設計は、全体としてネットワークの分岐やサイズをコンパクトに抑える。

f:id:lacolaco:20190317214956p:plain

すべての問題が解決されることはない。ネットワークの末端や途中には未解決の問題が残る。

f:id:lacolaco:20190317215641p:plain

それらを解決することでさらにシステムを拡張する選択もあるが、解決しないという選択を取ることもできる。 それは仕様と呼ばれたりレガシーと呼ばれたり負債と呼ばれたり、認識はいろいろである。 その問題から先のチェーンをふるい落とし、システムをコンパクトに維持するための先送りである。

f:id:lacolaco:20190317215812p:plain

そのようにして設計されたシステムは、全体として釣り合いがとれた平衡状態になる。

f:id:lacolaco:20190317215933p:plain

システムは時間とともに外部からの刺激を受ける。 新しい機能の追加、仕様の変更、あるいはインフラ環境の変化など。 それらはシステムの中で新たな問題を生み出す。

f:id:lacolaco:20190317220409p:plain

新たな問題を解決するためにまたシステムに手を加える必要が生まれる。 良くない設計は、新たな問題に対して必要な変更が大きいシステムを生み出す。柔軟性が低く、衝撃を吸収できない。メンテナンス性が低いともいう。 新たな作用により既存の問題と解決に影響してしまうこともあり、ネットワーク全体で新しい平衡に達するまでに時間がかかる。

f:id:lacolaco:20190317220637p:plain

良い設計は新しい平衡に達するまでの時間が短い。 あらかじめ新たな問題の発生を予測してあるシステムは、解決のために既存のネットワークに加える変更が小さい。

f:id:lacolaco:20190317220918p:plain

どう設計すべきか

現在観測できる問題をスマートに解決できるシステムであっても、未来に受ける新たな外部刺激から生まれる問題に弱くては良い設計とは言えない。 かといって最初からあらゆる問題を想定することは早すぎる最適化のような別の問題も引き起こし、開発コストも増加する。 どこまでを現在の設計でカバーし、どこから先を未来の再設計に先送りするかの選択こそが必要である。それすらもトレードオフである。

良い設計を生み出すために磨くべき能力は

  • プロジェクト、チームメンバーなど外部要因に合わせた設計空間定義
  • 問題を見逃さない目。多くの問題は一般的に発生するパターンがある。
  • 解決手段の引き出し。より多くの選択肢の中から最良の決定をする。
  • 想像力。一度達した平衡が崩れる将来リスクを認識し、備える。

Angularで巨大なライブラリを動的に読み込む

オリジナルはこちら

medium.com

基本的にコードサンプルなどはオリジナルを参照してください。この記事では込み入った事情の部分だけを日本語で補足します。

tsconfig.jsonの準備

tsconfig.jsonmodule 設定は、TypeScript内で記述したモジュールのimport/exportをどのように解決するかを指定します。 Angular CLIのデフォルトでは module: es2015 を指定しているので、静的な import ... from はそのまま残しますが、import() はサポートしていません。 tsconfig.jsonmodule: esnext を指定すると、import()JavaScriptにそのまま残すようになります。 import() がサポートされたブラウザ上であれば、webpackを通さなくてもそのままブラウザ上でモジュール解決できる状態になっています。

ところがまだ import() はTC39のProposalとしてはStage 3で、未サポートのブラウザが多くあります。 現実的には、webpackを使ってbundleする必要がありますが、webpackはこの import() をwebpackがもつ動的モジュール読み込みの仕組み ( require.ensure ) で置き換えてくれます。 つまり、 import() のpolyfillのように振る舞ってくれます。

webpackを通すことでbundle後のJavaScriptには import ... fromimport() も残らないため、 target: es5 のままトランスパイルしても問題ありません。つまりブラウザ互換性には影響しません。 Promiseがないブラウザではes2015のpolyfillが必要ですが、Angular CLI v7.2からはデフォルトで es2015非サポートなブラウザでだけ自動的に適用されるpolyfillを吐き出すので、我々がes2015のpolyfillについて気にすることはありません。

normalizeCommonJSImport について

これは TypeScriptの import() の型定義でCommonJSとの互換性に問題があるための処置です。 import() では名前付きインポートをサポートしておらず、ES Moduleにおける default export だけをサポートしています。 webpackではその互換性のために、CommonJSで書かれたモジュールを import() でインポートするときには、module.exports オブジェクトを default exportに見立てて、 import() で読み込まれるオブジェクトの default プロパティに格納しています。

TypeScriptの import() は賢いので、 静的にimportしたときに import * as Chart from 'chart.js' で得られる Chart の型と、 import('chart.js').then(result => result) で得られる result の型は同じに扱うのですが、実際は result.defaultChart に相当するので、素直に書くとTypeScriptのコンパイルが通りません。 そのために normalizeCommonJSImport でラップしています。

転職のお知らせ

f:id:lacolaco:20190227232704j:plain

写真は妻が作った雪だるまです。

  • From: 株式会社Kaizen Platform
    • 2/28が最終出社日でした
    • やってたこと
      • Webフロントエンド SPA開発 (React/TypeScript)
      • Schema-first GraphQLによるAPI仕様中心開発の整備
      • UX/UI設計
      • Webパフォーマンス計測、改善
      • その他
  • To: bitbank株式会社
    • 4月から入社します
    • 週4日にしてもらいました
    • やりたいこと
      • ある程度の規模に育ったAngularアプリの開発に関わりたい
      • Angularのエキスパートとして持てる力を尽くしてチームを加速させたい
      • Web技術とブロックチェーンのこれからについて、持論を持てる程度の学びを得たい
        • ついでにマイクロペイメントまで学びを得られたら嬉しい

本件についてのお問い合わせは Kyashの送金メッセージでお願いします。(返答を保証するものではありません)

f:id:lacolaco:20190227235324p:plain

README.mdに動的コンテンツを埋め込む、あるいはImage via Functionというアプローチ

突然ですが、 README.md に動的なコンテンツを埋め込みたいと思ったことはないですか?僕はあります。 具体的には、リポジトリのコントリビューターをREADME.mdに埋め込みたいという願望がありました。

つまりこういうことです。

f:id:lacolaco:20190226224230p:plain

しかし毎回CIなどでREADME.mdを編集するのはセットアップが面倒です。 <contributors-list> みたいなCustom Elementsが使えたらきれいな世界だなあと思ったのですが、肝心のscriptタグが動かないのでそれは無理です。 ということで、頼れるのは 画像 ということになりました。

Image via Function

README.mdに埋め込めて、なおかつ動的なコンテンツを扱えるのは画像のURL展開だけなので、つまりコントリビューターリストを画像化するHTTPエンドポイントを用意し、そのURLをREADME.mdに埋め込めばいいわけですね。同じことはすでにCIサービスのSVGバッジなどで広く行われているので、これ自体は特筆すべきことではないと思います。

問題はコントリビューターリストの画像をどうやって作るかということです。今回はまずコントリビューターリストを表示するAngularアプリケーションを作り、そのスクリーンショットをPuppeteerで撮影する、という流れを実装しました。

f:id:lacolaco:20190226225516p:plain

Angularアプリ

シンプルにAngular CLIで作成し、@octokit/rest を使ってGitHub APIを呼び出しています。単純なアプリケーションです。 これをFirebase Hostingで公開し、Puppeteerで表示できるようにします。

https://contributors-img.firebaseapp.com/repo=angular/angular-ja

f:id:lacolaco:20190226225027p:plain

ポイントとしては、画像化したい要素にIDを付与していることです。このアプリケーションでは後ほど画像化するときに #contributors を指定します。

Puppeteer on Cloud Functions

Cloud Functions for FirebaseではHeadless Chromeを便利に扱えるPuppeteerを実行できます。 Puppeteerを使ってHeadless Chromeを起動して、先ほどのAngularアプリケーションにアクセスし、スクリーンショットを撮影します。

async function renderContributorsImage(repository: string): Promise<Buffer> {
  const browser = await puppeteer.launch(
    isDebug
      ? {}
      : {
          headless: true,
          args: ['--no-sandbox'],
        },
  );
  const page = await browser.newPage();

  await page.goto(
    `https://contributors-img.firebaseapp.com?repo=${repository}`,
  );

  const screenshotTarget = await page.waitForSelector('#contributors');
  await page.waitForResponse(() => true);

  const screenshot = await screenshotTarget.screenshot({
    type: 'png',
    omitBackground: true,
  });
  return await browser.close().then(() => screenshot);
}

ここのポイントは await page.waitForResponse(() => true)GitHubアバター画像のリクエストをすべて待っているところです。 これを待たないとHTMLのレンダリングが終わっただけではまだ画像が空っぽになるからです。

Cache in Cloud Storage

Cloud Functions上でPuppeteerを動かすのはメモリを多く使うし実行時間も長くてお金が結構かかりますし、毎回画像を作るのはパフォーマンスも悪いです。 FirebaseのCloud Storage上にリポジトリごとに画像をキャッシュし、キャッシュがある場合はキャッシュから返します。 Cloud StorageのBucketにExpirationの設定をしているので、24時間経った古いキャッシュは自動的に削除されます。Firebaseコンソール側からはこの設定が見えないんですが、GCPのコンソールからアクセスすると普通に使えます。

f:id:lacolaco:20190226223147p:plain

f:id:lacolaco:20190226223210p:plain

async function _createContributorsImage(repository: string): Promise<Buffer> {
  const cacheId = generateCacheId(repository);
  const cacheFile = bucket.file(cacheId);

  console.log(`Look for a cache...`);
  if (await cacheFile.exists().then(data => data[0])) {
    console.log(`Return from the cache`);
    return cacheFile.download().then(data => data[0]);
  }

  console.log(`Render an image`);
  const image = await renderContributorsImage(repository);

  console.log(`Save new cache`);
  await cacheFile.save(image, {});

  console.log(`Return rendered image`);
  return image;
}

Puppeteerを使うHTTP Trigger Functionの定義

Puppeteerはメモリを多量に使うので、Function定義時にメモリの設定が必要です。1GBあればまあ動きます。 ブラウザの起動、Angularアプリの実行、スクリーンショット撮影と保存まで含めると結構時間がかかるので、タイムアウト時間も30秒にしてあります。

export const createContributorsImage = functions
  .runWith({
    timeoutSeconds: 30,
    memory: '1GB',
  })
  .https.onRequest((request, response) => {
    const repoParam = request.query['repo'];

    if (!repoParam || typeof repoParam !== 'string') {
      response.status(400).send(`'repo' parameter is required.`);
      return;
    }

    _createContributorsImage(repoParam)
      .then(image => {
        response.setHeader('Content-Type', 'image/png');
        response.status(200).send(image);
      })
      .catch(err => {
        console.error(err);
        response.status(500).send(err.toString());
      });
  });

完成!

というわけで画像を展開できる場所であればどこでもコントリビューターリストを表示できるようになりました!

例:

https://github.com/angular/angular-ja/blob/283047c5686cb3957966a671264e4c1d7b32a073/README.md

f:id:lacolaco:20190226224230p:plain

24時間ごとにしか更新されない課題はありますが、それほどリアルタイム性があるデータではないので問題ないでしょう。

課題としてはPuppeteerでGitHubアバター画像をダウンロードするときに外部ネットワークが発生するので、僕のお財布にダメージが大きいところです。しばらく従量課金で動かしますが破産しそうになったらリポジトリホワイトリストを作って運用します。

まとめ

  • Cloud Functionを使って画像を返せば、簡単に動的コンテンツを静的ページに埋め込める
  • Cloud Functions + Puppeteer + Webapp で、慣れてるHTML+CSSで作ったUIを画像化できる
    • 画像処理頑張らなくてもいい

久々に趣味プロらしい趣味プロをしたら楽しかったです。また何か思いついたらやります。