sitateru tech blog

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

2018年12月14日金曜日

いま話題の0円タクシーに乗ってみた!

12月 14, 2018

こんにちは!
シタテル株式会社UI/UXデザイナーの堤です。

先日ずっと気になっていた0円タクシーを利用したのでレポートします。

0円タクシーとは

DeNAさんが運営する次世代タクシー配車アプリ「MOV(モブ)」と日清のどん兵衛とのコラボレーションと なっており、2018年12月5日から31日までで都内の対象エリアでのみ運行されています。

利用方法

  1. MOV(モブ)アプリをダウンロード
    https://m-o-v.jp/campaign/donbei/
  2. アカウント情報を登録
  3. 乗る場所と行き先を指定
  4. タクシー会社の 中から「0円タクシーby日清のどん兵衛」を選択
  5. 「タクシーを呼ぶ」であとは待つだけ

タクシー会社選択画面。



目的地選択画面。
目的地を入力したりピンを移動すると瞬時に距離やかかる時間が表示されます! すごい!!



いま呼べるタクシーが地図に プロットされてる画面。
このときは 10 〜15分ぐらい に 1、2台呼べる状態 となっていました。



手配中の画面。
一度だけこの状態からキャンセルとなってしまったので 競争率も激しそうです。



手配完了の画面。
地図上で普通のタクシーとの差別化がうまくできていて、なにより不安を与えず待ち時間も楽しめる画面になっているので素晴らしいです。



外装だけかと思いきや内装も凝ってました!MOVのロゴもかわいい。



移動中は終始CMが 流れていました。



乗車中の画面。
添えられてるメッセージも良い!



乗車完了後の画面。
0円!!!



乗るときも 降りるときもドライバーさんに写真撮っていいですか?と聞くと快くOKと 言っていただいたので10枚ほど写真を撮り、移動中は「どんな人が利用してますか?」や「ドライバーさん自身は損しない仕組みなんですか?」などの質問にも笑顔で答えていただき 、どん兵衛もタクシーも より好きになりました。

12月下旬に乗車すると、実際にどん兵衛をいただけるようなのでまた利用したいと思います!

2018年12月13日木曜日

ec2のインスタンスログインを簡単に管理する ~EC2 with Peco ~

12月 13, 2018

sitateruでサービス・プロダクトを担当している北爪です。
インフラを見ることもあり、ec2サーバーのアドレス管理についてCLIで行う方法を紹介します。

目的

ec2のインスタンスはアドレスが構築するたびに変わり、負荷や新しいプロジェクトが立ち上がると増えたり減ったりします。
そのため、動的にサーバーアドレスを取得する必要があります。

AWS consoleに入ることなく、awsコマンドからサーバーリストを取得し、CLI filterのpecoを使って、SSHまでおこないます。

使い方

peco
Command+\ でpecoを起動

filtered
やじるしの ↑↓ か、キーワードで絞る
SSHするサーバーを選んでEnterを押す

ssh
SSHコマンドがはかれる

準備

zshを使っているのを前提に説明します。

必要ソフトのインストール

  • peco をインストールする
  • aws cli をインストールする
  • AWSのクレデンシャルを用意する

Shellの設定

ec2 コマンドを作る

AWS CLIから、ec2をリスト化するコマンドを作成し、shellに設定します。(zshrc)

alias ec2="aws ec2 describe-instances --query 'Reservations[].Instances[].{PublicDnsName:PublicDnsName,InstanceId:InstanceId,Tags:Tags[?Key==\`Name\`].Value|[0],InstanceType:InstanceType,State:State.Name}' --output table"

peco-ec2コマンドを作る

  • zshrcpeco-ec2 をつくります
  • ログインユーザーをdeployにしています
function peco-ec2 () {
  local selected=$(ec2 | peco --query "$LBUFFER")
  if [ -n "$selected" ]; then
    BUFFER="ssh deploy@$(echo $selected | awk 'match($0, /ec2.*\.amazonaws\.com/) {print substr($0, RSTART, RLENGTH)}')"
    zle accept-line
  fi
  zle clear-screen
}
  • さらに、好きなキーにバインドします。
  • ここでは、Command-\ につけています
zle -N peco-ec2
bindkey '^\' peco-ec2

まとめ

ec2をリスト化し、すぐにそこからsshを選べるようにしました。

2018年12月12日水曜日

Alexaスキルを作ろう

12月 12, 2018

こんにちは。シタテルでエンジニアをしている熊谷です。

主にSPECというサービスのバックエンド開発(Rails)を行っています。

少し前にAlexaスキルの作成を試したので、その流れを簡単に説明していきます。

手順

1.Amazonアカウントを用意する

  • すでにamazon.co.jpの開発者アカウントがある場合は、それを使います。

  • お買い物用のAmazonアカウントを使用して、開発を開始できます。

  • Amazon Developerから、サインインして、情報を入力すると、開発用アカウントが登録されます。

    Amazon Developer

    ※ 画像のページで、ログインをして進みます。[Amazon Developerアカウントを作成]から進むと、amazon.com用のアカウントになってしまい、US向けのスキルになってしまうようです。

2.Alexa Developer Consoleにアクセス

3.[スキルの作成]をクリックし、スキル名などを入力する

スキルページ

4.スキルのページで、[呼び出し名]を設定する

5.エンドポイントを設定する

  • AWS Lambdaが推奨されているので、Lambdaでエンドポイントを作成するのが良いと思います。
  • リクエスト、レスポンスはJSON形式で送信します。
  • 作成したLambdaのARNを指定します。

6.インテントを作成する

  • [インテントを追加]をクリックし、カスタムインテントを作成します。

7.作成したインテントのページで、スキルの発話時に使用する、スロット(変数のようなもの)を設定する

  • スロットには、日付、数値、検索クエリー(文字列)などの型を指定できます

8.インテントを設定する

  • 作成したインテントの設定で、サンプル発話を登録します。
  • サンプル発話には上で作成したスロットを{スロット名}のような形で使用できます。

これで簡単なスキルを作成できました!

今回はざっくりした流れの紹介なので、ここまでにしたいと思います。

次回はLambdaの内容や、作成したスキルの公開、テストなどを説明していこうと思います。

2018年12月11日火曜日

Active Storage移行記:データ移行編

12月 11, 2018

こんにちは、あさのです。

前にActive Storageの記事を書きましたが、今回もそのネタで行こうと思います。

Active Storage移行記:バリデーション編|sitateru tech blog

今回は、Paperclip管理のデータをActive Storage管理に移行したときのお話です。

データ構造

Paperclipではファイルのデータは各モデルのカラムに保存されています。

userクラスのiconというファイルであれば、userテーブルに以下のようなカラムがあります。

  t.string "icon_file_name", comment: "ファイル名"
  t.string "icon_content_type", comment: "ファイルのcontent type"
  t.integer "icon_file_size", comment: "ファイルサイズ"
  t.datetime "icon_updated_at", comment: "ファイル更新日時"

対してActive Storageでは全クラスの全ファイルをまとめて2つのテーブルで管理します。

create_table "active_storage_attachments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
  t.string "name", null: false
  t.string "record_type", null: false
  t.bigint "record_id", null: false
  t.bigint "blob_id", null: false
  t.datetime "created_at", null: false
  t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
  t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end

create_table "active_storage_blobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
  t.string "key", null: false
  t.string "filename", null: false
  t.string "content_type"
  t.text "metadata"
  t.bigint "byte_size", null: false
  t.string "checksum", null: false
  t.datetime "created_at", null: false
  t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end

たとえば、

  • userクラスのicon
  • user.id=15
  • ファイル名 prof.jpg

というファイルだと以下のようなレコードになります。
2つのテーブルは blob_id で関連づけられるんですね。

id    name    record_type    record_id    blob_id    created_at
15    icon    User    3    16    2017-12-28 19:23:03

id    key    filename    content_type    metadata    byte_size    checksum    created_at
16    3e0ca647-18e3-43ee-85c4-4de552bac46f    prof.jpg    image/jpeg    {}    44202    isFyYNJIs/6pyptfRUMUlA==    2017-12-28 19:23:03

データ移行

さて、それぞれのデータ構造がわかったのでマイグレーションをしていきます。

このあたりの記事を参考にさせてもらい、rakeタスクで実装しました。

namespace :import_to_active_storage do
  desc 'Import to ActiveStorage'
  task run_all: :environment do
    models = ActiveRecord::Base.descendants.reject(&:abstract_class?)
    import_data(models)
  end

  private

  def import_data(models)
    conn = ActiveRecord::Base.connection
    models.each do |model|
      attachments = model.column_names.map do |c|
        c =~ /(.+)_file_name$/ && Regexp.last_match(1)
      end.compact

      model.find_each do |instance|
        attachments.each do |attachment|
          puts "Process #{model.name}/#{instance.id}"

          next unless instance.send("#{attachment}?")
          next if conn.select_one("SELECT `id` FROM active_storage_attachments WHERE `record_type` = '#{model.name}' AND `record_id` = #{instance.id}").present?

          puts "Migrate #{instance.send(attachment.to_s)}"

          ActiveRecord::Base.transaction do
            execute_statement(conn, model, instance, attachment)
          end
        end
      end
    end
  end

  def execute_statement(connection, model, instance, attachment)
    active_storage_blob_statement(connection).execute(
      key,
      instance.send("#{attachment}_file_name"),
      instance.send("#{attachment}_content_type"),
      instance.send("#{attachment}_file_size"),
      checksum(instance.send(attachment)),
      instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
    )

    active_storage_attachment_statement(connection).execute(
      attachment,
      model.name,
      instance.id,
      instance.updated_at.strftime('%Y-%m-%d %H:%M:%S')
    )
  end

  def active_storage_blob_statement(connection)
    connection.raw_connection.prepare(<<-SQL)
      INSERT INTO active_storage_blobs (
        `key`, `filename`, `content_type`, `metadata`, `byte_size`, `checksum`, `created_at`
      ) VALUES (?, ?, ?, '{}', ?, ?, ?)
    SQL
  end

  def active_storage_attachment_statement(connection)
    connection.raw_connection.prepare(<<-SQL)
      INSERT INTO active_storage_attachments (
        `name`, `record_type`, `record_id`, `blob_id`, `created_at`
      ) VALUES (?, ?, ?, LAST_INSERT_ID(), ?)
    SQL
  end

  def key
    SecureRandom.uuid
  end

  def checksum(image)
    uri = URI.parse("https:#{image.url}")
    body = Net::HTTP.get(uri)
    Digest::MD5.base64digest(body)
  end
end

そこそこ長いですが、結局は各モデルの各ファイルごとにPaperclipのカラム情報とファイルのハッシュ値を取り、
Active Storageのテーブルに入れるという繰り返しです。

なお、各モデルの実装をActive Storage用に書き換えた後ではこのタスクは動かないので、
Paperclip時に実行する必要があります。

実際にやったところ数万件のデータがあったために5~6時間かかるという大引っ越しになってしましましたが、
なんとか乗り切りました。


ということで今回はActive Storageのデータ移行について書きました。

まだもう少しActive Storageネタで書けるかもしれません。

それでは。

2018年12月10日月曜日

縫製工場の類似度算出を深層学習のモデルでやってみた

12月 10, 2018
こんにちは!
シタテル株式会社CTOの和泉です。

今回は研究開発的に、「縫製工場の類似度算出を深層学習のモデルでやってみた」について書きます。
まず初めに結果から。
['loss', 'acc']
[273999.25, 0.695652186870575]
69.6% 成功?
なにが?って話ですよね。
結果的には全然精度出ませんでした。まぁ、のべ1週間ぐらいでやってみた感じですし、サーベイも全然できてないので結果出るわけないのですが。
せっかくやってみたのでブログに恥を晒しておきます。
その道の専門の方にはお目汚しかと思いますがご容赦ください。

やりたかったこと

弊社コンシェルジュが手動で類似分類(グループ化)した縫製工場 のデータを教師データとして学習し、同様の類似分類を行える学習済みモデルを作成する。
類似分類ができれば、これまでのアイテム作成履歴から代替となる工場の提案ができる。かも。

希望的仮定

類似度の判定に用いられる特徴はある程度絞られているのではないかと想定し、学習によって効果の高い特徴が反映されるモデルが作成されれば、人の判断に近づけるのではないか。

結果

教師データに対しては98.8%の正答率に達したが、テストデータについては69.6%にとどまった。残念ながら実用に足るような結果は得られなかった。
images/image-3.png
図1:教師データに対する推論結果のプロット(青が教師データ、点線先のオレンジが対応する推測結果)
images/image-4.png
図2:テストデータに対する推論結果のプロット(青が教師データ、点線先のオレンジが対応する推測結果)
images/image-5.png
図3:学習の経過

何をやったのか

データベースに保有する123の縫製工場を2次元平面上で類似しているものを近くに配置するように、弊社コンシェルジュに依頼して教師データを作成。(図1の青い点が各工場を表す)この際、「類似している=あるアイテムを相談するときに変わりに相談できる工場であるか」とした。作成された教師データが含む縫製工場データは114件。9件は情報不足により配置できなかった。
作成した教師データは各工場ごとに位置ベクトルが与えられた状態になっている。この位置ベクトルを各工場がもつ特徴ベクトル(全313次元のうち、名前などヒューリスティックに取り除けるものを除いて、108次元)から求めるモデルを学習により獲得した。

DNNモデルや条件などのメモ

model = Sequential()
model.add(Dense(DIM, activation='relu', input_dim=DIM))
model.add(Dropout(0.25))
model.add(Dense(DIM*2, activation='relu'))
model.add(Dense(2))

model.compile(optimizer='adam',
              loss='mean_squared_error',
              metrics=['accuracy'])
images/image-6.png
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 108)               11772     
_________________________________________________________________
dropout_1 (Dropout)          (None, 108)               0         
_________________________________________________________________
dense_2 (Dense)              (None, 216)               23544     
_________________________________________________________________
dense_3 (Dense)              (None, 2)                 434       
=================================================================
Total params: 35,750
Trainable params: 35,750
Non-trainable params: 0
_________________________________________________________________
history = model.fit(X_train, Y_train,
            batch_size=50,
            epochs=5000,
            validation_split=0.1, 
            verbose=1)

考察

代替工場を考える際には、アイテムによって代替として扱うものが変わることが想定されるため、全体を2次元平面上に次元縮退してプロットすることが難しいであろうことは当初予定していたとおりであった。
今回は、比較的手軽に作成できる教師データを用いて試してみたが、十分な結果は得られなかった。
統計によるリコメンドではデータの量が問題になるが、良質で量のあるデータが十分にないのが現状。シタテルの取扱量が増えることによりある程度のデータは蓄積されていくが、滝行との連携も含めて扱えるデータ量を増やしていくことが必要である。

工場データベースについて

シタテルは500を超える縫製工場やサプライヤーと連携して衣服づくりサービスを提供しています。
それぞれの縫製工場に対して、設備や人員、アイテムごとの金額感、繁閑の目安など300を超える情報をデータベースに格納しています。
images/image-1.png
工場を検索して絞り込み

(工場名や取引先は非公開のため消してあります)
images/image-2.png
いくつかの工場を並べて比較
弊社コンシェルジュはこれらのデータを使ってご相談いただいたアイテムごとに最適な工場に問い合わせを行います。

2018年12月7日金曜日

CircleCIでAWS ECSに自動デプロイする(おまけで環境面の切り替えも)

12月 07, 2018

こんにちは、シタテルの茨木です。

今回はCircleCIでDockerをビルドし、そのままAWS ECR/ECSにデプロイする為の設定例を紹介します。

Nuxt.jsアプリ用に作ったものですが、言語やAPサーバが違っても共通の部分がほとんどです。

PRマージからの自動デプロイ、いいですよね。

https://circleci.com/docs/2.0/ecs-ecr/
https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr

基本的にはCircleCI公式ガイドの上記リンクを参考にしていますが、元ネタはterraform前提だったりでなかなか大仰です。(下記に説明漏れがあったら原典を見てください…)

今回紹介するのはリンク先のスクリプト類を簡素化し、少し機能を追加(リポジトリ名からデプロイ先の環境面を切り替えできるように)したものです。

以下で省略しているけれど必要なモノ

  • ECS/ECRの設定

これだけで別の記事が書けてしまうので割愛します

ecsTaskExecutionRoleの割り当てとか忘れがちですかね

  • githubリポジトリ

言わずもがな。CircleCIとの接続設定はされているものとします

  • Dockerfile

リポジトリのルートに。Dockerfileの書き方は解説しません

  • requirements.txt

リポジトリのルートに。awscliを使えるようにします

https://github.com/CircleCI-Public/circleci-demo-aws-ecs-ecr/blob/master/requirements.txt

  • CircleCIの環境変数設定

github上にaccess_keyを晒さなくていいように、入れておきます

https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-project

この手順で設定した環境変数はCircleCIのジョブから参照できます

.circleci/config.yml:build

version: 2
jobs:
~省略~
  build:
    docker:
      - image: circleci/node:10-stretch
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Setup common environment variables
          command: |
            echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
            echo 'export FULL_IMAGE_NAME="${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/${ECR=REPOSITORY_NAME}:${CIRCLE_SHA1}"' >> $BASH_ENV
      - run:
          name: Build image
          command: |
            docker build -t $FULL_IMAGE_NAME .
      - run:
          name: Save image to an archive
          command: |
            mkdir docker-image
            docker save -o docker-image/image.tar $FULL_IMAGE_NAME
      - persist_to_workspace:
          root: .
          paths:
            - docker-image
~省略~

さっそくですがビルドジョブです。

  • YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください
  • - image: circleci/node:10-stretchnode:10を使っていますが、これはビルドする対象がNuxt.jsアプリだからですね。Dockerビルド時に必要なモノに合わせて修正してください

.circleci/config.yml:deploy

~省略~
  deploy:  
    docker:
      - image: circleci/python:3.6.1
    environment:
      AWS_DEFAULT_OUTPUT: json
    steps:
      - checkout
      - setup_remote_docker
      - attach_workspace:
          at: workspace
      - restore_cache:
          key: v1-{{ checksum "requirements.txt" }}
      - run:
          name: Install awscli
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r requirements.txt
      - save_cache:
          key: v1-{{ checksum "requirements.txt" }}
          paths:
            - "venv"
      - run:
          name: Load image
          command: |
            docker load --input workspace/docker-image/image.tar
      - run:
          name: Setup target environment variables
          command: |
            echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV
            source $BASH_ENV
      - run:
          name: Setup common environment variables
          command: |
            echo 'export ECR_REPOSITORY_NAME="YOUR-ECR-REPO-NAME"' >> $BASH_ENV
            echo 'export ECS_CLUSTER_NAME="YOUR-ECS-CULSTER-NAME-PREFIX${TARGET}"' >> $BASH_ENV
            echo 'export ECS_SERVICE_NAME="YOUR-ECS-SERVICE-NAME-PREFIX${TARGET}"' >> $BASH_ENV
      - run:
          name: Push image
          command: |
            . venv/bin/activate
            eval $(aws ecr get-login --region ap-northeast-1 --no-include-email)
            docker push $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$ECR_REPOSITORY_NAME:$CIRCLE_SHA1
      - run:
          name: Deploy
          command: |
            . venv/bin/activate
            export AWS_DEFAULT_REGION="ap-northeast-1"
            export ECS_TASK_FAMILY_NAME="YOUR-ECS-TASK-NAME-PREFIX${TARGET}"
            export ECS_CONTAINER_DEFINITION_NAME="YOUR-ECS-CONTAINER-DEF-NAME-PREFIX${TARGET}"
            export EXECUTION_ROLE_ARN="arn:aws:iam::$AWS_ACCOUNT_ID:role/ecsTaskExecutionRole"
            bash ./deploy.sh
~省略~

デプロイジョブです。

  • ブランチ名から環境名へ

echo 'export TARGET=`echo $CIRCLE_BRANCH | sed -e s/develop.*/dev/ -e s/release.*/stg/ -e s/master/prd/`' >> $BASH_ENV

ブランチ名に応じた値(developブランチなら"dev")を環境変数に入れています。これをsuffixとして使い、デプロイ先のクラスタ名等に反映しています

  • YOUR-HOGEHOGE箇所はご自身のECS/ECRの設定に合わせて置き換えてください

余談ですが、ECSはクラスタ・サービス・タスク・タスク定義といろいろ設定しないといけないのですが、なかなか直感的にわかりにくくてイマイチな印象です

.circleci/config.yml:workflows

~省略~
workflows:
  version: 2
  build-deploy:
    jobs:
      - build:
          filters:
            branches:
              only:
                - develop
                - /release\/.*/
                - master
      - deploy:
          requires:
            - build
          filters:
            branches:
              only:
                - develop
                - /release\/.*/
                - master

この辺はよしなに

deploy.sh

#!/usr/bin/env bash
set -eo pipefail
# more bash-friendly output for jq
JQ="jq --raw-output --exit-status"

deploy_cluster() {
  make_task_def   
  register_definition

  if [[ $(aws ecs update-service --cluster $ECS_CLUSTER_NAME --service $ECS_SERVICE_NAME --task-definition $revision | \
      $JQ '.service.taskDefinition') != $revision ]]; then
    echo "Error updating service."
    return 1
  fi

  # wait for older revisions to disappear
  # not really necessary, but nice for demos
  for attempt in {1..30}; do
    if stale=$(aws ecs describe-services --cluster $ECS_CLUSTER_NAME --services $ECS_SERVICE_NAME | \
        $JQ ".services[0].deployments | .[] | select(.taskDefinition != \"$revision\") | .taskDefinition"); then
      echo "Waiting for stale deployment(s):"
      echo "$stale"
      sleep 30
    else
      echo "Deployed!"
      return 0
    fi
  done
  echo "Service update took too long - please check the status of the deployment on the AWS ECS console"
  return 1
}

make_task_def() {
  task_template='[
    {
      "name": "%s",
      "image": "%s.dkr.ecr.%s.amazonaws.com/%s:%s",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 80
        }
      ],
      "environment": [
        {
          "name": "TARGET",
          "value": "%s"
        }
      ]
    }
  ]'

  task_def=$(printf "$task_template" $ECS_CONTAINER_DEFINITION_NAME $AWS_ACCOUNT_ID $AWS_DEFAULT_REGION $ECR_REPOSITORY_NAME $CIRCLE_SHA1 $TARGET)
}

register_definition() {
  if revision=$(aws ecs register-task-definition --requires-compatibilities FARGATE --cpu 256 --memory 1024 --network-mode awsvpc --execution-role-arn $EXECUTION_ROLE_ARN --container-definitions "$task_def" --family $ECS_TASK_FAMILY_NAME | $JQ '.taskDefinition.taskDefinitionArn'); then
    echo "New deployment: $revision"
  else
    echo "Failed to register task definition"
    return 1
  fi
}

deploy_cluster

デプロイジョブから呼び出すスクリプトです。

task_templateの内容を色々弄るとカスタマイズできます

設定可能な項目はこちら

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/create-task-definition.html

ここでは、environmentで環境変数TARGETを設定しています

ECSがdocker runする際に引数として付与され、docker内のアプリから環境変数として参照可能になります

実際には、nuxt.config.js内でTARGETを参照し、APIサーバの向け先を切り替えたり等しています

以上、ご参考になれば幸いです。

2018年12月6日木曜日

チームでのUI/UX改善のための取り組み「マイオペ触る会」のご紹介

12月 06, 2018

こんにちは!

シタテルでエンジニアをしている建山です。

主に工場向けのマイオペというシステムの開発を行っています。

シタテルでは、チームがプロダクトなどでわかれており、今担当しているマイオペチームでは、
デザイナー、エンジニアが一緒になって開発をしています。

その中で、「マイオペ触る会」というのを隔週で行っています。

その取組の目的や方法をご紹介します。

マイオペ触る会って

普段開発しているプロダクトを、開発しているメンバー全員でユーザーの気持ちになって操作する時間を設けています。

目的としては、以下のような感じです。

・ユーザーの目線に改めてなって自分たちが作っているものを使うことで、気づいてなかったユーザーの課題やUI/UXの問題を洗い出す
・ユーザー目線を忘れないようにする意識付け

方法として、それぞれ行いたい時に非同期でやろうとしたのですが、開発の忙しさに追われてやらないことがあったため、みんなで同じ時間に集まって行うようにしています。

マイオペチームは拠点がバラバラなため、触る会の際は、zoomを繋ぎっぱなしにして話しながら進めています。

以下のような、googleスプレッドシートを活用し、どういうストーリーで操作したかの簡単な説明、画面ごとの問題や気になったことを各自書いていきます。

終わった後はイシュー化

終わった後はgithubでイシュー化して、議論検討したり、ユーザーインタビューしてみたり、開発計画に組み込んで進めたりしていきます。

やっていてよかったこと

目的にも書いていますが、開発しながら毎日自分たちのつくっているものには触れ合っていますが、あらためて、ユーザーの気持ちになっと触る時間を設けると、また初心に帰ることができるし、新鮮な気持ちでユーザーの課題が見えることもあります。
また、触る会をきっかけに チーム全体でもユーザー目線で活発な議論が発生していると感じていてやっていてよかったと思う点ばかりです。

まとめ

今回は、チームで取り組んでいる「触る会」をご紹介しました。チームでは他にも、いろいろな取組をしていますので、また追々紹介したいと思います。
チームや状況に合わせて、引き続きいいやり方を模索しながら改善していきたいと思います!
優秀なUI/UXデザイナーがチームにいますので、きっと記事を書いてくれると思います。笑