Image for post
Image for post

CREグループの橋本です。

年度末も迫る中、去る3/25(水)に、 Customer Support Tech Meetup#1 というオンラインイベントを開催しました。

弊社CRE主催のイベントで、CSにまつわる様々な改善事例の発表やオンライン上で頂いたコメントを介した質疑応答も行い、充実した内容にすることができました。

私も僭越ながら、トリで発表させていただきました。スライドは下記のspeakerdeckに掲載しております。

発表に関して

今年度1年間かけて、これまでの弊社でのCS対応において課題に感じた点を包括的に解消できるようなCRMツールの開発を進めてきました。

イベントに参加いただいた方には繰り返しの内容にはなりますが、発表では

  • ミクシィCSが問い合わせ対応で抱える課題
  • 課題解決のため1年かけてCRMツールの開発を行ったので、1年間の開発プロセスやツールの主要な画面のご紹介
  • CRMツール導入後の効果と今後の展望

といった内容をご説明させていただきました。詳細につきましては、上のspeakerdeckのスライドや、イベント発表時の映像がアーカイブで残っております(冒頭のconnpassの概要欄からご参照いただけます)ので、もしよろしければご覧ください。

また発表では、技術面であまり突っ込んだ内容をご紹介しておりませんでしたが、開発にあたり、ナレッジの階層構造の管理においてRDBの設計で苦労した部分もありました。

そちらにつきましては、昨年末にMediumでご紹介しておりますので、興味がありましたらご参照いただけると幸いです。

質疑応答の補足

イベントでは、Twitterで頂いたコメントを中心にいくつかの質問に回答させていただきましたが、時間が限られていたこともあり、すべての内容にしっかりと回答することができませんでした。

そのため、こちらのエントリにていくつか補足をさせていただこうと思います。

ありがとうございます。

Zendeskに特化した内容となりますが、Zendeskのチケットは

  • 1度送信したメールに対し、再度返信を行うと1つのチケットとして取り扱われる
  • 返信ではなく、改めて再度メールを送信すると別のチケットとして取り扱われる

といった仕様があります。

ユーザーさんからは、「同じ内容に関する話題」について上記のいずれかのパターンで複数回問い合わせをいただくことがあります。

これは私自身の現時点での見解になるのですが、再オープンか新規かといった概念で捉えるよりかは、「ユーザーさんから問いかけられた1つの話題に対し、1度返信を行った」という単位でお問い合わせをひとまとまりとして扱うのが良いのではないか、と考えております。

しかしながらZendeskにはそのような仕組みがないため、今回開発したCRMツールでは、そういったまとまりで問い合わせを確認できるよう、いくつかの機能でカバーを行っております。

この機能については次の発表の機会がありましたら、その際にご紹介させていただこうかと考えております。

この点は本当に仰るとおりです。弊社では、今回の発表でご紹介したCRMツールやZendeskに限らず、ユーザーさんのサービスのご利用状況を確認するために、複数の管理ツールを駆使して問い合わせ対応にあたっています。

CSスタッフは複数の問い合わせ案件を並行して進めるケースも少なくなく、そういった場合にオペレーション上のミスが発生しやすくなるのもまた事実です。

CRMツールの今後の展望として、そういったミスを防ぐための仕組みも取り入れていきたいと考えています。

Zendesk上での誤送信防止の枠組みについては既に導入済でして、そちらも過去にqiitaにてご紹介させていただいた記事があります。

ここだけのこぼれ話

CRMツールのリリースからイベントでの発表まで日が浅かったこともあり、導入後の効果については十分な内容をお伝えできなかったかなと感じています。

改めてCRMツール内に蓄積されたデータの分析を進めていますが、リリースから日が経つにつれて、過去に対応を行ったユーザーさんからの再度のお問い合わせに対応する割合が徐々に増えていることが確認できました。

1人のユーザーさんへの過去の対応状況や、対応にあたったCSスタッフの思考内容がデータとして残っておりますので、これらのデータが今後資産として力を発揮する局面が増えていくのではないか、と推測しています。

もちろんデータとして残しておくだけではなく、そのデータを活用できるよう最適な機能提供を行うことがCREとしての使命になりますので、引き続きCRMツールの利用状況を鑑みつつ、改善を継続したいと思います。

イベントが終わった日は、興奮冷めやらずあまり熟睡することができませんでした。そのため、夜布団の中で、今回のプロジェクトやこれまでのキャリアについて様々考えを巡らせていました。

イベントにて、このような嬉しいコメントを頂きました。

CS対応の改善は「片手間で進める」というのが基本スタンスです。CSに所属している以上、日々のユーザーさんからのお問い合わせに誠意を持って回答し続けることが最重要課題であり、改善というのは優先順位としてはその次に来るものです。

したがって、お問い合わせが急増するとこれらの改善も一旦横において対応で手一杯になる時期も当然あります。それはCSスタッフもそうですし、我々CREもお問い合わせの技術調査で一週間が終わるというケースもザラにあります。

このようにお問い合わせの分量によって、CSスタッフに「余裕がない」時期というのも当然あるため、CS対応の改善は粘り強く取り組まないとなかなか先に進まない、といった実情があります。このため「一人で進める」というのは、実はかなりのエネルギーが必要だったりします。

現部署に所属してからもう少しで6年目に突入します。5年間継続して改善を進めてきた結果、一朝一夕ですぐに完結するといった類の改善施策は、徐々に少なくなってきているのが実情です。

そのため直近では、1つの施策を完遂するまで数年単位の時間を要するケースが多くなってきており、具体的な効果が出ないまま半期が終わる、という時期もあります。実際今回の開発においても、昨年の上半期には特段CSの現場に良い効果をもたらすまでは至らず、道半ばの状況でした。

「長期的に見て効果があると確信しているからこそ、少しずつ着実に施策を進められる」というのが自分自身の開発スタンスの根底にあるのだな、と今回の発表を通じ実感しました。

今回のCRMツールの開発もリリースこそしましたが、ここがゴールではなく、また新たなスタートであると考えています。来年度以降も粘り強く、そして着実に改善を継続していきたいです。

おわりに

CRMツールのリリースとイベントの準備が時期的に重なっていたこともあり、今月はとてもバタバタした1ヶ月間となりました。無事にイベントも盛況に終わり、またご参加いただいた皆様からの反応が更に次の開発へのモチベーションへとつながっているなと感じています。

引き続き1歩1歩、着実によりよいCS対応をお届けできるよう、技術面からサポートしていきたいと思います。


Image for post
Image for post

ミクシィグループアドベントカレンダー14日目になりました。

前日の @m77so さんに続き、CREグループの橋本がアドベントカレンダーをお送りします。

最近業務で階層構造のデータをRDBで実装する機会があったので、今日はその際に設計したアルゴリズムについて書いていこうと思います。

なお実装時の開発言語はruby、ORMはActiveRecord、RDBはmysqlを採用しており、本エントリに記載の擬似コードはそちらに沿って執筆しています。

データ構造について

Image for post
Image for post
図1: 必要な要件を満たすデータ構造

図1に実際に設計したデータ構造を大まかにまとめました。必要な要件としては下記のとおりです。

  • カテゴリは特定のプロダクトに属する(複数プロダクトにはまたがらない)
  • カテゴリには階層があり、2階層目以下のカテゴリは親となるカテゴリが存在する
  • カテゴリは任意の階層まで作成でき、親となるカテゴリは自由に変更ができる
  • カテゴリを削除した場合、子階層以下のカテゴリは連動して削除される

カテゴリはOSのファイルシステムと同様の階層構造を満たしており、 加えてプロダクトという1つ上位の概念に内包される構造になっています。

既存のruby製ライブラリには隣接リストモデルや経路列挙モデル、入れ子集合モデル、閉包テーブルモデルといったアルゴリズムを利用したものがありますが、今回ライブラリは使用せずに実装しました。理由として

  • カテゴリの1つ上のプロダクトという概念についても考慮した設計にする必要があり、既存のライブラリを使用しながらの実装は何らかの制約を生む可能性があるため
  • システムの仕様上、カテゴリへのアクセスがRDBの負荷のボトルネックになる可能性があり、負荷の状況に応じてビジネスロジックやテーブルの改善を柔軟に行えるようにするため

といった点があり、今後の開発の拡張性を考慮すると全て自前で実装しておいたほうが見通しが良いと判断したためです。

ER図

設計したテーブルの概要をER図にしてみました。今回アルゴリズムの説明に使用するのは categories , category_relations のテーブルです。以降の説明では、ActiveRecordに即したmodelとして記載し、 Category, CategoryRelation と表記します。

Image for post
Image for post
図2: 設計したテーブルのER図

CategoryRelation はカテゴリ間の関連を規定するために用います。ロジックとしては閉包テーブルモデルに近いのですが、異なる点として category_iddescendant_category_id


Image for post
Image for post

CREグループの橋本です。

CREグループの日常の業務の中の1つに「CSスタッフ向け管理ツール(以下、管理ツールと表記します)の保守・運用」があります。CREグループは社内では横軸の組織として位置しているため、多種多様なプロダクトの管理ツールを保守運用しており、現在片手で収まらない数まで増えてきています。

社内では新規プロダクトのローンチが活発に行われており、複数の管理ツールの新規開発が同時に進行する時期もあります。このため、すべてをゼロベースで実装していくとグループのリソースが逼迫しうるという課題がありました。

このエントリでは、上述の課題を解決すべく、コードジェネレータを自作して新規プロダクト向け管理ツールの開発工数を削減した話を書いていきます。

コードジェネレータの仕組み

管理ツールを構成する機能は、大きく2種類に分けることができます。

(1) プロダクト固有の機能

  • ユーザーの利用データの参照や更新

(2) どの管理ツールにも含まれる共通の機能

  • CSスタッフのアカウント管理
  • ログイン機構
  • 権限管理(アカウントごとに利用可能な機能を制御)

コードジェネレータはこの2種類の機能のうち、後者の「共通の機能」に関する実装について再利用可能にしたものです。弊社ではミドルウェアにPhoenixを採用したプロダクトのローンチがここ数年続いていたため、今回Phoenix向け管理ツールのコードジェネレータを作成しました。

Image for post
Image for post
コードジェネレータの概要

コードジェネレータのざっくりとした構造を図にまとめました。Elixirで実装しており、ImporterとExporterの2つの機能を備えています。

既存のプロダクトのソースコードを取り込み、テンプレート化する機能です。設定をyamlに記述し、mix taskにyamlを渡すことで実行できるようにしました。

yamlで設定する内容は下記の項目を用意しています。

  • product_root_path: 取り込み先のプロダクトのファイルパス
  • path_replacers: 取り込み時のファイルパスの置換ルール
  • template_replacers: テンプレート化する際の文字列の置換ルール

処理の大まかな流れは下記のとおりです。

(1) product_root_pathを元に、読込の必要なディレクトリをFileCrawlerが再帰的に探索してファイルをリストアップ

(2) リストアップされたファイルを元に、取り込みの可否をyes/noのダイアログで確認

※取り込みたい共通機能向けのファイルのみyesを選択

(3) yesで選択されたファイルについて、template_replacersに指定された文字列の置換ルールに沿って文字列をテンプレート変数に変換し、path_replacersに指定された置換ルールに沿ったファイルパスへ保存

※置換ルールはReplacerが生成

既存のソースコードを元にテンプレート化する作業自体はshellコマンドを活用することによっても実現はできますが、共通機能のソースコードは一度実装して終わりというわけではないので、Importerを実装してテンプレートを柔軟に改修できるようにしました。

リファクタリングや依存ライブラリのアップデートなどにより既存のプロダクト内で修正が入った際、Importerを使えばコードジェネレータにもその差分をスムーズに取り込むことができます。これにより、レガシーな状態のテンプレートを新規プロダクトに投入する事態を防ぐことができます。

コードジェネレータのメインの機能で、テンプレート化されたソースコードに対し、設定を元に変数展開した上でmodule化します。Importerと同様設定をyamlに記述し、mix taskにyamlを渡すことで実行できるようにしました。

yamlで設定する内容は下記の項目を用意しています。

  • product_root_path: 展開先のプロダクトのファイルパス
  • path_replacers: 展開時のファイルパスの置換ルール
  • template_variables: テンプレートを展開する際に代入する変数の値

処理の大まかな流れは下記のとおりです。

(1) FileCrawlerが再帰的に探索して適用するテンプレートをリストアップ

(2) リストアップされたテンプレートについて、template_variablesに指定された変数をアサイン

(3) path_replacersに指定された置換ルールに沿ったファイルパスに順次出力

※置換ルールはReplacerが生成

コードジェネレータ導入の効果

現在Phoenixを採用した新規プロダクトのローンチに携わっており、ここで今回実装したコードジェネレータを利用する機会がありました。共通機能に関するコードは自動生成することができたため、Pull Requestをスピーディーに作成することができました。

画像は実際にsubmitしたPull Requestの一覧です。怒涛の勢いでPull Requestがsubmitされていることが分かるかと思います。(ちなみに、この間に追加したコードはおよそ7400行です。)

Image for post
Image for post
新規プロダクトローンチ時の管理ツールに関するPull Request

今年の2月8日から2月18日にかけ、6営業日ですべてのPull Requestがmergeされました。実際に手を動かした作業としてはコードジェネレータ実行後の若干のconfig修正やcommitの整理となっており、ちょうど1営業日でこれらは完了しました。

初めてElixirで管理ツールを実装した際には、この共通機能部分だけで15営業日前後かかっていたことを考慮すると、コードジェネレータによる工数削減の効果は高かったです。

これにより、プロダクト固有の機能開発に余裕を持って対応することができました。

まとめ

コードジェネレータによる工数削減の事例について、概要にはなりますがご紹介させていただきました。仕事に携わる中でどうしても繁忙期というものは避けては通れないものになりますが、繁忙期を乗り切った後一度フラットな視点から忙しさの要因を考察していたところ、今回のツールの着想に至りました。

このコードジェネレータは今後改善したい点が幾つかあり、より良いものにすることができたらまた何らかの形で共有できればと考えています。

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store