トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

トレタCCにAmazonConnectを導入した話

本記事はトレタアドベントカレンダー19日目の記事です。

こんにちは。リングフィットアドベンチャーを2週に1回のペースでプレイしているフロントエンドエンジニアの北川です。

私はトレタのコンタクトセンター事業(以下、トレタCC)のシステム開発全般を担っており、今年は基幹のCTI(電話機)をAmazonConnectへ入れ替えたので、その辺の話を本記事で紹介します。

トレタCCとは

トレタCCは、契約している飲食店の電話業務を代行するサービスです。

トレタって予約台帳なのになんでコンタクトセンターやってるの?と聞かれることがありますが、トレタは飲食店を支援することをミッションとしています。店舗にとっての電話業務とは、予約の電話に限らず当日の道案内や落し物の問い合わせなど、予約以外の多種多様な電話も時間問わずにかかってくるとても業務負荷の高いものです。トレタCCではそれらすべての電話を代行し、飲食店に電話がかかってこない環境をつくることで本来の業務にフォーカスしていただくためのサービスです。

トレタCCのシステムアーキテクチャ

トレタCCで使用するアプリケーションは、代行した店舗の電話をうけるCA(コールエージェント)が利用するシステムです。 トレタの予約台帳への予約の書き込みや、顧客台帳から電話口の顧客情報を表示したりします。また、前述の通り予約以外の電話もあるため、専用のDBヘ電話の対応履歴を登録したりします。

利用技術

  • AmazonConnect
  • Angular
  • Lambda(Node.js)
  • DynamoDB

f:id:mkitagawa-312:20191216132949p:plain

クライアントはAngularで作られたWebアプリケーションになっており、AmazonConnectが提供しているCCP(ContactControlPanel)を埋め込み電話操作を行います。 サーバーサイドはAmazonConnectとの連携を考慮し、Lambdaを主軸にしたサーバーレス構成にしています。AngularへのAPIを提供や、AmazonConnectの問い合わせフロー(IVR)との連携を行います。DBはLambdaとの相性を考慮してDynamoDBを利用しています(先日LambdaにRDS Proxy機能が発表されたので、今ならRDBを使う選択肢もあると思います)。 また、serverlessフレームワークを利用しており、Lambdaのデプロイや、APIGatewayとDynamoDBをオフライン化した状態(serverless-offline, serverless-dynamodb-local)の開発環境を構築しています。

データ分析

AmazonConnectでは、履歴メトリクスとして通話履歴やエージェントの稼働状況の統計画面が標準で提供されています。データ出力も提供されており、トレタCCでの通話履歴とトレタ台帳の予約データを突合させるため、BigQueryでデータの一限管理を行なっています。

  • AmazonKinesis
  • GoogleBigQuery

f:id:mkitagawa-312:20191216133023p:plain

AmazonConnectはAPIを提供していますが、通話履歴のAPIは提供されていないため、AmazonKinesisを設定することで、リアルタイムの通話履歴が取得できます。AmazonKinesisに設定されたLambdaが随時トリガーされ、渡される通話履歴データをBigQueryへ集約しています。また、音声ファイルは自動でS3へアップロードされ、通話履歴内にも音声ファイルのURLが含まれています。

f:id:mkitagawa-312:20191216133038p:plain

AngularとAmazonConnectの連携

電話のインターフェースはAmazonConnectが提供しているCCPを使用しています。また、jsライブラリであるamazon-conect-streamsを使うことで、CCP上での受電などのイベント取得や発信を行うことが出来るようになります。

f:id:mkitagawa-312:20191216133054p:plain (AmazonConnectのCCP)

amazon-conect-streamsでは、エージェントのステータス変更イベントや通話のステータス変更イベントにコールバックが設定できるので、RxJsと組み合わてストリームにしてAngularで扱っています。

export class AmazonConnectAdapter {
  callStatus$ = new BehaviorSubject<CallStatus>(CallStatus.disconnect);
  agentStatus$ = new BehaviorSubject<AgentStatus>(AgentStatus.unavailable);
  availableQueues$ = new BehaviorSubject<Queue[]>([]);
  agentEndpoints$ = new BehaviorSubject<AgentEndpoint[]>([]);
  onInboundCallIncoming: EventEmitter<CallNumbers> = new EventEmitter();
  onInboundCallAccepted: EventEmitter<CallNumbers> = new EventEmitter();
  onOutboundCallConnected: EventEmitter<CallNumbers> = new EventEmitter();

  initialize(ctiElement: ElementRef) {
    this.initCCP(ctiElement);
    this.openLogin();

    connect.agent(agent => {
      this.onAgentCallback(agent);

      agent.onRoutable(() => {
        this.agentStatus$.next(AgentStatus.available);
      });

      agent.onOffline(() => {
        this.agentStatus$.next(AgentStatus.unavailable);
      });

      agent.onAfterCallWork(() => {
        this.agentStatus$.next(AgentStatus.unavailable);
      });
    });
// ...

CCP内で行える機能はほぼamazon-conect-streamsから可能になっているため、今後は自前のUIに切り替える予定です。 逆にCCPではできないこともあり、発信時には発信元が設定できなかったりします。

f:id:mkitagawa-312:20191216133113p:plain (発信用インターフェースの例)

LambdaとAmazonConnectの連携

AmazonConnectはIVR機能である問い合わせフローを管理画面から設定することができ、フロー内でLambda関数を呼び出し、取得した値に応じたフローを書くことが可能です。

f:id:mkitagawa-312:20191216133135p:plain たとえば、コールセンターの業務時間の設定は標準機能である「オペレーション時間」の管理画面から設定することができますが、飲食店では祝日や祝前日は特別なオペレーションを行うことがあるため、自前のオペレーション時間設定をLambda関数から呼び出すことで柔軟に対応を行なっています。

今後の取り組み

AmazonConnectへの切り替えが終わり、現在はAmazonConnectを利用した自動音声応答(VoiceChatBot)に力を入れています。 AmazonConnectの問い合わせフローでの標準機能として、

  • VideoStreamによるリアルタイムな音声データの抽出
  • Lambda呼び出しによる動的なフロー作成
  • AmazonPollyによる動的な文章読み上げ

が可能です。 これらを組み合わせることで、店舗にあった動的な自動応答が可能になります。 また、先日Amazon Transcribeの日本語対応が発表され、文字起こしがさらに容易に行えるようになりました。 テンプレ的な予約の受け答えはBot化し、人にしか対応できない部分はオペレーターが対応するハイブリッドな形を目指していこうと思っています。

おわりに

トレタCCでは、今までにない新しいコンタクトセンターを作っていく仲間を募集しています。 フロントエンドでAngularをゴリゴリやりたい方、Node.jsでサーバーレスをやりたい方、通話内容を分析して機械学習をやりたい方、などなど幅広く募集しております!

www.wantedly.com

お次はインフラエンジニアのなぎらさんです。お楽しみに!

トレタに入社して新鮮に感じていること

この記事はトレタ Advent Calendar 2019 18日目の記事です。

こんにちは。2019年10月に入社したフロントエンドエンジニアの@punipunityanです。好きな動物は犬です!犬の話になると急にテンションがあがります!!特に柴犬が大好きです!!!

入社して3ヶ月弱なので、今日はトレタに入社して新鮮に感じていることを書いてみようと思います。

トレタに入社して新鮮に感じていること

Respect ALL

トレタの VALUE の1つに「Respect ALL」があります。

私は転職する上で、「お互い尊重することを大切にしている環境」で働きたいという理想を持っていました。そしてトレタに入社してみると、この Respect ALL という VALUE が実際に浸透していると感じました。

例えば、自分の担当の仕事をしたとき、自分では当たり前な些細なことをしたという感覚のことでも、すごく「承認してくれている」と感じるような言葉をかけていただくことがありました。そして、それを当たり前のようにチームの皆さんがされていました。

正直、最初はびっくりして少し照れ臭さがあったし、戸惑いました。しかし、やはり一緒に働いている方に喜んでもらえることは嬉しいですし、入社したばかりで緊張している状況の中でも、安心感を得られました。

素直に、惜しみなく感謝の言葉を伝えたり賞賛したりする文化は、本当に素晴らしいと思います。自分も積極的に言葉にしていきたいと思っています。

丁寧なレビューコメント

開発をしていて驚いたことの一つに、PRに対してかなり丁寧なレビューコメントを書いてくださることがありました。その手厚いレビューから日々学ばせていただいています。

自分の発想になかったような指摘をいただいた時は素直に面白いな、なるほどこういう捉え方があるのかと感じますし、疑問に思ったことに関しては腑に落ちるまで丁寧にコミュニケーションをとってくださいます。

その過程で自分自身の引き出しを少しずつ増やしたり、発想を少しずつ柔軟にしていったりする感覚があり、ワクワクしながらレビューコメントを読んでいます。すごく丁寧なコメントを書いてくださっている労力を考えると、感謝しかありません。

色々と指摘を受けるという意味では自分が未熟な点があるということでもあり、反省も多いですが、そこをネガティブに捉えるのではなく、感謝の気持ちと、指摘いただいたことを自分の血肉にして恩返ししていこうというポジティブな気持ちで取り組んでいます。

また、会社によって文化が違ったりするので、「トレタではこういうやり方だよ」という文化を学んでいるフェーズでもあります。

こうした過程で得たものはしっかり吸収して、早く同じ目線に立ち、自分もどんどん価値を生み出していけるようになりたいと思っています。

DDDを実践している

私はこれまでDDDを実践してきたことがなく、数年前に増田さんの「現場で役立つシステム設計の原則-変更を楽で安全にするオブジェクト指向の実践技法」を読んだことがあるくらいの状態です。その時はそれまでやってきた開発スタイルとのギャップから、実際に実践していくイメージがあまりわいていませんでした。

トレタに入社して感じたことは、あらゆる役割の方々がDDDを重要だと捉え、DDDを実践することに積極的だということです。社内ではDDDをテーマとして勉強会が開催されたりしていますし、ユビキタス言語を明確にしようと、気づいたものから一つ一つ議論して決めていくという過程をしっかり踏んでおり、『あ、DDDって実践できるんだ。こうやって実践していくんだな』と思いました。

自分自身、DDDの実践に取り組んでいるところですが、まだキャッチアップ中の状態です。必要性はしっかり理解していると思っていますので、プロダクトに関わるあらゆる方々の幸せのためにしっかり身につけて実践していきたいです。

フロントエンド定例とテックトーク

トレタではフロントエンド定例というのが週一回行われており、そこではそれぞれの進捗報告、課題があればその相談をしています。また、進行中の業務と直接関係あるなしに関わらず、最近気になることがあればその話のシェアなどを行なっています。

最近は毎週成長ポイントの報告をするようになったりして、日々学んだことを改めて言葉にして振り返るようになりました。そこから新たに他の方からコメントをいただけたりもするので学びの多い、私の好きな時間です。

それとは別に、最近社内で始まったテックトークという時間があり、エンジニアが集まって毎回異なった話を持ち回りで行っています。普段自分がやっていることとまた違った学びがあり、楽しみな時間です。また、仕事で直接関わっていないエンジニアの方々が日頃何を考えているのか、どういうことをされているのかというのを知ることができる点でも面白いです。

部活が盛ん

トレタは部活が盛んなようです。 日本酒部、映画部、フットサル部、釣り部など様々な部活があります。

私は元々運動不足で運動したいなと思っていたこともあり、筋肉部に入部しました。筋肉部は毎週火曜日の9:30〜10:00に筋トレの時間があって、詳しい方に教えていただきながら筋トレしています。(と言ってもまだ2回しか参加できていないのですが。。)

体を動かすのは楽しいですし、部活を通して仕事では普段関わらない方ともコミュニケーションが取れるので、そういった機会があるのはありがたいなと思っています。

電車に乗って美味しいものを食べにいく

これまでは同僚の方と飲みに行く時、会社から徒歩圏内のお店にいくことが当たり前でした。

トレタに入社してからは、会社終わりに電車やタクシーで移動して美味しいものを食べにいく機会が増えました。トレタはやはり食へのこだわりが強い方が多いということを感じています。

おわりに

以上、入社3ヶ月目のフロントエンドエンジニアがトレタに入って新鮮に感じていることでした。

私は今、人や環境に恵まれて、ここにいられるのは本当に幸運だなと思っています。これからしっかり貢献していくべく、毎日自分をアップデートする意識で目の前の役割に取り組んでいます。

トレタに興味を持った方は、ぜひ遊びに来てください!

Postmanを使ってAPIテストを行うために最低限理解しておきたい3つのこと

この記事は トレタ Advent Calendar 2019 の17日目です。

はじめまして!QAエンジニアの @gonkm です。先週末17年ぶりに再結成を果たしたバンドのライブに参加して筋肉痛です。

トレタでは、今年ローンチしたToreta nowとトレタの予約台帳のオプション機能のQAを担当しています。本日はToreta nowのテストでよく使っているGUIのAPIクライアントツールであるPostmanについて、最低限理解しておきたい3つのことを書きます。

1. Testsを使ってレスポンスの結果を変数にセットする

APIテストを行っていると、レスポンスの結果を次のリクエストのパラメータとして扱いたいことが多いです。
Postmanではレスポンスが返ってきた後に実行されるTestsという処理がありますので、下記のように記述することで、Postmanの環境変数に値をセットすることが可能です。

let data = JSON.parse(responseBody)

pm.environment.set("starts_from_at", data.reservable_times.starts_time);

pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

2. Pre-request scriptでテストの前の事前処理を行う

これで {{starts_from_at}} という変数を作成できましたが、実際に次に実行するテストでは、この1時間後の値である {{starts_to_at}} という値が必要になります。
Postmanではテスト実行前に実行されるPre-request scriptという処理がありますので、これを使って1時間後の値を作成してみます。

let  an_hour_ago = pm.environment.get("starts_from_at"); 

an_hour_ago = new Date(an_hour_ago);

an_hour_ago.setHours(an_hour_ago.getHours() + 1);

pm.environment.set("starts_to_at", an_hour_ago);

このとおり starts_to_atstarts_from_at から1時間後の値をセットすることができました。

3. テスト実行時に外部ファイルから値を渡し、変数をセットする

テストによっては実行するIteration毎に値を変えたい場合があります。それに伴い、期待する結果が変わる場合もあるでしょう。
前述のPre-request scriptやTestsを使って頑張ることも可能ですが、PostmanでもData-driven testingをサポートするための外部ファイルの取り込み機能があるので、それを使います。

この機能を使って境界値確認のテストを行ってみましょう。
まず下記のようなCSVを用意します。seatsにはテスト条件となる値を、expected_status_codeにはテスト結果として期待するstatus codeを記述しました。
それぞれはPostman内で {{seats}}, {{expected_status_code}} としてパラメータにセットしておきます。

seats,expected_status_code
0,400
1,200
4,200
99,404
100,404

次にPostmanの実行モジュールになるCollectionRunnerを起動し、 Data からcsvファイルをインポートします。
レコード数によってIterationも自動でセットされるので大変気がきいてます。ナイスですね。

f:id:gonkm:20191217082430p:plain

実際の実行結果は以下の通りです。今回はTestsでのstatus_code チェックに {{expected_status_code}} を使っているので、seats の値によって期待の異なるテストを実行することができました。

f:id:gonkm:20191217082332p:plain

最後に

弊社では監視にDatadogを使っているのですが、PostmanProだとDatadogのSynthetics API TestとIntegrationが可能なようなので来年はテストだけでなく外形監視にも使っていければと思っています。

他にもToreta nowではXCUITestを使ったUIテストの自動化を実施したりしています。この先、Androidのテストの自動化やsnapshotテストの導入、アジャイル開発でのQAプロセスの整備など、やりたいことがたくさんあるのですか、如何せん人が足りていません!ご興味のある方は下記よりご応募頂けるととってもうれしいです。よろしくお願いします!

© Toreta, Inc.

Powered by Hatena Blog