前回に引き続き、スピーチ原稿と合わせて公開します。
(講演ではアドリブもあるので原稿とは微妙に異なることをご了承ください)
追記 : 前回の内容を読んでいない方は、先にそちらを読んでください。
前回の Droid Kaigi で私は「ドメイン駆動設計とは何か」という話をしました。
本当は前回のCfPを出す時点でAndroidアプリ開発での実装の話もいれようかなと思っていたのですが、ドメイン駆動設計が何かをきちんと説明するだけでいっぱいいっぱいでした。
今回は前回の続きなので、簡単に前回の復習からはじめます。前回の話の完全版は私のブログに書いてありますので、ぜひ読んでください。
前回の説明を復習すると、ドメイン駆動設計とは
ドメインエキスパートの言葉を観察し、ドメインを構成するユビキタス言語を見つけ、
ユビキタス言語を使ってドメインを適切に反映した我々のソフトウェアに役立つドメインモデルを作り、
作ったドメインモデルを正確に表現するようコードを実装し、
これを繰り返してドメインモデルと実装の両方を洗練させていく設計手法です。
ドメイン駆動設計では、実践するために役立つさまざまな手法が出てきます。これらは主に戦略的設計と戦術的設計の2つに分けることができます。
ドメインモデルを作り上げるために役立つ手法が戦略的設計、ドメインモデルからそれを表現した実装を行っていくのに役立つ手法が戦術的設計です。
戦略的設計のユビキタス言語、境界づけられたコンテキスト、コンテキストマップについては前回お話しました。
今回は戦術的設計についてお話します。
ドメイン駆動設計では、ドメインモデルをそのまま表現するように実装します。
究極的にはコードを読めばそのドメインモデルがわかるし、そのドメインモデルを理解しているならエンジニアじゃなくてもなんとなくテストコードが読めるという状態です。
じゃあ具体的にどうすれば、ドメインモデルをそのまま表現した実装にできるのかなって思いますよね。
そこで、こういうふうに実装するとうまくドメインモデルを表現できたよ、という実装パターンが紹介されています。
それが戦術的設計です。
戦術的設計としてたくさんパターンが紹介されているのですが、これらを取り入れなくても、ドメインモデルをうまく表現した実装ができるなら、それはちゃんとドメイン駆動設計です。
例えばドメインイベントは、エリック・エヴァンスの本が出版された後に新しく付け加えられています。
戦術的設計のパターンはたくさんありますから、すべてがAndroidアプリの実装に役立つとは限りません。
例えば、実践ドメイン駆動設計にはMongoDBやSpringでの例が出てくるパターンがあるのですが、Androidアプリの開発に置き換えて考えるのはなかなか難しいです。
よって、この講演ではこれらを網羅的に紹介したりはしません。
この講演では、
長年の開発で大きく、複雑になってきたAndroidアプリにドメイン駆動設計のエッセンスを取り入れてみたいけど、どこからはじめればいいかさっぱりわからない。
という方に向けて
この一年半、私がドメイン駆動設計を念頭に、既存のAndroidアプリの実装をリファクタリングして、これはよかったなという戦術的設計のパターンと、どう取り入れたかの例を紹介します。
作り直すときの話ではありません。
いままで5つ以上のアプリで、ドメイン駆動設計をどう取り入れるか試行錯誤してきました。
複雑に絡み合ったアプリのコードを少しづつ紐解いて、UIに隠されていたドメインモデルを実装としてどう表現してきたかという話です。
もう少し前提条件というか状況をはっきりさせておきます。
こういう方法よかったよと言う場合、無条件にすべてに適用できるものではありません。どういう状況のときによかったのかをはっきりさせておかないと、お互いに不幸になります。
特にドメイン駆動設計はさまざまなソフトウェアの開発で取り組まれていますから、同じ戦術的設計のパターンであっても、対象とするドメインだけでなくプラットフォームやフレームワークによっても最適な実装が異なるでしょう。
この講演で紹介する実装例は、以下の状況を前提としています。
まず、Androidアプリの開発での話です。iOSアプリやサーバーサイドの開発には参考にならない可能性があります。
次に、少なくとも1年以上継続的に開発しているアプリを前提としています。
これは「ある程度モデルらしきものがメンバーの共通認識としてあり、コードにもそれっぽいものがあるが、ドメインモデルとしてコードでうまく表現されていない」という状況になるには1年ぐらい必要かなと思ったからです。実際にはもっと年数の経っているアプリで取り組んでいます。
そして重要な前提条件が、サーバーとAPIを介してやりとりする、よくあるクライアントアプリであるという点です。ゲームアプリなどにはあまり参考にならないかもしれません。
サーバー側が別チームというのは、前回お話した境界づけられたコンテキストに関係する部分です。サーバーとアプリで別の境界づけられたコンテキストになっているという状況です。
では、さっそく本題に入りましょう。
最初の戦術的設計パターンは「ドメインを隔離する」です。
前回の話で、「ドメインを隔離する」というのは
「ビジネスロジックをドメインモデルとして隔離する」ということだとお話しました。
なぜ隔離するのか
本では次のように書かれています。
「ドメインロジックがプログラムの他の関心事と混ざり合っていたら、設計と実装をこのように一致させることは現実的ではない」
ここでのドメインロジックは、ドメインモデルに属するロジックのことです。ドメインモデルに関連するロジック、ドメインが持っているロジックとも言えます。
例えば、ドメインモデルに属するロジックが UI に書かれていると、ドメインモデルをそのまま表現した実装にできません。
設計と実装を一致させるために隔離する、ということがわかりました。
そうすると次は、どうやって隔離するのか、です。
本では次のように書かれています。
「ドメイン層を隔離した状態に保つことさえできれば、どのアプローチでもかまわない。」
「DDDの大きな利点のひとつが、特定のアーキテクチャに依存しないということだ。」
この文章は前回も紹介しました。
つまりドメインを隔離する方法は問わないということなんです。
好きな方法でやればよいのです。なので前回は具体的な方法は紹介しませんでした。
エリック・エヴァンスのドメイン駆動設計を読んだ方なら、じゃあアレはなんなんだ、と思うかもしれません。
そう、レイヤ化アーキテクチャです。
なぜエリック・エヴァンスのドメイン駆動設計では、レイヤ化アーキテクチャが紹介されているのか。
方法は問わないと言っているのに紹介しているなら、その理由は簡単です。
ただの例です。
いきなりドメインを隔離しろと言われても、具体例がないとなかなか理解できないものです。
つまり、ドメインを隔離できそうな設計としてこういうのがあるよ、という例としてレイヤ化アーキテクチャが紹介されている、ということなんです。
あくまで目的はドメインを隔離することです。それができればどのアプローチでもかまわない。
ただし、方法については次のようにも書かれています。
「アーキテクチャがドメインに関連するコードを隔離して、凝縮度が高いドメインの設計が、システムの他の部分と疎結合できるようにしているなら、そのアーキテクチャはおそらくドメイン駆動設計を支えられるだろう」
単に置き場所を分ければいいというわけではなく、他の部分と疎結合できるようになっている必要がある、というわけです。
まとめると、システムの他の部分と疎結合できるような方法でドメインを隔離する、ということです。
Androidアプリでもドメインを隔離したくなってきましたね。
前回お話しましたが、我々はUIにドメインにある概念や知識、ビジネスロジックを詰め込みがちです。
利口な UI から脱却するにはぜひともドメインを隔離したいところ。
そうなると問題はどう隔離するか?です。
やりたいことは、
UIはドメインを知っているが、ドメインはUIを知らないようにしたいということ、
ドメインと他の部分を疎結合にしたいということ、
そして、それを強制させたいということです。
単純にパッケージを分けるだけだと、ドメインからUIのクラスを見ようと思ったら見えてしまいます。
見ないように気を付けようだと形骸化します。メンバーが変わった時だけでなく、リリースに間に合わせるために今回だけ特別、みたいなことが容易に想像できます。
そこで、ドメイン用に別モジュールを用意することにしました。
このモジュールは gradle のモジュールのことです。
このような構成にすると、ドメインがUIを知らないように強制できますし、純粋なロジック部分だけになるのでテストが書きやすいです。
また、UI を変更してもドメイン部分に変更がないのであれば、その部分の動作を担保できますし、ドメインモジュールのリビルドが走らないのでちょっとだけビルドが速くなります。
さぁ、ドメインモデルの置き場所が決まりました。
あとは、ここにドメインモデルをガンガン入れていくのですが、
何を入れたらいいかわからん...ってなりませんか?
いや、入れるのはドメインモデルってのはわかってる。
わからないのは、自分のコードの中でどれがドメインモデルであるべきなのか、どこから見つけていけばいいのか...
そこで役に立つのが次の戦術的設計のパターン、「値オブジェクト」です。
まずは、ドメイン駆動設計の本で値オブジェクトについてなんと言っているのか見てみましょう。
「あるオブジェクトが、ドメインにおける記述的な側面を表現し、概念的な同一性を持たない場合、そういうオブジェクトは、値オブジェクトと呼ばれる。」
概念的な同一性を持たない、というのは、一意に識別する必要がない、区別する必要がないということです。
例えば色は値オブジェクトの一例です。
同じ色を持つ Color オブジェクトを区別する必要はありませんよね。
ドメインモデルの中には、このような「何であるかだけが問題となり、誰であるかやどれであるかは問われないような要素」がでてきます。
これらを値オブジェクトとして実装することで、ドメインモデルを表現した実装になります。
値オブジェクトの他の面も見てみましょう。
ドメイン駆動設計では、値オブジェクトを不変にすることを勧めています。
不変であれば自由にコピーや共有ができ、複数のスレッドで安全に利用できます。また、引数や戻り値として別のオブジェクトに渡しても、その先で変更されないので設計の簡素化につながります。
例えば Color オブジェクトであれば、このように内部で保持する色の値を可変にするのはおすすめできません。
このように Color オブジェクトは不変にし、色を変更するときは Color オブジェクト自体を入れ替えるようにします。
この話を聞いて、なんか聞いたことあるなと思った方いらっしゃると思います。このイミュータビリティの話は Effective Java でも紹介されています。
さらに別の面も見てみましょう。
「値オブジェクトを構成する属性は、概念的な統一体を形成すべきである」
概念的な統一体...ちょっと難しいですね。
例えばユーザーに紐づく情報として、IDや名前の他に郵便番号、都道府県、残りのアドレスがあるとします。
このとき、郵便番号、都道府県、残りのアドレスは、ユーザーの別々の属性ではなく、住所という統一体を形成する属性です。
よって、この3つで形成される住所という値オブジェクトを用意し、ユーザーには住所を持たせるようにします。
値オブジェクトはドメイン駆動設計において、ドメインモデルを構成する基本的な要素です。エンティティやドメインサービスなど他の戦術的設計パターンではみな値オブジェクトを利用します。
つまり、値オブジェクトとなるドメインモデルを見つけるところから始めるのが最適ということです。
いやいや、そうは言っても、Color とか Android フレームワークにあるし、値オブジェクトにするもの見つかるかな?
そんなあなたのために、今回は Android アプリの開発で値オブジェクトとしてモデリングした例をたくさん用意しました。
例を紹介する前に、Android アプリで値オブジェクトをどう実装するかですが、
実装要件である
状態を不変に保つ、つまりイミュータブルにできる
値が等しいかどうか比較できる
全体を完全に置き換えられる
に注目すると、
Kotlin の data class が最適です。イミュータブルにしたいのでプロパティは val ですね。
Java ならフィールドを全て final にして equals() と hashCode() を override するか、AutoValue などのライブラリを使うという手もあります。ですが、これを機に Kotlin を導入してみてはいかがでしょうか。
では Android アプリで値オブジェクトを見つけていきましょう。
UserId, ItemId, ProductId, OrderId などなど、アプリの中で一つぐらい ID というものを扱っていると思います。
このID、文字列で取り回していませんか?
この ID を値オブジェクトにするとどうなるか、最初の例を見てみましょう。
ユーザーID からフォロワーの一覧を取得するAPIです。
フォロワー一覧画面を作るとき、このAPIを呼び出す側も呼び出される側も同じ人が実装すると、何を渡せばいいのかわかっているので String であっても特に困ることなく実装できます。
後になって、別の人が他の場所からこのAPIを呼ぶことになったり、機能を変更することになったら、次のような問題が出てきます。
userId ってどこにある id だっけ?
Profile クラスの持ってる id ってここに渡していいやつだっけ?
空文字ってだめだよね?呼び出す前にチェック必要?
userId が空文字になるわけないんだけど、でも Profile が持ってる id に空文字が入らないなんて他の部分も読まないと保証できないし...
そこで UserId を値オブジェクトにしましょう。
UserId のインスタンスがあるなら、それが保持している文字は絶対に空文字ではないと保証できる状態にします。
こうすれば、利用側は安心して getFollower() API を呼び出すことができます。
Profile クラスが持っている id が UserId 型なら、この API に渡してよいいIDであることが明白です。
さらに String から UserId 型にすることで、どこで利用されているかを静的に解析でき、リファクタリング時の影響範囲も調べやすくなります。
値オブジェクトとして ID を表現するということは、ユビキタス言語の成長にも繋がります。
例えばアプリの中に categoryId というのがあったので、値オブジェクトにしようと思って利用箇所などを調べていると
genreId というのもあることに気づいてしまいました。よくよく話を聞くと
この2つは同じものでした。
この ID 用の値オブジェクトを用意するにはクラスの名前が必要です。この名前はユビキタス言語になるものです。
そこで、どちらをユビキタス言語にするか話し合って GenreId に統一しようということになりました。
GenreId を値オブジェクトとして用意するとこうなります。
GenreId の変数が categoryId なのすごく違和感ありますよね。
ID を値オブジェクトにできないか考えたことで、言語の問題に気づけ、チームとして新しいユビキタス言語を見つけることができました。まぎらわしい変数名がつけられるのを防ぐこともできます。
次は ID が複数の情報を含んでいる場合の例です。
商品 ID が、ブランドの ID とブランド内でのコードから構成されています。
商品画面からブランド一覧画面に遷移するために、商品 ID の文字列を処理してブランド ID 部分を取り出すようになっていました。
id が空文字だったり : を含まない文字列だったら、意図しない値で BrandActivity を開くことになる、という問題もありますが、
ここでの一番の問題は、UI が知るべきではない商品 ID のフォーマットが漏れてしまっているということです。
もうわかりますね、
値オブジェクトとして ProductId を用意しましょう。
正しいフォーマットから ProductId が構成されることを保証できますし、UI 側は商品 ID の文字列表現がどうなっているかを知らなくてすみます。
値オブジェクトの見つけ方わかってきたでしょうか?
では次の例に行きます。
ユーザー登録画面の作成を依頼されました。
性別と生年月日を渡す必要があります。
作り始めて気がつきました。
性別ってどんな文字列送ればいいの?
というか、そもそも扱いたいのは性別そのものであって、性別を表現した文字列じゃないよね。ということは性別はモデルでは...
同じ性別なら、性別として区別する必要はないのでこれも値オブジェクトです。
このようなとりうる状態が限定されている値オブジェクトは enum で表現できますね。
性別の文字列について UI が知らなくて済むようになりました。
しかし、まだ問題があります。
誕生日の方もどういう文字列を送ったらいいかわかりませんね。
こちらも考えてみましょう。
同じ日を指している日付は区別する必要がないので、値オブジェクトとして表現できます。
誕生日など特定の意味をもった日付はドメインモデルです。
DateOfBirth クラスとして実装するとこのようになるでしょう。
どういうフォーマットで文字列にするのかを UI から隠蔽できます。
引数の型として DateOfBirth を使うことで、何を渡せばいいのか明白になりました。
次の例に行きましょう。
画像の縦横サイズとURLを持つ Image というオブジェクトがあります。画像のアクペクト比が 4 : 3 より横長、例えば 16 : 9 とかだったら、 wide 用の placeholder を使う、という仕様です。
このコードの問題は、wide かどうかの判定処理が UI に書かれているところですね。ImageView に画像をロードするあちこちの処理で同じコードが書かれていそうです。
UI に書くのはよくないよね、ということで次にありがちなのが、ユーティリティクラスです。
判定処理の重複はなくなりましたが、ドメイン駆動設計としては問題があります。
Image クラスがただのデータモデルになっているという点です。このような状態をドメインモデル貧血症といいます。
wide かどうかの判定はドメインモデル、つまり Image に属するロジックなので、Image に持たせましょう。
ロジックが誰の責務なのか考えるの難しいですよね。私も迷うことがよくあります。この例にしても、wide かどうかの情報は placeholder を使い分けるために使っているから、UI のロジックであってドメインのロジックではないのでは?と思うかもしれません。
この例では Image に置きましたが、表現するドメインモデルが異なれば異なる実装になるでしょう。画一的は判断基準はなく、頭の中のドメインモデルにとって自然かどうかだけです。
wide かどうかという属性は Image というドメインモデルにとって自然かどうか。
最初からしっくりくる実装ができないことはよくあります。大事なのは、その実装で固定化せず、よりよい方法が思いついたときにリファクタリングする、できるようにしておくことです。
次の例にいきましょう。
サーバーのAPIを叩いて JSON などのレスポンスをもらい、それをオブジェクトにマッピングするという処理は、多くの Android アプリで行われています。
このとき、JSONやXMLの構成をそのまま反映しただけのクラスにしていませんか?
例えば商品の情報を取得するAPIからこのようなレスポンスが返ってくるとします。
この images の配列には、同じ画像の異なるサイズへのURLが入っていて、アプリでは表示する領域の大きさに応じて、この中から適切なものを使う仕様になっています。
この JSON をそのままマッピングすると、このようなクラスになります。
アプリでは表示する領域の大きさに応じて、この中から適切なものを選んで使う仕様になっているので、どの Image を使うか判定するロジックが必要になります。
さぁ、このロジックをどこに置きましょうか?
複数箇所で同じことをやるからとか、UI にロジックを置くのはよくないからなどの理由で、このようなユーティリティクラスに置かれることがあります。
この場合、Item クラスは値を持っているだけで、そのデータを使った判断や加工処理は別のところで行われることになります。
このような状態は、先ほどと同じドメインモデル貧血症ですね。
Item が貧血になっているので、Item にこのロジックを置いてみました。
ところが問題が起こります。
Item の他に Category というのがあり、こちらでも Image を選択する処理が必要でした。
Item と Category に同じロジックが重複することになってしまい、よくありません。
じゃあどうするか。
JSON の形式を一旦忘れて、「表示する領域の大きさに応じて適切な Image を選択するロジック」は誰の責務なのかを考えましょう。
本当に Item の責務なのかな?
Item や Category の責務ではなく、Image の集合という別のドメインモデルの責務なんじゃないかな。
Image の集合を表現するドメインモデルとして Images クラスを導入してみたらどうだろう。
適切な Image を選択するロジックの置き場所として自然な感じがしませんか?
サーバーのレスポンス形式にとらわれず、別のドメインモデルがあるのではないか、ドメインモデルを適切に表現する値オブジェクトがあるのではないか考えてみてください。
次の例にいきましょう。
今度はお知らせの一覧を取得したときのレスポンスです。
各お知らせにはバナーが含まれている場合があり、バナーには表示期間の開始日時と終了日時があります。
これをそのままマッピングすると、このようなクラスになります。
これを使うとしたらどうでしょう。
bannerImageUrl は null じゃないけど、bannerStartDate が null のときはどうするの?って思いませんか?
この banner に関する3つの属性は、それぞれがお知らせに属するのではなく、この3つで概念的な統一体を構成しています。
つまり、この3つの属性をもった値オブジェクトが必要ということです。それを Banner クラスとして用意すると、このようになります。
banner のインスタンスがあれば url も日付も揃っているということを保証できます。
さらに、開始日時が終了日時より前であることを事前条件として保証できますし、今日が表示期間内か判定するロジックの置き場所としても自然です。
次の例に行きましょう。
このアプリにはユーザーが使える電子アイテムがあります。
アイテムにはログインしていなくても使えるものや、ログインしていれば使えるもの、有料会員になっていれば使えるものなどがあり、サーバーからは複数のフラグが返されます。
昔は2種類しかなかったなど、歴史的経緯とか拡張とかで、こういうレスポンスになっています。
今までの話の流れから想像つくと思いますが、こういうクラスにマッピングされていました。
UI 側で if 文を駆使した処理が行われていることは容易に想像できます。
ここでも一旦レスポンス形式のことを忘れましょう。
アイテムをドメインモデルとして考えると、アイテムの属性として自然なのは個々の Boolean よりも、どういうアイテムなのかという種類です。
そうなると次はどういう種類があるのか、ということになりますが、ここで何も考えずに Boolean 4つの組み合わせなので、2の4乗で16種類あるね、としてはいけません。
次にすることは、ドメインエキスパートに話を聞きに行くことです
どういう種類のアイテムがあると認識しているのか聞いてみると、結局は5種類しかないことがわかりました。
この5種類を enum にすればよいですね。
Boolean の組み合わせは16パターンあるのに、アイテムは5種類しかないということは、ありえない組み合わせや縮退している組み合わせがあるということです。
判明した種類を元に、サーバーチームにどういうときにどういう値が返ってくるのか確認しましょう。
例えば、
isMemberUsable は都度課金用のアイテムができたときに isPurchaseRequired と一緒に追加したので、isPurchaseRequired が true のときしか意味ないです。
とか、
isPurchaseRequired が true の時は isLoginNeeded と isMembershipRequired は意味ないので無視してください。
などの情報が得られました。
あとは得られた情報から enum に変換しましょう。
どこで変換するかって?
腐敗防止層ですよ。前回出てきましたね。
同じ属性値を持つなら区別が必要ない、というモデルがあり、それを値オブジェクトで表現する例をいろいろ紹介してきました。
しかし、モデルの中には同じ属性値であっても区別が必要なものがあります。例えば、AさんとBさんが同じ山田太郎という名前であっても、別の人として区別が必要です。
このようなモデルを表現するオブジェクトとしてドメイン駆動設計にはエンティティという戦術的設計パターンがあります。
本ではこのように書かれています。
「主として同一性によって定義されるオブジェクトはエンティティと呼ばれる。」
人間には同一性があります。例えば年齢や見た目が変わってもAさんが同じ人だと認識しますよね。
「ライフサイクルにおいて、エンティティの形態と内容は根本的に変わることがあるが、連続性のつながりは維持されなければならない。」
エンティティにはライフサイクルがあります。人がその一生の間に変化していくように、ライフサイクルの間にエンティティの内容は変わることがあります。そのとき、新しく作られるのではなく、以前の内容が変わったという連続性が維持される必要があります。
この連続性と同一性をソフトウェアで表現するときに問題になるのが、連続性を維持するためにどう永続化するかと、同一性を持たせるための識別子をどう発行するかです。
これらは技術的な制約と関係することもあり、エリック・エヴァンスのドメイン駆動設計でも実践ドメイン駆動設計でも具体的な実装を出して議論されています。
Android アプリでも、スタンドアローンで、マスターデータをアプリ内のデータベースに保存するような場合には、これらをどう実装するかが問題になります。
しかし、マスターデータがサーバーに保存され、識別子もサーバーで発行されるクライアントアプリでは、これらをアプリ側で行うことがありません。
現時点で Android アプリでのエンティティについて私が言えることは、識別子を使ってオブジェクトを比較するように実装すべきということだけです。
UserId で一意に識別すべき User なら、UserId で識別するよう equals() と hashCode() を override すべきでしょう。
Android アプリでのエンティティの扱いについてはまだまだ試行錯誤中で、例えば SharedPreferences に保存している、初回起動かどうかのフラグや通知を受け取るかどうかの設定はエンティティなのか、など悩みはつきません。
この話は機会があれば次回できるといいなと思っています。
では、まとめです。
ドメインを隔離するという目的に対し、gradle のモジュールで分ける方法を紹介しました。モジュールに分けることで依存方向を強制でき、ドメインが UI などのその他の部分を知らない状態に隔離できます。
同じ属性値をもっているなら区別する必要がないモデルは値オブジェクトとして表現できるということを紹介しました。
属性は不変にし、値オブジェクト自体を差し替えることで変更に対応しましょう。
モデルに属するべきロジックを持たせましょう。モデルの表現として、条件を満たしているときだけインスタンス化しましょう。
文字列で取り回している要素が値オブジェクトではないか考えましょう。ID、種類、日付、サイズなどは値オブジェクトを導入するよいスタート地点です。
サーバーのレスポンス形式は時としてモデルをうまく表現できていないことがあります。それに引きづられてドメインモデル貧血症になっていませんか?一旦レスポンス形式のことは忘れて、どういうモデルがあるべきかを考えましょう。
以上です。ご静聴ありがとうございました。
2018年2月9日金曜日
2018年2月7日水曜日
Android アプリの開発でドメイン駆動設計に取り組む話をします @ DroidKaigi 2018
明日はいよいよ DroidKaigi 2018 ですね。
私は2日目(2/9 金曜日)の 15:40 から Room3 でドメイン駆動設計の話をします。
前回の DroidKaigi 2017 では「ドメイン駆動設計とは何か」の説明だけでいっぱいいっぱいでした。
(ドメイン駆動設計をうまく説明するのが難しい...)
今回は前回の続きです。ぜひ前回の話を読んでからお越しください。 中・上級者タグがついていますが、前回の話を読んでいれば大丈夫です。
この一年半、私がドメイン駆動設計を念頭に、既存のAndroidアプリの実装をリファクタリングして、これはよかったなという戦略的設計のパターンと、どう取り入れたかの例を紹介します。
長年開発が続けられていたり、規模が大きかったり、複雑になってしまっている Android アプリに日々向き合っている方の一助になればと思います。
では、DroidKaigi 楽しみましょう!楽しみだな〜
私は2日目(2/9 金曜日)の 15:40 から Room3 でドメイン駆動設計の話をします。
前回の DroidKaigi 2017 では「ドメイン駆動設計とは何か」の説明だけでいっぱいいっぱいでした。
(ドメイン駆動設計をうまく説明するのが難しい...)
今回は前回の続きです。ぜひ前回の話を読んでからお越しください。 中・上級者タグがついていますが、前回の話を読んでいれば大丈夫です。
この一年半、私がドメイン駆動設計を念頭に、既存のAndroidアプリの実装をリファクタリングして、これはよかったなという戦略的設計のパターンと、どう取り入れたかの例を紹介します。
長年開発が続けられていたり、規模が大きかったり、複雑になってしまっている Android アプリに日々向き合っている方の一助になればと思います。
では、DroidKaigi 楽しみましょう!楽しみだな〜
2017年12月8日金曜日
Google Assistant, Actions on Google の Google I/O 2017 セッションまとめ
こんにちは、やんざむです。このエントリは Actions on Google Advent Calendar 2017 の8日目です。
Google I/O 2017 の Actions on Google のセッション を見て Bagel のサンプルを試してみたりしていたのですが、しばらく遊ばないでいたら API.AI の名前が変わって DialogFlow になっていました。Google Home が日本でも発売され、いまこそ絶好の Actions on Google 始めどきですね。
最初、11月に新しく増えた機能のことを書こうかと思っていたのですが、Google I/O のセッションのまとめが下書きのまま放置されていたので、この機会に書き上げました!どうぞ!
* 注意: セッション中の API.AI は DialogFlow に置き換えてあります。
* 注意: Google I/O 2017 時点の情報なので、もしかしたら現在変わっている部分があるかもしれません。
Google Assistant でも ecosystem を作る
ecosystem のキモは Actions on Google という platform
ユーザーが Google Home に「Ok Googke, talk to Google IO 17」と話す
↓
応答を書く
3. Use Training to improve
基本的なモデルを設定(ユーザーの会話例を入力)したら、DialogFlow で training を使って会話モデルを改善する
「What Android sessions are there?」
↓
list session intent にマッピング
↓
webhook
ListSessions(session_tags.TRACK_ANDROID)
5. mobile 向けに拡張する
API.AI ではビルドインの作成機能があり、 cards, chips, carousels, lists を簡単に定義できる
webhook に request を投げて動的な response を生成することも可能
配送料を決めたり、サービス可能かどうかを判定するために delivery address などが必要になることがあり、これらをリスエストする API が用意されている
2. オーダーの設定が終わったらユーザーに propose して authorization を取得する必要がある
Google Assistant SDK
Runs on nearly any platform with a TCP/IP socket.
Connect
DialogFlow(Google I/O 2017 時点)
マルチモダリティ (multimodality) に影響する要因
なぜ音声なのか?
音声は究極的に便利
会話デザインの戦略
Keynoteでは deep learning の適用によって 4.9% になってことが発表された
一方で、ユーザーが意図していることが何かを"理解"することはより難しい
例1) What's the weather in Springfield?
Springfield はメジャーな街の名前、あちこちにある
ユーザーはどこの Springfield のことを言っている?
正しく理解するにはコンテキストを適用しないといけない
例えば、ユーザーはミズーリ州の Springfield に住んでいるとか
でもこれは正しくない可能性がある
もし Springfield に住んでいるならこの聞き方はおかしい
what's the weather here とか what's the weather like today のように聞くだろう
他の Springfield のことを意味している可能性がある
例2) Play Yesterday.
Yesterday は Song? Movie? Playlist? Audiobook? game の可能性もある
同じようにコンテキストとかユーザーについて知っていることを適用して、song を探しているだろうとする
でもここで別の問題が出てくる
Yeasterday は
Original version by The Beatles?
Cover version by Boyz II Men?
Some other cover version ?
他にもいろいろある、どれかなんてわからない
いくつか戦略がある
acknowledge ambiguity あいまいさを認めよう
会話デザインの戦略
余計なステップではと思うかもしれないが、間違いを犯すよりはステップを経るほうがよりよい
可能ならユーザーの選択を覚えておいて次回に生かす
Users in context
会話デザインの戦略
機会を活用する例
ユーザーの視点
"What's my current balance?"
"How much do I owe?"
"What was my last transaction?"
"I need to make a payment"
"Transfer $100 to Joe's account"
"Whan does my policy expire?"
例) Retail
"Is my order on the way?"
"What's your return policy?"
"Do you do same-day delivery?"
"Do you carry milk?"
"Any headphones on sale?"
"I need to re-order cat food"
例)Healthcare
"Is my prescription ready?"
"Can I get a flu shot?"
"Any appointments available?"
"I need to schedule a follow up"
"Leave a note for my doctor"
"Refill my prescription"
例)Fun & Wellness
"What's my horoscope?"
"Got any good dad jokes?"
"Read me a story"
"Let's play a game"
"I need to meditate"
g.co/dev/ActionsDesign
Vision : Google Assistant は IoTデバイスとのインテリジェントなやりとりを促進する中心的存在になる
Conversations > Commands
コマンドを覚えるのではなく、人に話すように操作できる
Home Graph
家のなかの device の contextual data を保存し Google Assistant に提供する
Structure
2つのフローがある
device 登録フロー
基本的な device type を用意しており、より多くの device type が今後数週間から数ヶ月で作られる
これらの device type は traits で構成される
traits は実際に行われる機能
例えば最も簡単な trait は on/off
g.co./dev/ActionsDesign/
Checklist: Your calls to action
エラーを防ぐために質問を工夫する
DialogFlow では agent は intent で構成される
intent はユーザー特定の入力とコンテキストに対応する
Repeat - 「Pardon?」
Help - 「I'm not sure.」
Quit - 「I have to go!」
なんどもエラーのやり取りを繰り返すとユーザーはすぐに離脱してしまう
エラーを防ぐように準備すること
I don't know は通常は Help に割り当てられるべきだが、予想ゲームをしているときは give up と解釈するべき
Conversation Helpers
DialogFlow 以外にも sdk で複数指定することもできる
必要に応じて default fallback intent を書き換える
より context に沿った内容に
fallback intent に到達した回数を数えて、prompt の内容を変える
もし回数が閾値(ユーザーペルソナによる)を超えたら会話を終了する
ペルソナを作るべき、すごく役に立つ
コンテキストを維持して流れを強化する
handle できなかった query を intent に割り当てることができる
number genie のサンプルをチェックする
優秀な Assistant というのは情報を与えるだけではなく、get things done を助ける
そのために ecosystem のパートナーが必要だった
Assistant での transaction とは何か?
food delivery やコンサートチケットを購入できる
美容院やレストラン、ヨガクラスなど、予定の予約ができる
なぜこれが重要なのか
ユーザーが脱落するハードルがたくさんある
2つの大きなポイントが payments と identity
お店に行って買うとする
ほしいものが売り切れていたり、少し前にセールしていたかもしれない
アプリで買うとする
アプリをダウンロードしないといけない
支払い情報、住所な電話番号なども入力しないといけない
最後にカートに入れて支払いをする
悪くないけど、これをアプリごとにやるとしたら大変
デバイスを変更したらその度にダウンロードしてサインインしないといけない
Google はすでにユーザーの payment と identity 情報を持っているので、その情報を渡すことで transaction を容易にすることができる
ハードルが下がるということは、より多くの購入につながる
3つの主な機能
Payments
1/3のユーザーがクレジットカードの入力中に購入を諦める
Google Play で買い物をしたことがあるユーザーならクレジットカード情報が Google Account に登録されている
Google は Assistant での transaction 中に payment 情報を安全に渡すことができる
処理の流れ(以下でのアプリは自分の Assistant app のこと)
もし顧客がすでに支払い方法を持っている場合(例えば会員カードやポイント)、それらを使うこともできる
Identity
54%のユーザーがアカウント登録処理で離脱する
ログイン情報を忘れたユーザーの90%が離脱する
Assistant App でログインまたはアカウントの作成が必要になった場合、Google はユーザーの identity 情報をもっているので、それを使って簡単にログイン・アカウントの作成を行うことができる Google アカウントの選択と確認の2タップで終わる
eye-free, hands-free なシナリオでも可能、Google Home は Link されているユーザーがいればそれを使うので Google アカウントの選択をスキップできる
ユーザーが Google アカウントを選択したら、Assistant App に identity token を送る
token にはユーザー名やメールアドレスなどの情報が含まれる
アプリはこれらの情報を使って対応するアカウントが存在しているかチェックする
アカウントがない場合、ユーザーの Google identity を使って作成することができる
Google Sign in と同じような処理 アカウントがすでにある場合、アカウント作成プロセスを完了するために追加の OAuth 2 script を許可するか、任意でユーザーに聞くことができる
Re-engagement
transaction はユーザーが支払ったときに終わるわけではない
ユーザーはオーダーに対して質問があるかもしれない
今どこにある?現在のステータスは?配送される前に他のものを追加できる?
ユーザーはオーダーに対して何か行動したいかもしれない
アップグレードしたい、ドライバーと話したい、金曜日に変更したい
Google Assistant が全ての transaction 履歴を一箇所で管理する
ユーザーの follow-up アクションへの導線をわかりやすくできる
オーダーの update を提供できる
ユーザーはそのためのアプリをいれたり、メールを探さなくてよくなる
Transaction Helpers
Google I/O 2017 の Actions on Google のセッション を見て Bagel のサンプルを試してみたりしていたのですが、しばらく遊ばないでいたら API.AI の名前が変わって DialogFlow になっていました。Google Home が日本でも発売され、いまこそ絶好の Actions on Google 始めどきですね。
最初、11月に新しく増えた機能のことを書こうかと思っていたのですが、Google I/O のセッションのまとめが下書きのまま放置されていたので、この機会に書き上げました!どうぞ!
* 注意: セッション中の API.AI は DialogFlow に置き換えてあります。
* 注意: Google I/O 2017 時点の情報なので、もしかしたら現在変わっている部分があるかもしれません。
Building apps for the Google Assistant
Google Assistant でも ecosystem を作る
ecosystem のキモは Actions on Google という platform
- 1. Actions Console でプロジェクトを作る
- 2. DialogFlow を使って自然言語を処理する
- 3. webhook につなぐ
- 4. web simulator かデバイスでテストする
- 5. mobile phone 向けに拡張する
- 6. Transactions API と integrate する
ユーザーが Google Home に「Ok Googke, talk to Google IO 17」と話す
↓
- Assistant で text に transcribe され
- NLP(自然言語処理)され
- Knowledge Graph を使った Ranking を適用し
- ユーザーのコンテキストを使ってユーザーのクエリを解釈し
- I/O アプリが適切なサービスだと判別する
- Assistant は I/Oアプリの DialogFlow agent を起動し、DialogFlow agent は対応する intent (定義済み)がどれか理解する
- DialogFlow はビジネスロジックを実行するために web hook を呼び出し結果を受け取る
- 会話のレスポンスを公式化
- DialogFlow から Assistant に送り返され、ユーザーに出力される
- Google I/O 2017 で公開
- metadata と directory listing, ブランディング情報の設定
- Analytics
- Simulator
- natural language tool
- intents と entities を定義する
- intents
- user query のモデル
- 構造化された方法でユーザーが何をしたいかを理解できる
- entities
- 文字列の構造化されたパケット
- ユーザーのリクエストから意味を取り出す
応答を書く
3. Use Training to improve
基本的なモデルを設定(ユーザーの会話例を入力)したら、DialogFlow で training を使って会話モデルを改善する
「What Android sessions are there?」
↓
list session intent にマッピング
↓
webhook
ListSessions(session_tags.TRACK_ANDROID)
const App = require('actions-on-google').ApiAiApp;
function listTopicsIntent(app) {
GoogleIOAPI.getCategories().then(
categories =>
app.ask("The topics covered are: ${topics}. What do you want to learn?")
)
}
exports.myApp = function(request, response) {
var app = new App({ request, response });
app.handleRequest(listTopicsIntent);
};
npm install actions-on-google
4. Web Simulator でテスト
5. mobile 向けに拡張する
API.AI ではビルドインの作成機能があり、 cards, chips, carousels, lists を簡単に定義できる
webhook に request を投げて動的な response を生成することも可能
// screen があるかチェック
if(app.hasSuraceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) {}
app.ask(app.getIncomingRichResponse()
.addBasicCard(app.buildBasicCard(sessionData.desc)
.setTitle(sessionData.title)
.addButton('Google IO Schedule', sessionData.url))
);
}
6 Transaction API
- Google I/O 2017 で developer preview としてリリース
- 購入とオーダーが可能
- seamless, build-in experience を提供
- payments と identity information の共有
- receipts の発行による reengagement
配送料を決めたり、サービス可能かどうかを判定するために delivery address などが必要になることがあり、これらをリスエストする API が用意されている
2. オーダーの設定が終わったらユーザーに propose して authorization を取得する必要がある
- propose order API を使う
- ユーザーに購入を聞いて approve してもらう
- order が active かどうか知らせ、レシートを送るため
- confirm order API を使う
- OAuth 2 Web server と統合する機能を提供
- seamless log in flow をユーザーに提供
let order = app.buildOrder()
.setCart(app.buildCart()
.addLineItems(app.buildLineItem(session.title))
.setNotes(session.description))
.setTotalPrice(app.Transactions.PriceType.ACTUAL, 'USD', 0)
app.askForTransactionDescription(order);
let orderUpdate = app.buildOrderUpdate(orderId)
.setOrderState(app.Transactions.OrderState.CONFIRMED, "Booked!")
.setReceipt("io-seat-order-" + orderId)
app.tell(app.buildRichResponse()
.addSimpleResponse("Thanks! Here's your receipt.")
.addOrderUpdate(orderUpdate));
自分の Assistant App へ繋げるには
- ソシャルメディアを通してシェアする
- 自分のサイトやアプリを通してプロモートする
- Assistant app へのトラフィックが増えるようプレスを奨励する
- 会話中に
- Explicit triggering(暗黙的なトリガー)
- Implicit triggering(明示的なトリガー)
- Assistant Directory
- シェアされたリンクから
Bringing the Google Assistant to any device
Google Assistant SDK
- I/Oの3週間前にリリース
- gRPC API
- Docs and samples
- Tools
- Hotword support ("Ok, Google ...")
- Timers and alarms
- Enhanced device control (Coming soon)
- Respberry Pi 3B
- One or optimally Two microphone
- Speacker
- Internet Connection
start()
set_microphone_mute(boole is_muted)
start_interaction() # To initiate with button instead of hotword
Library Events
- ON_START_FINISHED
- ON_CONVERSATION_TURN_STARTED
- ON_CONVERSATION_TURN_TIMEOUT
- ON_END_OF_UTTERANCE
- ON_RECOGNIZING_SPEECH_FINISHED
- ON_RESPONDING_STARTED
- ON_RESPONDING_FINISHED
- ON_NO_RESPONSE
- ON_INTERACTION_FINISHED
- ON_ALERT_STARTED
- ON_ALERT_FINISHED
- ON_ASSISTANT_ERROR
- ON_MUTED_CHANGE
- Client/server API - Remote Procedure Call
- Minimal Processing
- Minimal Power reqirements
- Runs on most platforms, OS's Programming Languages
- Open-Source Sample Code
Runs on nearly any platform with a TCP/IP socket.
- Bi-directional streaming:
- streams audio up and streams audio down - fo minimal latency
- Robust transport over HTTPS/2
- Open protobuf description on Github
- gRPC is free and open-source
- Platforms: Linux, MacOS, Windows, Android, iOS
- Languages: C/C++/C#, Go, Python, Node.js, Ruby, and more
- Open toolchain to generate bindings in any language
Connect
channel_factory = ChannelFactory(api_host, credentials)
service = EmbeddedAssistantStub(channel_factory.make_channel())
response_stream = service.Converse(request_stream(), DEADLINE_SECS)
return handle_response_stream(response_stream)
Configure
def _create_config_request(self):
audio_in_config = embedded_assistant_pb2.AudioInConfig(
encoding='LINEAR16',
sample_rate_hertz=16000,
)
audio_out_config = embedded_assistant_pb2.AudioOutConfig(
encoding='OPUS_IN_OGG',
sample_rate_hertz=22050,
)
converse_config = embedded_assistant_pb2.ConverseConfig(
audio_in_config=audio_in_config,
audio_out_config=audio_out_config,
)
return embedded_assistant_pb2.ConverseRequest(config=converse_config)
Send Config and Streaming Audio
def request_stream(self):
yield self._create_config_request()
while True:
data = self._audio_queue.get()
if not data:
return
yield self._create_audio_request(data)
Receive and Process Streaming Results
def _handle_response_stream(self, response_stream):
for resp in response_stream:
self._response_audio += resp.audio_out.audio_data
if resp.event_type =- ConverseResponse.END_OF_UTTERANCE:
self._end_audio_request()
if resp.result.mucrophone_mode:
_dialog_follow_on = (resp.result.microphone_mode == ConverseResult.DIALOG_FOLLOW_ON)
Neural Beamforming
- One, or optimally Two microphones
- Server-side Neural Beamforming
- Farfield
- Noise robust
- Machine Learning
- Minimal client-side processing and cost
- https://developers.google.com/assistant/sdk
- https://aiyprojects.withgoogle.com/voice/
- Actions on Google Deve_loper Challenge
Building Rich Cross-Platform Conversational UX with API.AI (Google I/O '17)
DialogFlow(Google I/O 2017 時点)
- 150K+ developers
- 14 languages
- 32 platform SDKs and integrations
- Intents : サポートしたい action の種類
- Entities : Intents で使われる objects
- Default Fallback Intent : システムが理解できない場合 Fallback Intent がトリガーされる
- Default Welcome Intent : agent が開始されるときにトリガーされる
- analytics service
- request early access
- https://dialogflow.com/
- https://dialogflow.com/docs/getting-started/basics
- https://chatbase.com
- https://codelabs.developers.google.com/?cat=Assistant
- g.co/actionsdev
Defining Multimodal Interactions: One Size Does Not Fit All (Google I/O '17)
このセッションは音声レスポンスの Good, NG 例が紹介されていてよかったです。TV と Auto の話もあります。マルチモダリティ (multimodality) に影響する要因
- 動き (Motion)
- 環境 (Environment)
- 近さ (Proximity)
- 音声機能 (Audio capability)
- 視覚的機能 (Visual capability)
- 読まない、聞く
- 情報の過負荷を避ける
- 質問に答える
- あるモードが終わったら、別のがそれを引き継ぐ
- 一番強いモードに最適化するが、両方を許可する
- 各モードの強みを活かす
- モダリティ間に冗長性を持たせる
- 各モードの強みと弱みを知る
- 一番強いモードに最適化するが、両方を許可する
- 読まない、聞く
- 短く、良いものにする
-
- ユーザーの環境と条件への自動的な適応
- 複数の Surface (Home, Phone ...) 体験の関連
- より複雑なマルチモーダルインタラクション
Finding the Right Voice Interactions for Your App (Google I/O '17)
会話デザインの話です。例がたくさん出てきてとてもよかったです。なぜ音声なのか?
- 速度
- 簡易・簡潔
- 普遍性 (Ubiquity)
音声は究極的に便利
会話デザインの戦略
- 1. ユーザーと関連する日常の言語を使う
- 2. 簡単に答えられる質問をする
- 3. 簡単に再呼び出しできるような情報構造
- ユーザーが言ったことを認識する
- ユーザーが意図していることを理解する
Keynoteでは deep learning の適用によって 4.9% になってことが発表された
一方で、ユーザーが意図していることが何かを"理解"することはより難しい
例1) What's the weather in Springfield?
Springfield はメジャーな街の名前、あちこちにある
ユーザーはどこの Springfield のことを言っている?
正しく理解するにはコンテキストを適用しないといけない
例えば、ユーザーはミズーリ州の Springfield に住んでいるとか
でもこれは正しくない可能性がある
もし Springfield に住んでいるならこの聞き方はおかしい
what's the weather here とか what's the weather like today のように聞くだろう
他の Springfield のことを意味している可能性がある
例2) Play Yesterday.
Yesterday は Song? Movie? Playlist? Audiobook? game の可能性もある
同じようにコンテキストとかユーザーについて知っていることを適用して、song を探しているだろうとする
でもここで別の問題が出てくる
Yeasterday は
Original version by The Beatles?
Cover version by Boyz II Men?
Some other cover version ?
他にもいろいろある、どれかなんてわからない
いくつか戦略がある
acknowledge ambiguity あいまいさを認めよう
会話デザインの戦略
- 1. あいまいさを認め、ユーザーに聞く
- 2. ユーザーの選択を覚えておいて、次回に活用する
余計なステップではと思うかもしれないが、間違いを犯すよりはステップを経るほうがよりよい
可能ならユーザーの選択を覚えておいて次回に生かす
Users in context
- 手が離せない (Hands-busy)
- 目が離せない (Eyes-busy)
- マルチタスク中
- プライベートな場所にいる
- 家族と共有している場所にいる
- 教える必要がない (Instant experts)
- 高い予想
- 許容誤差が小さい
会話デザインの戦略
- 1. 例外に対する戦略を開発するのに時間をかける
- 2. ユーザーが元のフロー (track) にすごく簡単に戻ってこれるようにする
- 3. 日常の会話で使用するテクニックを活用する
機会を活用する例
ユーザーの視点
- 自分の質問に答えてほしい
- 何かするのを助けてほしい
- トラック上にキープしてほしい
- やることを教えてほしい
- 素早い回答
- ひっかかりのないやりとり
- 関連する提案
"What's my current balance?"
"How much do I owe?"
"What was my last transaction?"
"I need to make a payment"
"Transfer $100 to Joe's account"
"Whan does my policy expire?"
例) Retail
"Is my order on the way?"
"What's your return policy?"
"Do you do same-day delivery?"
"Do you carry milk?"
"Any headphones on sale?"
"I need to re-order cat food"
例)Healthcare
"Is my prescription ready?"
"Can I get a flu shot?"
"Any appointments available?"
"I need to schedule a follow up"
"Leave a note for my doctor"
"Refill my prescription"
例)Fun & Wellness
"What's my horoscope?"
"Got any good dad jokes?"
"Read me a story"
"Let's play a game"
"I need to meditate"
g.co/dev/ActionsDesign
Home Automation with the Google Assistant (Google I/O '17)
Vision : Google Assistant は IoTデバイスとのインテリジェントなやりとりを促進する中心的存在になる
Conversations > Commands
コマンドを覚えるのではなく、人に話すように操作できる
Home Graph
家のなかの device の contextual data を保存し Google Assistant に提供する
Structure
- Address
- Managers
- Rooms
- Labels
- Devices
- Labels
- Devices
- Type
- Traits
- Attributes
- Labels
- State
- living room に light はある? - ある
- light は点いてる ? - 点いてる
- 現在の明るさは? - 50%
- little bit ってどのくらい? - デフォルトの設定による、little bit や a lot を support してるかも、いずれにしろ値を決める、例えば 3%
- Google Assistant に最終的なデータ(47%にする)を渡す
- Google Assistant が light の明るさを変更する
- Actions on Google を使って build
- device を直接 Google Graph に登録
- パートナーはクラウドサービスの薄いレイヤーを追加する
- Google が言語の理解、Home Graph、各種デバイスの詳細なハンドリングを提供する
2つのフローがある
device 登録フロー
- 1. アプリを登録すると、Smart Home Apps の一覧で出てくるようになる
- 2. ユーザーがそれをクリックしたら、アプリの OAuth registration を呼び出す
- 3. 戻ってきたら device に関するリクエストを送るので、ユーザーが持っている全てのデバイスの情報を JSON で返す
- 4. 状態は Home Graph で持つのでアプリ側で持つ必要はない
- 1. ユーザーの音声入力を解析し、結果を単純な JSON にして Agent に送る
- 2. Agent が IoT デバイスを操作し、結果を返す
基本的な device type を用意しており、より多くの device type が今後数週間から数ヶ月で作られる
これらの device type は traits で構成される
traits は実際に行われる機能
例えば最も簡単な trait は on/off
Getting Your Assistant App Discovered (Google I/O '17)
Assistant App をユーザーに見つけてもらうようにするには、という話g.co./dev/ActionsDesign/
Checklist: Your calls to action
- ブランド認定を行う : ウェブサイトやアプリ (soon) と関連づける
- 有益なディレクトリリストを作成する : 実行可能な会話の例を出す
- フレーズを追加する
- 人々がまた使いたいと思うアプリを作る!
In Conversation, There Are No Errors (Google I/O '17)
14:15 からのデモがよいです。エラー時に何が起こるかがよくわかります。エラーを防ぐために質問を工夫する
DialogFlow では agent は intent で構成される
intent はユーザー特定の入力とコンテキストに対応する
Repeat - 「Pardon?」
Help - 「I'm not sure.」
Quit - 「I have to go!」
なんどもエラーのやり取りを繰り返すとユーザーはすぐに離脱してしまう
エラーを防ぐように準備すること
I don't know は通常は Help に割り当てられるべきだが、予想ゲームをしているときは give up と解釈するべき
Conversation Helpers
- AskForSignIn
- AskForOption
- AskForDateTime
- AskForConfirmation
- Transactions related system intents (delivery address, payments)
DialogFlow 以外にも sdk で複数指定することもできる
必要に応じて default fallback intent を書き換える
より context に沿った内容に
fallback intent に到達した回数を数えて、prompt の内容を変える
もし回数が閾値(ユーザーペルソナによる)を超えたら会話を終了する
ペルソナを作るべき、すごく役に立つ
コンテキストを維持して流れを強化する
- ランダムな prompt のリストを作る
- 連結を駆使して多くの選択肢を用意する
- 動的な値を活用する
- 必要に応じてトラッキングとスキップを活用し、繰り返しを避ける
- エラーの数や種類を覚えておいて prompt を調整する
handle できなかった query を intent に割り当てることができる
number genie のサンプルをチェックする
Transactions with the Google Assistant (Google I/O '17)
Google Assistant (Actions on Google) で Transaction を行う話です。途中でコードサンプルがたくさんでてきます。優秀な Assistant というのは情報を与えるだけではなく、get things done を助ける
そのために ecosystem のパートナーが必要だった
Assistant での transaction とは何か?
food delivery やコンサートチケットを購入できる
美容院やレストラン、ヨガクラスなど、予定の予約ができる
なぜこれが重要なのか
- ユーザーにとって良いから
- 情報と行動のギャップを埋める
- 開発者にとって良いから
- 新しいユーザーが見つけられる
- 既存のユーザーを re-engage できる
- Actions on Google app でマネタイズできる
ユーザーが脱落するハードルがたくさんある
2つの大きなポイントが payments と identity
お店に行って買うとする
ほしいものが売り切れていたり、少し前にセールしていたかもしれない
アプリで買うとする
アプリをダウンロードしないといけない
支払い情報、住所な電話番号なども入力しないといけない
最後にカートに入れて支払いをする
悪くないけど、これをアプリごとにやるとしたら大変
デバイスを変更したらその度にダウンロードしてサインインしないといけない
Google はすでにユーザーの payment と identity 情報を持っているので、その情報を渡すことで transaction を容易にすることができる
ハードルが下がるということは、より多くの購入につながる
3つの主な機能
- 1. payments
- 2. identity
- 3. Re-engagement
Payments
1/3のユーザーがクレジットカードの入力中に購入を諦める
Google Play で買い物をしたことがあるユーザーならクレジットカード情報が Google Account に登録されている
Google は Assistant での transaction 中に payment 情報を安全に渡すことができる
処理の流れ(以下でのアプリは自分の Assistant app のこと)
- 1. 特定のユーザーに特定の額のチャージが必要なことをアプリから Google に伝える
- 2. Google はアプリの payment prosessor's key を使って生の credit card 情報を暗号化する(payment prosessor's key はアプリ specific)。これにより暗号化された payment credential が生成される
- 3. Google は暗号化された payment credential をアプリに渡す
- 4. アプリは通常の debit card やクレジットカードのように credential に対して課金する
- Convenient
- ユーザーは single tap で支払いできる
- Free
- Google はこれに関して手数料を取らない
- アプリは payment processor に対する通常の processing fee だけ払えばよい
- Lightweight
- payment credential が必要なのは runtime 時だけ
- 通常の credit card と同じように課金できる
- Supported
- stripe
- Braintree
- vantiv
- Coming soon(in the next several months)
- ACI UNIVERSAL PAYMENTS
- adyen
- First Data
- worldpay
もし顧客がすでに支払い方法を持っている場合(例えば会員カードやポイント)、それらを使うこともできる
Identity
54%のユーザーがアカウント登録処理で離脱する
ログイン情報を忘れたユーザーの90%が離脱する
Assistant App でログインまたはアカウントの作成が必要になった場合、Google はユーザーの identity 情報をもっているので、それを使って簡単にログイン・アカウントの作成を行うことができる Google アカウントの選択と確認の2タップで終わる
eye-free, hands-free なシナリオでも可能、Google Home は Link されているユーザーがいればそれを使うので Google アカウントの選択をスキップできる
ユーザーが Google アカウントを選択したら、Assistant App に identity token を送る
token にはユーザー名やメールアドレスなどの情報が含まれる
アプリはこれらの情報を使って対応するアカウントが存在しているかチェックする
アカウントがない場合、ユーザーの Google identity を使って作成することができる
Google Sign in と同じような処理 アカウントがすでにある場合、アカウント作成プロセスを完了するために追加の OAuth 2 script を許可するか、任意でユーザーに聞くことができる
Re-engagement
transaction はユーザーが支払ったときに終わるわけではない
ユーザーはオーダーに対して質問があるかもしれない
今どこにある?現在のステータスは?配送される前に他のものを追加できる?
ユーザーはオーダーに対して何か行動したいかもしれない
アップグレードしたい、ドライバーと話したい、金曜日に変更したい
Google Assistant が全ての transaction 履歴を一箇所で管理する
ユーザーの follow-up アクションへの導線をわかりやすくできる
オーダーの update を提供できる
ユーザーはそのためのアプリをいれたり、メールを探さなくてよくなる
Transaction Helpers
- buildCart()
- askForDeliveryAddress()
- getDeliveryAddress()
- buildOrder()
- askForTransactionDecision()
- getTransactionDecision()
- buildOrderUpdate()
- getUser()
- askForSignIn()
- getSignInStatus()
登録:
投稿 (Atom)

























































































































