Elasticsearchのjoin datatypeを使ってみた
@kaibaと申します。業務委託として手伝わせていただいております。
Stampsでは導入店舗様が様々な条件で、来店してくれたアプリユーザにお知らせ、クーポン、アンケートなどを送ることができます。
サービスが育つとデータ量も増え、RDBMSでは厳しくなってきました。
今回はElasticsearchで高速化する話を書きます。
はじめに。Elasticsearchとは?
ElasticsearchはElastic社がメンテしているOSSの全文検索エンジンです。ドキュメント指向のデータストアで、RDBが苦手とする部分一致検索や複雑な検索を高速に実行できます。Elastic cloud、Amazon Elasticsearch Serviceなどマネージドサービスにもなっていることも多く、環境構築と運用はすごく楽になりました。 RDBと大きく異なるのは「ドキュメント」を扱うことです。ドキュメントとは雑にいうと1つのJSONです。Twitterを例に、ツイートを検索する例を考えると、以下のようなドキュメントを挿入することになります。
{ "id" : "6789", "full_text" : "こんにちは!", "created_at": "2019-01-01T04:00:00", "user" : { "id" : "1234", "name" : "kaiba", "screen_name" : "kaiba", } }
RDBではユーザの情報は別のテーブルに保持して正規化するのが一般的ですが、 Elasticsearchではこのように非正規化した形でドキュメントを保持します。 「screen nameが変更になったら不整合がおきないか」という不安が湧いてきます。 悩ましいところですが以下の案があります。
- screen name変更時にすべてのドキュメントを更新してやる
- サーバのコストや負荷が気になります
- screen nameを保持しないようにする
- screen nameでの検索を諦める
- screen nameからuser IDに変換して検索する
- join datatypeを使う(今回はこれを説明します)
StampsとElasticsearch
Stampsには様々な条件でアプリユーザにクーポン、お知らせ、アンケートなどをお届けできます。 例えば以下のようなケースです。
- 来店頻度によりお客様にクーポンを送る
- イベントに来ていただいたアプリユーザにアンケートをお送りし業務の改善に活かす
- 今月誕生日を迎えるアプリユーザに誕生日お祝いのクーポンをお送りする 条件は複数組み合わせることができ、かなり複雑なクエリになっており、RDBだとどうしても速度と負荷の問題がありました。
設計
Elasticsearchのドキュメントには検索に使用する項目だけを入れるのが一般的です。 今回検索に使用したい項目は大きく分けると以下になります。
- アプリユーザ(以後、単にユーザと呼びます)
来店情報
- ユーザに対して来店情報は複数あり、どのようなindexにするか悩みました。 今回は、join datatypeを使用したのですが、以下も検討しました。
ユーザに来店情報を配列で持たせる
- ユーザが来店すればするほどユーザのドキュメントが肥大化する
- 一部分だけ更新したいのに巨大なドキュメントを更新する必要がでてくるためリソースに不安がある
- 来店情報にユーザ情報を紐付ける
- ユーザの情報が更新されたときに大量のデータを同期する必要がある
- ユーザを探したいのに得られるのは来店情報になってしまい、複雑になる
- indexをユーザと来店情報に分割してしまう
- 2回Elasticsearchに対して検索することになる
- 「人気店のA店に1回以上来店した男性」のようなケースだと、「A店に1回以上来店した」ユーザが大量になるケースがあり、リソースに不安がある
- リクエストの実装が複雑になる
join datatype
Elasticsearchはjoin datatypeを 使用することでドキュメントに親子関係を持たせることができます。 ただ、パフォーマンスに関して以下のような記載があり、ここが気になるところでした。 (僕なりの翻訳になります)
join datatypeはRDBのように使うべきではありません。 Elasticsearchでは非正規化してドキュメントを作ることで高いパフォーマンスが得られています。 join datatypeが意味を成すのは1つのエンティティに対して大量のデータが含まれるケースです。 例えば製品と製品の注文です。この場合、製品を親、注文を子としてドキュメントにすることは適しています。
実際にstagingの数百万のレコードからドキュメントを構築してみたところ、十分なパフォーマンスが得られました!
どのように使うのか?
先程のTwitterの例でjoin datatypeを構築してみます。 今回は「ユーザを検索する」のを目的に、ユーザとツイートが1対多の関係になるように構築します。 以下の形式でドキュメントを1つのindexに入れます。
{ "user" : { ... } }
{ "tweet" : { ... } }
mapping設定です。
{ "mappings": { "users_tweets": { "properties": { "user": { "type": "object", "properties": { "name": { "type": "text" }, "screen_name": { "type": "text" } } }, "tweet": { "type": "object", "properties": { "user_id": { "type": "text" }, "full_text": { "type": "text" }, "created_at": { "type": "date" } } }, "users_tweets_join_field": { "type": "join", "relations": { "user": "tweet" } } } } } }
userとtweetの型情報とusers_tweets_join_fieldに親子関係を定義しています。 ここではuserが親で子をtweetにしています。 続いて挿入するドキュメントを見ます。
{ "user": { "name": "kaiba", "screen_name": "kaiba" }, "users_tweets_join_field": { "name": "user" } }
{ "tweet": { "id": "6789", "user_id": "1234", "full_text": "こんにちは!", "created_at": "2019-01-01T04:00:00" }, "users_tweets_join_field": { "name": "tweet", "parent": "1234" } }
親は自分が親であることを宣言し、子は親のIDを指定しています。
型定義をし、ドキュメントを挿入していきます。
userとtweetのIDをドキュメントのIDとしたいのですが、IDが被る場合を考慮する必要があります。
tweetのIDは tweet + tweet.id
の形式にしました。
curl -X PUT -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets -d @mapping.json {"acknowledged":true,"shards_acknowledged":true,"index":"users_tweets"}% <200b> curl -X PUT -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/1234 -d @user.json {"_index":"users_tweets","_type":"users_tweets","_id":"1234","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1}% <200b> curl -X PUT -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/tweet6789\?routing\=1234 -d @tweet.json {"_index":"users_tweets","_type":"users_tweets","_id":"tweet6789","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1}%
条件なしで検索して確認します。 ユーザとツイートが得られました。 RDBに慣れていると気持ち悪いですね。
curl -X POST -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/_search\?pretty { "took" : 9, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 2, "max_score" : 1.0, "hits" : [ { "_index" : "users_tweets", "_type" : "users_tweets", "_id" : "1234", "_score" : 1.0, "_source" : { "user" : { "name" : "kaiba", "screen_name" : "kaiba" }, "users_tweets_join_field" : { "name" : "user" } } }, { "_index" : "users_tweets", "_type" : "users_tweets", "_id" : "tweet6789", "_score" : 1.0, "_routing" : "1234", "_source" : { "tweet" : { "id" : "6789", "user_id" : "1234", "full_text" : "こんにちは!", "created_at" : "2019-01-01T04:00:00" }, "users_tweets_join_field" : { "name" : "tweet", "parent" : "1234" } } } ] } }
「こんにちは」とツイートしているユーザを探してみます。
has_child
クエリを使います。ドキュメント挿入は難しかったですがここはすごくわかりやすいですね。一度入れてしまえば、検索はまさにElasticです。
{ "query": { "bool": { "must": [ { "has_child": { "type": "tweet", "query": { "match": { "tweet.full_text": "こんにちは" } }, "min_children": 1 } } ] } } }
curl -X POST -H "Content-Type: application/json" http://b.lvh.me:9200/users_tweets/users_tweets/_search\?pretty -d @search3.json { "took" : 3, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 1.0, "hits" : [ { "_index" : "users_tweets", "_type" : "users_tweets", "_id" : "1234", "_score" : 1.0, "_source" : { "user" : { "name" : "kaiba", "screen_name" : "kaiba" }, "users_tweets_join_field" : { "name" : "user" } } } ] } }
ばっちり! 良さそうですね!
プログラムからの呼び出し
StampsのバックエンドではRailsを使用しています。
RailsはDBのテーブルと密接に関わるため、今回のようにテーブルとindexが1:1でない場合、設計の難易度が上がります。
Elasticsearch-railsのテストコードが綺麗に書けているので参考にすると良いでしょう。
まとめ
Elasticsearchのindex設計は「何を探したいか」に着目するとうまくいく気がします。
Stampsでは「来店したアプリユーザ」を様々な条件で検索したかったので、ユーザをベースに設計しました。
複雑な検索条件を柔軟に記載し、高速にできるようになりました。
意外と知らない便利なRuby/Railsメソッド5選
こんにちは、22Inc.山崎です。
この記事では、便利ではあるけども意外と知らないRuby(またはRails)のメソッドを紹介します。
以下に紹介するような便利メソッドを用いて短く書かなくても、機能開発に必要なコードを書くことはできます。 しかし、短く書く方法を知らないと、書くコード量が多くなり抜け漏れも発生しがちになります。
保守性に優れたコードを書いて 一歩上のエンジニアになるために、以下に紹介するようなコードを積極的に書いていきましょう!
- Enumerable#detect
- Hash#fetch_values
- Object#in?
- ActiveRecord_Relation#find_or_create_by
- ActiveRecord_Relation#size
Enumerable#detect
使用場面
ArrayやHashの要素の中から、任意の条件にマッチする最初の要素を取り出したい。
ary = [1, 7, 55, ... , 9893211, 9953441, 10000000 ] # ↑のように、昇順に不規則な値が並ぶ要素を持つ配列の中から、6の倍数である値のうち、一番最初にマッチする値を取り出したい
良くない書き方
ary.select {|e| e % 6 == 0}.first
selectメソッドを用いると、配列の要素を全てチェックすることになり、配列の要素数が多くなればなるほど実行完了までの時間がかかることになります。 条件にマッチする最初の要素を見つけた時点で、要素の探索を終了するほうがパフォーマンス上望ましいですよね。
キレイな書き方
ary.detect {|e| e % 6 == 0}
detectメソッドはブロック内の条件にマッチする要素を探索するという点ではselectメソッドと同じですが、1つ目の要素にマッチした時点で探索を終了します。
find, detect (Enumerable) - Rubyリファレンス
Hash#fetch_values
使用場面
複数の要素を持つハッシュの中から、指定した複数keyに対応するvalueのみを抽出したい場合。以下のような例が挙げられます。
params = { name: "James", age: 25, gender: "man", phone_number: "333-2323", prefecture_id: 1, prefecture_name: "北海道", segment: "free" } #↑のハッシュのkeyに対応するvalueのうちいくつかを、nilまたは空白でないことをチェックしたい。
良くない書き方
params = { name: "James", age: nil, gender: "", phone_number: "333-2323", prefecture_id: 1, prefecture_name: "北海道", segment: "free" } # nameとage、gender、segmentのキーには値が入ってなければならない。 if params[:name].present? && params[:age].present? && params[:gender].present? && params[:segment].present? puts "問題ありません" else puts "必須パラメータが不足しています" end
if文の条件に params
が4回も重複して登場しており、DRYでないことが一目で分かります。今後、新たなパラメータを追加したり既存のパラメータを削除したりした際は、if文の条件部分の修正も行わなければならなくなり、ヒューマンエラーによるバグの温床となってしまいます。
キレイな書き方
params = { name: "James", age: nil, gender: nil, phone_number: "333-2323", prefecture_id: 1, prefecture_name: "北海道", segment: "free" } # nameとage、gender、segmentのキーには値が入ってなければならない。 necessary_params = [:name, :age, :gender, :segment] if params.fetch_values(*necessary_params).all? {|e| e.present?} puts "問題ありません" else puts "必須パラメータが不足しています" end
fetch_values
メソッドは、引数に渡したkeyにマッチする要素のvalueを配列で返します。今回は以下のような結果を返します。
params.fetch_values(*necessary_params)
#=> ["James", nil, nil, "free"]
コードの重複が削減され、パラメータの追加や削除などの変更に強くなりました。
Object#in?
使用場面
あるオブジェクトが、配列の要素として存在するかを確認したいとき。
Array#include?
メソッドを使うことでも確認できますが、代わりにObject#in?
を使用することでコードの可読性を改善できます。
良くない書き方
#ID1番のユーザーが、いくつかある契約プランのうちどれかに加入していることを確認したい。 plan = User.first.plan contract_term = User.first.contract_term if %w(1week 2week 1month).include?(contract_term) puts "短期間向けプラン加入済" elsif %w(3month 6month a_year).include?(contract_term) puts "長期間向けプラン加入済" elsif %w(free trial beginner).include?(plan) puts "初心者向けプラン加入済" elsif %w(standard select medium).include?(plan) puts "中級者向けプラン加入済" elsif %w( high pro super ultra great).include?(plan) puts "上級者向けプラン加入済" else puts "プラン加入なし" end
配列の要素数が増えれば増えるほど、include?
メソッドのレシーバは巨大になるので、いったいどのオブジェクトを確認の対象としたいかが分かりにくくなってしまいます。
上記のように、配列の要素数が多かったり組み合わせパターンが多い場合には、配列を変数に格納することで可読性を上げるためには、パターンの数だけ変数を宣言することになりかえって冗長になります。
キレイな書き方
#ID1番のユーザーが、いくつかある契約プランのうちどれかに加入していることを確認したい。 plan = User.first.plan contract_term = User.first.contract_term if contract_term.in? %w(1week 2week 1month) puts "短期間向けプラン加入済" elsif contract_term.in? %w(3month 6month a_year) puts "長期間向けプラン加入済" elsif plan.in? %w(free trial beginner) puts "初心者向けプラン加入済" elsif plan.in? %w(standard select medium) puts "中級者向けプラン加入済" elsif plan.in? %w( high pro super ultra great) puts "上級者向けプラン加入済" else puts "プラン加入なし" end
in?
メソッド を用いることで、レシーバと引数をinclude?
の場合と逆転させることができました。(実際、in?
メソッドは内部でinclude?
を呼んでいます)
まだこのif文は改善の余地がありますが、ひとまずどのオブジェクトが条件として用いられるかは分かりやすくなりました。
ActiveRecord_Relation#find_or_create_by
使用場面
一定期間分のデータの集計をバッチで行い、その結果をデータベースに保存するという業務ロジックを組むのはよくありますよね。
集計ロジックでまず必要になるのは、データベースに保存していない時期のデータを集計して保存を行う処理でしょう(例: 毎月1日に前月の売上データを集計する) また、集計を行うタイミングによっては、過去に集計したデータと異なる数値が得られることもあり、その場合は既存データを上書きしなければならないときもあります。(例: 毎月1日に、過去1年分の月次データを集計するバッチ処理)
ここで必要になってくるのが、「データの集計をした上で過去データと値を比較し、既存データがない場合は新規レコードを作成し、既存データがある場合は取得データによる既存データの上書きを行う」という処理です。
このfind_or_create_by
メソッドは、その名の通りの働きをします。
つまり、
「引数に指定した条件にマッチするレコードが存在すればそのオブジェクトを取得し、存在しなければ引数に指定した条件に従うレコードを新規作成する」
という挙動になります。
なお、データベースに保存する処理はせず、インスタンスの生成までに留めるfind_or_initialize_by
というメソッドもあるので、場面によって使い分けることができます。
ActiveRecord_Relation#size
使用場面
ActiveRecord_Relation#size
は基本的にコレクションの要素数を返しますが、ActiveRecord_Relation#group
の戻り値に対して呼んだ場合、以下のようにグルーピングした状態でハッシュを返します。
#ID1〜100番のユーザーのうち、契約プランごとにグルーピングした上で各プランの人数を抽出したい User.where(id: 1...100).group(:plan).size #=> {"free"=>25, "standard"=>50, "pro"=>20, "super"=>5}
このとき内部ではGROUP BYとCOUNT(*)を用いたSQLが1回投げられています。そのためcount
メソッドを用いても同じくハッシュの戻り値になりますが、 length
メソッドを用いると 要素の種類数 が戻り値となります。
User.where(id: 1...100).group(:plan).count #=> {"free"=>25, "standard"=>50, "pro"=>20, "super"=>5} User.where(id: 1...100).group(:plan).length #=> 4
以上、意外と知らない便利メソッド5つを紹介しました。 複雑な処理をついつい力技で実装しそうになりますが、「もっとスマートな方法はないか?」を常に考えながらコードを書いていきましょう!
UX MILK Fest 2019に参加してきました
22Inc.のUXデザイナー ヨウソウシです。 今回はUX MILK Fest 2019に参加してきました!!フェス感がありセッションスケジュールもぎっしりで濃厚な一日でした。この記事で当時の見たこと、感じたことをまとめています。
- 「今まさにUX浸透させてます」新設UX組織がやっていること ベイクルーズ 佐々木さん
- オンラインとオフラインを越境するUX Payke 神谷さん
- 全部話します!組織にUXデザインを導入するためにしたアレコレ mediba 岡さん
- BtoBtoCサービスのデザイナーがユーザーに近づくために実施したこと ヤプリ 城さん
- アジャイルUXリサーチ メルペイ 松薗さん
- 情緒の設計 クックパッド 倉光さん
- 未参加セッション
- 全体感想
- 終わりに
https://uxmilkfest2019.studio.designuxmilkfest2019.studio.design
入り口からあちこちにある素敵なキャラクターミルクボイです。
Tシャツまで作ってあって気付いたら三瓶さんが販売ブースにw
では早速、自分の参加したセッション内容をまとめてまいります。
「今まさにUX浸透させてます」新設UX組織がやっていること ベイクルーズ 佐々木さん
UXアプローチとは?
- UIデザイン
- ユーザー理解
- インタビュー/アンケート
- ペルソナ
- ユーザーテスト(会社内部・外部両方やる)
- 適切なステップ
- HCDサイクル
案件
- 01.返品体験全体
- 02.商品ページ全体改善
- 03.CRMに置けるシナリオ最適化
まとめ
- UXデザイン以外の業務をしっかりやる →CS周りお客様と関わることなど
- トップダウンでUXを取り入れる →社内の影響力のある方・上の方からしっかりUXについて理解し、率先して周りを巻き込んでUXの概念を社内に広げる
- ユーザーのNPSも含めてたくさんデータ取る、そこから振り返る・ヒントが見つかる
感想
返品体験のユーザーテストはかなりしっかりしている。ユーザー体験はCSと切り離せないので継続改善はやりやすい環境と感じた。テスト→問題発見→デザイン改善といういいサイクルでサービスを作り上げていく。
オンラインとオフラインを越境するUX Payke 神谷さん
- 経歴:YAHOO(ロッジ)→MAMORIO→Payke(全てオフラインの実体験に関わる)
- オンラインとオフラインの横断でユーザー体験が発生
- 理想のUXと技術面のギャップを埋める(バランスをとる)
MAMORIO
課題1:家で探したい場合音を鳴らすのは一番いい方法だが技術面ではできない
- スマホの画面を回して検知されたら画面で知らせる
- 歩き回ってもらうことを工夫(チュートリアルや画面上のヒントなどで)
- 距離を示すため位置関係をマップっぽいUIにする
課題2:充電不可で電池切れたときものを無くしたがっかり体験の解消
- 対策:プッシュ通知で(モーダル)電池切れたかもの通知を出す
- が、電池切れと紛失はシステムで区別できない
- →電池の寿命の一年期間を想定してもうすぐ経つタイミングでアラート出す
Payke
payke.co.jp 外国人観光客向けの多言語対応アプリ
ユーザーとデザインの接触するタイミングから離脱を考える
- ユーザーとの一番の接点としては、外国人観光客が旅中に店舗にいる時。店頭にアプリインストール済みのタブレットをいかに使ってもらうのは大事。特にドンキーやビレバンなどPOPの情報量の多い店舗でどうタブレット自身を気付いてもらうのは難しいところ。
- 自分の作っているデザインがどのタイミングでユーザーと出会うのかを常に考える(ユーザーとの接点)
店頭のタブレットに人感センサーとか付けて調査する(使ってくれてるか)
- 理想と現実をすり合わせていく
- ユーザーリサーチしてストーリーを見直す。改善できるところを探す。
デザインチーム作りについて
- デザインチームの「あり方」を可視化する
- デザインチームのKPI(社長と一緒にすり合わせた)とKPIに基づく「やること」と「やらないこと」の明文化(工数・コストを考えて社内でやるか、社外に投げるか)
- モックアップを作ってこうやるとユーザー体験はどう変わるかどう向上になるのかプレゼンする
- 役割分担:プロダクトマネージャー→どう実践するか考える/UXデザイナー→全体の体験を考えて、改良していく
感想
MAMORIO、Paykeどちらでも斬新的なサービスで、今までにない体験をどうわかりやすく作るのか、色々工夫が必要。そして技術面でのバランスを取るのも難しいが大事。特にPaykeはユーザーとの接点は旅行中の店頭で、いかにビジュアルがごちゃついてる店でタブレットに気付いてもらうかは、なかなかのチャレンジと感じた。
全部話します!組織にUXデザインを導入するためにしたアレコレ mediba 岡さん
https://speakerdeck.com/masakioka/zu-zhi-niuxdezainwodao-ru-surutamenisitaarekore
- 趣味:インタビューでありえない姿勢の写真撮られることw
- お客様感謝DAY:全ての業務を止めてユーザーのことだけ考える
- 目的を持って目標を立てる
- 目標の為にデプロイ・リサーチをするなどなど
- 目的→目標→手段
- 目的を持つとモチベーションも変わる(三人のレンガ職人)
- BTCトップ人材による継続的UX改善評価
- トップの人に常にフィードバック・評価を繰り返す
- モックの段階ユーザービリティをテストする
- 会社全体をユーザー中心にする
- 会社の方向性:会社のクレドを明確に
- 人事制度の見直し
- スキルをあげれば給料もあげる(売上を上げるではなく)
- 売上を上げるのは経営陣
- 仕事(W)=力(F)x距離(S)
- UXデザインの摩擦を0にする(押したら動く、働きやすい環境)
感想
抽象的な「UX」はいかに組織に導入・浸透するのは、トップダウンはもちろんのことで、会社全体一丸となってしっかりユーザーのことを考える、継続的にやり続ける必要がある。
BtoBtoCサービスのデザイナーがユーザーに近づくために実施したこと ヤプリ 城さん
https://speakerdeck.com/yumijo/btobtocsabisufalsedezainagayuzanijin-dukutamenishi-shi-sitakoto
- 周りを巻き込むUXデザインを実行
- ユーザーが抱えている問題掴みづらい
- ユーザーとの距離を縮めるためやっていた3つのこと
- 小さな実践(加工された情報がデザイナーに届けてしまう)
- 社内のユーザー像に近い社員でユーザービリティテストをする
- 使えそうなデータとか勝手に分析してみる
- クライアント訪問に同行してヒアリング
- クライアント招待イベントで時間をもらってヒアリング
- ↓これらの成果を社内で共有
- ↓納得感がある
- !→腑に落ちる
- 共感者を増やす
- 自分たちの業務と繋がりが見える
- みんなサービスを成功させたいと思っている
- 仕組み作り
- 武器が増える(分析ツールができたとか、顧客アンケート実施してもらえるとか、ユーザーインタビューの予算できたとか)
- 小さな実践(加工された情報がデザイナーに届けてしまう)
- まとめ
- やったことを発表・共有しないのはやってないのと同じ
- UXリサーチについてまず社内で声かけてみる、意外と興味ある人いるかも
感想
まだまだUXの概念が浸透していない組織・会社では、デザイナーが率先して地道な努力がとても大事。それの結果を可視化して社内のメンバーに説得し共感を得た上で、UXリサーチ・改善もやりやすくなる。
アジャイルUXリサーチ メルペイ 松薗さん
https://www.slideshare.net/MihoMatsuzono/agile-ux-research-171767909
- UXリサーチチームの立ち上げ
- 「デザイン組織のつくりかた」を参考して組織作りしている
- 外注するか?採用して立ち上げるか
- そもそも金融系なので外注は難しいし、毎週やるのもディレクションが大変→インハウス立ち上げ
情緒の設計 クックパッド 倉光さん
https://speakerdeck.com/transit_kix/qing-xu-falseshe-ji
- 情緒設計することによって、情報がもっと受け入れやすくなる
- アイデアを具現化→抽象レイヤーの霧を晴らす
- 形が先にあり、ロジックは後から見出される
- 具現化することによって色々見えてくる
- 主観:感じて欲しい世界観はどのように構築?
- ユーザーストーリー:吹き出しにユーザーの考えていることを書く(P.31)
- UX MILKのリデザインプロセス情緒設計
- サービスの世界観を体験する
- ヒアリング→キーワードピックアップ→ムードボード
- ムードボード
- たくさんの画像を探してこれはイメージと合っているかOXで確認
- なんか違う?と思うやつは具体的に理由をいう
- アイデアの打ち合いが楽しい
- いいセッションが情緒を生む
- ユーザーに向きあってサービスを作る
- 誰のために、誰の感情を動かすか、一番初めから情緒を決めて進めていく
感想
UX MILK BOYの誕生物語面白すぎ
未参加セッション
未参加セッションですがツイッターでスライドのシェア見かけましたのでまとめました。
誰のためのUX
株式会社ユーザベース / 株式会社デスケル 平野 友規様
2軸で考えるプロダクトデザインのシンプルな意思決定方法
Pivotalジャパン株式会社 坂田 一倫様
MaaSにまつわるエトセトラ―UXデザインにおける誤った通念
dots inc. 坂本 貴史様
エンジニアがUXリサーチャーを兼任するに至ったワケ
ピクシブ株式会社 森田 洋介
チャットボットで考える プッシュ&デリバリーUX/Push & Delivery UX at Chatbot(LT)
株式会社ZEALS 福本 晃之
電子カルテならではの要望に応えるUI/UX #uxmilk_fest(LT)
株式会社クリプラ 寺嶋 章子
「わかる」だけじゃ不十分 機能から体験のデザインへ / UX MILK Fest 2019(LT)
株式会社ビデオマーケット 木下 由季子
全体感想
- UXリサーチ、ユーザービリティテストが大事。やる。
- トップダウンでやれば色々進めやすい
- 結果、仮説検証、評価を出して説得する→UXが社内で浸透できる
終わりに
一日のセッションが多く、3会場に分けて全て参加するのは不可能でしたが、全体的にフェスのような感覚で、わいわいして楽しい一日でした。
UX大喜利もやっていて、どれも面白かったですw
オフィシャルグッズはこんな感じです。
このようなUX中心のイベントは、日本国内でどんどん盛り上がれば嬉しいなと思います。来年もやるならぜひまた参加したいですね。
夏休みの読書感想文 : オブジェクト指向設計 実践ガイド
はじめまして。22Inc.にエンジニアとして中途入社しました藤原です。 実は、昔に営業代理店で「スタンプス」を販売していた経験があります。 そんな私が偶然、縁があり22incにエンジニアとして拾っていただきました。
さて、夏休みの読書感想文を書いていきたいと思います。
読んだ本:
オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 作者: Sandi Metz,?山泰基
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/02
- メディア: 大型本
- この商品を含むブログ (6件) を見る
本を読む前まで
ダックタイピングってなに?
もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである。
へ〜よくわからんけど、まだ自分には早い技術やな。
オブジェクト指向ってなに?
- クラスがあるやつやろ?
- 継承ってのが便利みたい。
- インスタンスがつくれる(クラスは設計書みたいな感じ)
恥ずかしながらそんなレベルでした。
では、オブジェクト指向を少し身に着けた軌跡をまとめていきます。
オブジェクト指向のオブジェクトの考え方
感動した言葉があったので引用
仮にオブジェクトが人間だとして、自分の関係を説明できるとしましょう。すると、図4.5では、TripはMechanicに「私は自分が何を望んでいるかを知っているし、あなたがそれをどうやるかも知っているのよ」と伝えているはずせす。図4.6では、「私は自分がなにを望んでいるかを知っていて、あなたが何をするのかも知っているよ」、図4.7では、「私は自分が何を望んでいるかを知っているし、あなたがあなたの担当部分をやってくれると信じているよ」でしょう
オブジェクト指向とそうでないコードの対比
では、具体的にオブジェクト指向なコードとそうでないコードのを比較してみます。 本書にでてくる4章のシーケンス図の例を参考にしながらRailsで自分なりにコードにしてみました。
自転車旅行会社で、旅行の前に整備士が自転車の準備をするプログラム
オブジェクト指向でないコード
Tripクラスのprepare_bicyclesメソッドは、Mechanicクラスをメソッド内で使っており、かつMechanicクラスのメソッドの引数にどのようなデータを渡すかまで知っておりそれも、使っている。 つまり、「私は自分が何を望んでいるかを知っているし、あなたがそれをどうやるかも知っているのよ」という状況
class Trip < ApplicationRecord has_many :trips def prepare_bicycles self.bicycles.each do |bicycle| Mechanic.clean_bicycle(bicycle) Mechanic.pump_tires(bicycle) Mechanic.lube_chain(bicycle) Mechanic.check_brakes(bicycle) end end end class Bicycle < ApplicationRecord belongs_to :trip, optional: true end class Mechanic < ApplicationRecord def self.clean_bicycle(bicycle) bicycle.update(is_clean: true) end def self.pump_tires(bicycle) bicycle.update(is_pump_tires: true) end def self.lube_chain(bicycle) bicycle.update(is_lube_chain: true) end def self.check_brakes(bicycle) bicycle.update(is_check_brakes: true) end end trip = Trip.find(1) trip.prepare_bicycles
オブジェクト指向なコード
Tripクラスの内には、MechanicクラスがなくMechanicに関することを何も知らない。Mechanicクラスのprepare_trip(trip)メソッドで引数としてオブジェクトが注入され、アソシエーションのbicyclesメソッドが呼び出される。Mechanicクラスに引数として渡り、どのように処理されるかはMechanicクラスに任せている。 つまり、「私は自分が何を望んでいるかを知っているし、あなたがあなたの担当部分をやってくれると信じているよ」という状況
class Trip < ApplicationRecord has_many :bicycles end class Bicycle < ApplicationRecord belongs_to :trip, optional: true end class Mechanic < ApplicationRecord def self.prepare_trip(object) object.bicycles.each do |bicycle| self.prepare_bicycle(bicycle) end end private def self.prepare_bicycle(bicycle) self.clean_bicycle(bicycle) self.pump_tires(bicycle) self.lube_chain(bicycle) self.check_brakes(bicycle) end def self.clean_bicycle(bicycle) bicycle.update(is_clean: true) end def self.pump_tires(bicycle) bicycle.update(is_pump_tires: true) end def self.lube_chain(bicycle) bicycle.update(is_lube_chain: true) end def self.check_brakes(bicycle) bicycle.update(is_check_brakes: true) end end trip = Trip.find(1) Mechanic.prepare_trip(trip)
オブジェクト指向ならどういうメリットがあるのか
新機能の実装を例に
e.g) 新しく、自転車レースをする事業が始まったため、整備士(Mechanic)は旅行のときだけではなく、自転車レースのときも自転車を準備する必要がでてきた。
オブジェクト指向でないコード
class Trip < ApplicationRecord has_many :trips def prepare_bicycles self.bicycles.each do |bicycle| Mechanic.clean_bicycle(bicycle) Mechanic.pump_tires(bicycle) Mechanic.lube_chain(bicycle) Mechanic.check_brakes(bicycle) end end end # >>>>>>>>>>>>> 新しく追加 class Race < ApplicationRecord has_many :bicyclesbicycle def prepare_bicycles self.bicycles.each do |bicycle| Mechanic.clean_bicycle(bicycle) Mechanic.pump_tires(bicycle) Mechanic.lube_chain(bicycle) Mechanic.check_brakes(bicycle) end end end # >>>>>>>>>>>>> class Bicycle < ApplicationRecord belongs_to :trip, optional: true end class Mechanic < ApplicationRecord def self.clean_bicycle(bicycle) bicycle.update(is_clean: true) end def self.pump_tires(bicycle) bicycle.update(is_pump_tires: true) end def self.lube_chain(bicycle) bicycle.update(is_lube_chain: true) end def self.check_brakes(bicycle) bicycle.update(is_check_brakes: true) end end trip = Trip.find(1) trip.prepare_bicycles race = Race.find(1) race.prepare_bicycles
オブジェクト指向なコード
class Trip < ApplicationRecord has_many :bicycles end # >>>>>>>>>>>>> 新しく追加 class Race < ApplicationRecord has_many :bicycles end # >>>>>>>>>>>>> class Bicycle < ApplicationRecord belongs_to :trip, optional: true end class Mechanic < ApplicationRecord def self.prepare_bicycles(object) object.bicycles.each do |bicycle| self.prepare_bicycle(bicycle) end end private def self.prepare_bicycle(bicycle) self.clean_bicycle(bicycle) self.pump_tires(bicycle) self.lube_chain(bicycle) self.check_brakes(bicycle) end def self.clean_bicycle(bicycle) bicycle.update(is_clean: true) end def self.pump_tires(bicycle) bicycle.update(is_pump_tires: true) end def self.lube_chain(bicycle) bicycle.update(is_lube_chain: true) end def self.check_brakes(bicycle) bicycle.update(is_check_brakes: true) end end trip = Trip.find(1) Mechanic.prepare_bicycles(trip) race = Race.find(1) Mechanic.prepare_bicycles(race)
つまり
オブジェクト指向でないコードの場合はコピペで prepare_bicycles メソッドを必要となるクラスに書く必要がある。 これは、Tripクラスのprepare_bicyclesの内容が変わると、Raceクラスのprepare_bicyclesも修正する必要がある。 オブジェクト指向なコードの場合は、Mechanicクラスのprepare_bicycleメソッドを変更するだけで大丈夫!
感想
はたして今回書いたサンプルコードはオブジェクト指向なのか? 本を読んでる最中はオブジェクト指向を「完全に理解した」が、ブログを書いていると「なにもわからない」という感じになった。 新しくGoFに関する本を買ったのでオブジェクト指向の習得していく! また、オブジェクト指向に関するブログを書いていきます。 TripクラスとRaceクラスでprepare_bicyclesの実装が違うみたいな記事を次書こうかなと思ってます。
zeplinでデザインスタイルガイドを作ってみました
22Inc.のUXデザイナー ヨウソウシです。
弊社サービススタンプスのブランディングをより向上できるように、スタイルガイドを作りましょう!という話になったので、どういう風に作っていくか、どのツールを使うか模索しながら制作していく過程を記録してみました。
ツールの選定
ネットで調べてみたら、fractal、storybook、documentCSS…など、世の中に出回っているスタイルガイドジェネレーターはたくさんあります。どれでもターミナルを使ってインストールなどの作業が必要です。ふむふむ、なるほど…若干デザイナーに優しくないですね。
と、思っているその時、frontifyというツールを見つけました。
frontifyを試してみた
ブラウザ上で楽々編集・作成できるツールですが、有料です。無料試用期間は14日あります。その後は29ドル/月になります。海外のサービスなので全て英語です。
ここの記事を参考してざっくり作成してみました。 www.webcreatorbox.com
操作画面はかなりシンプルでわかりやすく、英語ですが問題なく操作できました。色々いじってみて、最後の出来上がりは下記のリンクからご覧ください。 company-146839.frontify.com
UIコンポーネントもcodepenのようにhtml / css / jsが書けるので非常にいいです。
スタイルガイドの作り方はここの記事を参考にしました: uxmilk.jp
frontifyを使って作成していく中に、結局UIコンポーネントを作成するため、コードを書く必要があるのでコードに慣れていないデザイナーにとってやや大変な作業、、その時、デザイン指示書として利用しているzeplinというツールを思い出しました。
zeplinに移行
sansan社がイベントでこのツールをデザイン指示書として使っていますよという話がありました。これはもしかしたらスタイルガイドとしても使えそうじゃない?と。
Sketch、Adobe XD、Figma、Adobe PhotoshopなどのUI作成ツールと連携できて、ファイルそのままアップするだけで全てのオブジェのサイズ、角丸具合、間隔などのcssコード情報が自動生成されます。
無料で利用可能ですがプロジェクトは1つのみです。コード書けなくて済むのはかなり楽です。速攻zeplinに引っ越しました。
zeplinでスタイルガイドを作る
前提としてはSketch上にスタイルガイドを整えておくことです。画面だけ作ってzeplinにアップしてもただのデザイン指示書になります。
Sketchでスタイルガイドを作成
Sketchの主力機能の「シンボル」「テキストスタイル 」「レイヤースタイル」を使ってデザインスタイルガイドを作っていきます。
シンボル
ボタンやアイコンなど作ってシンボルに登録します。登録しておくと、流用で使い回しがかなり便利になります。
テキストスタイル
見出し、本文、注釈、テキストリンクなど、テキストのカラー・サイズ・フォントの情報をテキストスタイルとして登録します。
レイヤースタイル
テーマカラー・サブカラーなどの色を登録します。色の情報だけではなく、枠線・ドロップシャドウ・半透明なども登録できます。色々な場面で活用できそうです。
zeplinにアップロード
zeplinにアップするにはzeplinのプラグインが必要なのでインストールしました。諸々作成した上で、アートボードを選択してzeplinにアップします。すると、自動的にシンボル(Components)、テキストスタイル(Text Style Catalog)、レイヤースタイル(Color Palette)が全て読み込まれます。これで一瞬でスタイルガイド完成できて最高ですね。
アートボードを選択してzeplinにアップ:
アップしたら自動にスタイルガイドが出てくる:
zeplinをslackと連携
zeplinはslackと連携する機能があります。連携しておくと、何かしらの操作(画面アップロードとか)があるとシステムでslackに通知が飛んできます。画面のプレビュー画像も付いててアップしたらすぐそれに対して議論することも可能で、やり取りはかなり便利です。
zeplin x sketchは最高
sketchでしっかりしたスタイルガイドを作ってしまえば、ワンクリックでzeplinにアップすると瞬時にエンジニアに共有できるスタイルガイドも完成されます。もうこれみたいな最強なコンビはないじゃないかと思うくらい愛用しています。
現在はプロジェクトを進めながらスタイルガイドを作り上げていく段階で、まだまだお見せできるものではないですが、デザイナーとしてzeplinとsketchの連携で本当に楽で最高です。
「Simple Rails Navigator」というVSCodeプラグインを作った話
こんにちは、22Inc.大薮です。
タイトルのままなのですが、VSCodeでRailsを書こうとすると、どうしても自分の欲しい操作感で使えるプラグインがなかったんすよね。
試しに作ってみたら意外にも簡単に実装できたので、自分でプラグインを作って公開するまでの流れを紹介します。
カンタン3Stepな手順です。
Step.1 準備
yoをインストールして、npm installします。
npmが入ってれば特に特別なことは何もしなくて大丈夫だと思います。
(試してないけど yarnでも大丈夫かな??)
➜ npm install -g yo npm WARN deprecated cross-spawn-async@2.2.5: cross-spawn no longer requires a build toolchain, use it instead /usr/local/bin/yo -> /usr/local/lib/node_modules/yo/lib/cli.js /usr/local/bin/yo-complete -> /usr/local/lib/node_modules/yo/lib/completion/index.js > spawn-sync@1.0.15 postinstall /usr/local/lib/node_modules/yo/node_modules/spawn-sync > node postinstall > yo@2.0.6 postinstall /usr/local/lib/node_modules/yo > yodoctor Yeoman Doctor Running sanity checks on your system ✔ Global configuration file is valid ✔ NODE_PATH matches the npm root ✔ Node.js version ✔ No .bowerrc file in home directory ✔ No .yo-rc.json file in home directory ✔ npm version ✔ yo version Everything looks all right! + yo@2.0.6 added 542 packages in 51.939s ╭─────────────────────────────────────╮ │ │ │ Update available 5.6.0 → 6.9.0 │ │ Run npm i npm to update │ │ │ ╰─────────────────────────────────────╯ ➜ ~ npm install -g generator-code + generator-code@1.1.50 added 385 packages in 29.613s
Step.2 実装
ジェネレーターで雛形を生成したらあとは楽しく実装していくだけです。
今回作成した「Simple Rails Navigator」はGithubに公開してあるので、興味がある人は見てみてください。
簡単に実装できそうな気がしてきませんか?
➜ yo code ? ========================================================================== We're constantly looking for ways to make yo better! May we anonymously report usage statistics to improve the tool over time? More info: https://github.com/yeoman/insight & http://yeoman.io ========================================================================== Yes _-----_ ╭──────────────────────────╮ | | │ Welcome to the Visual │ |--(o)--| │ Studio Code Extension │ `---------´ │ generator! │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` ? What type of extension do you want to create? New Extension (TypeScript) ? What's the name of your extension? Simple Rails Navigator ? What's the identifier of your extension? simple-rails-navigator ? What's the description of your extension? Simple plugin for navigating Ruby on Rails applications. ? Initialize a git repository? Yes ? Which package manager to use? npm create simple-rails-navigator/.vscode/extensions.json create simple-rails-navigator/.vscode/launch.json create simple-rails-navigator/.vscode/settings.json create simple-rails-navigator/.vscode/tasks.json create simple-rails-navigator/src/test/extension.test.ts create simple-rails-navigator/src/test/index.ts create simple-rails-navigator/.vscodeignore create simple-rails-navigator/.gitignore create simple-rails-navigator/README.md create simple-rails-navigator/CHANGELOG.md create simple-rails-navigator/vsc-extension-quickstart.md create simple-rails-navigator/tsconfig.json create simple-rails-navigator/src/extension.ts create simple-rails-navigator/package.json create simple-rails-navigator/tslint.json I'm all done. Running npm install for you to install the required dependencies. If this fails, try running the command yourself. > simple-rails-navigator@0.0.1 postinstall /Users/hisashi/Documents/code/repository/simple-rails-navigator > node ./node_modules/vscode/bin/install Detected VS Code engine version: ^1.34.0 Found minimal version that qualifies engine range: 1.34.0 Fetching vscode.d.ts from: https://raw.githubusercontent.com/Microsoft/vscode/a622c65b2c713c890fcf4fbf07cf34049d5fe758/src/vs/vscode.d.ts vscode.d.ts successfully installed! npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN simple-rails-navigator@0.0.1 No repository field. npm WARN simple-rails-navigator@0.0.1 No license field. added 112 packages in 7.974s Your extension simple-rails-navigator has been created! To start editing with Visual Studio Code, use the following commands: cd simple-rails-navigator code . Open vsc-extension-quickstart.md inside the new extension for further instructions on how to modify, test and publish your extension. For more information, also visit http://code.visualstudio.com and follow us @code. ➜ cd simple-rails-navigator
Step.3 公開
基本的にはこちらの手順に沿って進めます。
Azure DevOpsへの登録が必要になります。
(個人的にはこれが一番めんどくさかった😅)
$ npm install -g vsce /usr/local/bin/vsce -> /usr/local/lib/node_modules/vsce/out/vsce > fsevents@1.2.9 install /usr/local/lib/node_modules/vsce/node_modules/fsevents > node install node-pre-gyp WARN Using needle for node-pre-gyp https download [fsevents] Success: "/usr/local/lib/node_modules/vsce/node_modules/fsevents/lib/binding/Release/node-v59-darwin-x64/fse.node" is installed via remote + vsce@1.61.0 added 323 packages in 14.744s $ vsce login yaboojp Personal Access Token for publisher 'yaboojp': **************************************************** $ vsce publish Executing prepublish script 'npm run vscode:prepublish'... > simple-rails-navigator@0.0.1 vscode:prepublish /Users/hisashi/Documents/code/repository/simple-rails-navigator > npm run compile > simple-rails-navigator@0.0.1 compile /Users/hisashi/Documents/code/repository/simple-rails-navigator > tsc -p ./ Publishing yaboojp.simple-rails-navigator@0.0.1... DONE Published yaboojp.simple-rails-navigator@0.0.1 Your extension will live at https://marketplace.visualstudio.com/items?itemName=yaboojp.simple-rails-navigator (might take a few seconds for it to show up).
しばらくすると、公開されます🎉🎉🎉
この時は、数分ほどでメール通知されました
小並感
自分は家具などDIYするのが好きなのですが、プログラミングの道具もDIYできるって、やっぱり楽しいですね。 エディタの使い勝手は、エンジニアにとって生産性に大きく影響するのでこだわっていきたいですね。 ちなみにショートカットの使い勝手がまだ手に馴染まないのでもうちょっと改善する予定です。