BUYMAの検索システムを刷新したお話

こんにちは。主にBUYMAの検索周りを担当しているエンジニアの伊藤です。

BUYMAではSolrを利用した検索システムがいくつかあります。
BUYMAの検索というと検索ボリュームが一番大きな商品検索を想像されると思いますが、
今回はデータボリュームが一番大きい検索システムをターゲットとして、インフラ周りを含め全面的にシステムの刷新を行いました。

ここでは、

  • 既存の検索システムがどういったものだったのか
  • なぜシステム更改が必要だったのか(どういう課題があったのか)
  • 更改後の検索システムはどういったものか
  • 今後の課題について

等々についてご紹介したいと思います。

既存の検索システムについて

既存の検索システムは下記の通り、シンプルという点ではとても素晴らしいものでした。

f:id:pma1013:20200203151456p:plain

ただし下記のような問題を抱えている状況でした。

  • スケールアウトしない構成である
  • スケールアップの限界
  • Solrのバージョンが古い
  • 本番環境のデータを利用した検証が難しい
  • 開発担当者にもSolrの知識が必要とされる
  • 問題に気付けない

以降でその内容について補足していきます。

スケールアウトしない構成である

  • 検索システムはデータ増とアクセス増の2軸でスケールアウト構成を取る必要があります。
  • アクセス増にはSolrサーバを追加することで対応できるようにはなっていましたが、該当の検索システムはアクセス数というよりデータが多いことが特徴でこのデータ増に追従出来なくなっていました。

スケールアップの限界

  • 該当の検索システムは元々オンプレ環境で運用していましたが、ヒープサイズ不足やFull GCが頻発するようになっていたため、暫定対処としてAWS側に移行し、GCチューニングを行いました。
  • ただしこの対応も一時的なもので、データ数自体は日々増加している状況の中で使用しているGCアルゴリズム的に、メモリを増やしてどうこうできる状況ではなくなっていました。
    • データ増に伴いOOM Killer、ヒープサイズ不足が発生 → ヒープサイズを拡張 → STWのインパクトが大きくなるという負のループに陥る

Solrのバージョンが古い

  • 既存の検索システムで利用しているSolrのバージョンは3.2.0でした
  • バージョンが古くても通常時の運用にはそれほど支障はありませんが、何か問題があった場合の調査が大変でした(ドキュメントが見つからない)
  • またSolrのバージョンupに伴いindexing/検索性能はupしていますので、性能面でのデメリットであったり
  • 以降のバージョンで提供された様々な機能が利用できない等のデメリットがありました

本番環境のデータを利用した検証が難しい

  • 既存システムで本番環境相当のデータで検証する際にはindexing処理だけで数日かかるという状況で、環境構築自体がハードルとなっていました。

開発担当者にもSolrの知識が必要とされる

  • Solrへの検索時はphprubyといったクライアント側でSolrのクエリを組み立てて実行していました。
  • クライアント側を担当する開発担当者はその時々で変わり、ある程度はSolrを知っている人もいれば、全く知らない人まで様々です。
  • 一方Solrのクエリは書き方によってはキャッシュを使えていなかったり、または逆にキャッシュを無駄に汚してしまったりと、同じ検索結果を得るにしてもある程度Solrの知識を必要とします。

問題に気付けない

  • 必要なデータやログが適切に可視化され監視されていないため、サービス影響、ユーザ問い合わせがあって初めて問題に気付くケースがありました

更改後の検索システム

上記で挙げた課題感を解決するために設計したアーキテクチャが下記になります。

f:id:pma1013:20200203172549p:plain

  • 変化に強いシステムへ

    • SolrCloud構成に変更しつつ、データ増にはデータsharding、アクセス増にはデータreplicationでスケールアウト可能な構成に変更
  • 環境再現性

    • 全てコンテナベースのアプリケーションとなっているため、本番と同じマシンスペック、構成を簡単に構築可能
    • 本番データを利用した検証も短時間で可能となった
    • indexing処理についてもBUYMAで利用しているメインのDBにアクセスする必要がなく、中間DBを利用することで数時間でfull-import可能
  • 耐障害性の向上

    • プラットフォームにKubernetesを採用し、想定外のアプリケーション停止、ノードdown時にもセルフヒーリングで自動復旧可能
  • マイクロサービス化(隠蔽化)

    • アプリケーション〜Solr間に検索APIを新たに用意。これによりアプリケーション側はSolrの独自クエリを意識することなく、検索APIを介したシンプルなI/Fをベースとした開発が可能に。
    • Solrの独自クエリなど、専門的な知識を開発者が持つ必要がなくなり、学習コストが抑えられることで開発効率が上がる
    • 不適切なクエリにより、Solr側のキャッシュをうまく使えないなどの問題回避
  • 障害検知

    • 監視、ログ運用基盤としてDatadogを利用し、インフラ/ミドル/アプリケーションのメトリクス情報やログを統合的に管理可能とした
  • 属人性の排除

    • 検索システム間でバラバラだったスキーマ定義を汎用的なものにし、複数の検索システムをシームレスに対応可能に
    • dynamicフィールドをベースとしているため、スキーマ定義自体も運用上ほぼ変更する必要がない
  • GitOps対応

    • 全てのシステム設定をコードとしてGit上で管理
    • 運用上変更頻度が高いものはコマンド実行不要でCI/CDで可能

苦労した点

今回せっかくシステム更改するならCloudNativeなシステムにしたいという思いがありこのような構成にしました。
SolrのようなstatefulなアプリケーションをコンテナとしてKubernetes上で扱うのも初めてでしたし、 監視、ログ運用周りも今回の対応と合わせて刷新したことで対応範囲が広くなり苦労した点も多くありました。
ここではいくつかピックアップして紹介したいと思います。

  • Solr/ZookeeperをKubernetes上に構築、運用した経験がない

    • Solrに関してはあまり構築事例もなく問題に遭遇した際の解析に難航
    • リソース設計、ログ周りなど、コンテナ/Kubernetesだからこそプラスαで考慮しなければ行けない点が多々あった
  • 監視、ログ運用基盤の検討

    • コンテナ/Kubernetesベースのシステムをどう監視すべきか?
    • ログはどう扱うべきか?
  • 検索APIの設計、開発

    • I/F仕様どうするか?
    • 開発しながらSolr/Zookeeper周りの設定や問題対応をするのが辛い
  • Solrバージョンアップによる未知の問題対応

    • indexing処理周りで問題多発
    • SolrCloudかつDIHで並列indexing処理を行うこと自体が事例がなく、色々とはまる

今後に向けて

システム更改により当初挙げていた課題感というのは概ね解決することが出来ました。
ただし今回のようなシステムを構築、運用したことでてきた新たな課題や、まだまだ改善が必要だなと思えるところもあります。

  • indexing処理などはクライアント側の処理と密になっている箇所がまだまだあり、今後もブラッシュアップが必要
  • Solrのような複雑かつstatefulなアプリケーションは運用がやはり大変。Kubernetes operatorを利用するなどして運用をもっと楽にしつつ、属人性もなくしていきたい。

さいごに

今回は検索のインフラ面を中心としたお話を紹介させていただきましたが、 弊社では検索のサービス向上(ex パーソナライズサーチなど)や自然言語処理機械学習を活用したサービス導入なども積極的に行っています。
今回技術的なところにはほぼ触れませんでしたが、ここどうなってるの?など少しでも興味、関心を持たれたら是非お気軽にお話だけでも聞きにいただければと思います。

株式会社エニグモ 正社員の求人一覧 hrmos.co

データサイエンティストとしてアウトプットを改善するために必要な4つの力

はじめに

エニグモでデータサイエンティストを名乗っている庄子です。こちらは Enigmo Advent Calendar 2019 の25日目の記事です。 今年の振り返りも兼ねてのポエムとなります。

さて、データサイエンティストが活躍するためのスキル要件として、いくらでも切りようがあると思いますが、特に自分自身に感じている課題について、4つの力という観点で書きたいと思います。

その1 提案力

  • PoCとして小規模のデモを行う
    • そのデータサイエンスのアウトプットが使えそうか、事業に詳しい人に想像してもらう

実際にデータサイエンスを使って問題解決できそうな場合も、実際にやってみないと分からないですし、得られたアウトプットが事業に有効かどうかを、事業に詳しい人に意見をいただいた方が良いでしょう。筋が悪そうな分析は早めに判断してもらうためにも、なるべく小規模でPoCを行います。 本年度の振り返りとしては、このPoCの手数がもっとあった方が良いと感じました。

また、データサイエンティストが使う手法でできることは、データサイエンティストがもっとも良く分かるはずです。もちろん、使われる手法に関して詳しい人がデータサイエンティスト以外のポジションを担っているケースもあると思いますが、そのケースはここでは考えないことにします。

なお、課題設定が済んでおり、入出力が決まっているテーブルデータに対しては、AutoMLを十分ですし、AutoMLに変えられるようなタスクをこなすだけであれば、データサイエンティストは価値を発揮できません。 課題設定と分析を繰り返し、仮説検証を繰り返しながら課題設定の質を上げるスキルこそがデータサイエンティストの価値を差別化すると思います。

  • 提案を誤解なく伝えるために、共通言語を関係者と揃える
    • 使う手法や方法論を伝える概念を伝える
    • 伝えたことをドキュメントとして残し、説明の場にいなくてもそのドキュメントを読めば共通言語が理解できるようにする
    • 関係者が運用している具体的なデータを使って示す

共通言語を揃える場合は、最初に自分が受ける説明として、こんな説明ならすっと理解できそうというのを思い出しながら、説明するということは普段気をつけているつもりでしたが、後から考えると考慮が不十分だと思うことは多々ありました。特に、背景や経緯を共有できている前提から資料を作ってしまう場合です。 分析レポートの報告の際に、なるべく普段気をつけていたはずだと思いますが、ドキュメントの作成や整備も、振り返るともっとやりようがあると思います。 質問された点について補足を付け加えていくなどして、社内のナレッジの質を高め、リテラシーを共有できるのかと思います。

その2 ヒアリング力

  • 定期的に情報交換の場を現場のメンバーと設ける

こちらの取り組みを参考にし、ざっくばらんにデータ活用で解決してほしい課題について話すというランチミーティングを設定しました。

データ民主化を加速させる「分析ワクワクタイム」 - pixiv inside

このランチミーティングをきっかけにとあるPoCに取り組みましたが、中途半端なところで頓挫してしまっている状況です。 さらに、定期的にということも実現できていません。やったら効果的かもしれないと思うことをすぐに実現できず、せっかくヒアリングしたのに形にできないということに負い目を感じてしまった部分もあります。

いずれにせよ、定期的にコミュニケーションを行うことで、風通しをよくする、心理的安全性を担保することが重要だったりするので、またどこかの機会で復活させていきたいと思います。

  • 現場での運用について体験させてもらう

今年は取り組めなかったですが、ビジネス理解をもっとも得られる方法が実際に体験することにあると思います。データサイエンティストが共通言語の理解を得にいくということで矢印の向きは反対になりますが、先に挙げた共通言語を揃えるということにもつながると思います。

その3 タスク依頼力

  • 得意な領域に専念し、タスクを切り出してより得意な人に任せることを考える
    • 実験的なコードを書くことに場合に専念する
    • 仕様が固まる、あるいはプロダクトに関わる部分はエンジニアに任せる

プロダクトに乗るべきコードの要件はここで詳しく触れませんが、手元で自分が扱うコードが読みやすく、条件や機能の拡張がしやすいものだったら良いなと何度も思いました。そのようなコーディングを行うためには言語やフレームワークのベストプラクティスを踏まえている、専門家に任せた方がよいです。

データサイエンスに関するプロジェクトを実行するにあたり、必要なスキルで全てに秀でるのは多くの人にとって現実的ではないでしょう。依頼をする内容を、その領域を詳しい人へ誤解なく橋渡しができるくらいの知識があるという前提は置きますが、なるべく、得意な領域に専念した方が、組織としてのアウトプット力は上げられるはずです。

個人の領域として広くやれるのが弊社の良いところでもあるのですが、実験のためのコードを書く時間の割合を多くすることが、現在ネックになっていると思いました。

  • タスクを分解する
    • 分解した単位で、インプットとアウトプットを明確にする

こうやって書くと、基本的な仕事の進め方の話になりような気がします。。達成基準が明確な、細切れなタスクに分解できるということは、仕事の依頼もしやすくなり、今後育成という観点でも必要になってくる力です。

その4 採用力

  • 来て欲しい人に、その会社に来る魅力をきっちり伝える
    • 具体的なタスクや、データサイエンティストの要件を上手く伝える

人的リソースを増やすために、データサイエンティスト自体の人員を増やすということも当然選択肢に入ると思います。今年度は、データサイエンティスト、および、データアナリストの採用にも、面接などを通し関わらせていただきました。

もちろん活躍できそうな人を採用することが目的になりますが、活躍を期待できる人材ほど、他社からも内定が出ることは容易に想像できます。給与といった待遇面以外で考えるとすると、仕事内容やその会社で働くことの魅力を伝えるかどうかにかかっていると思います。

そこで、ミスマッチをなくすということに務めてきました。課題と思っているとこと、なるべく包み隠さず伝えたということはできていたと思います。実際に業務を行うイメージを固めるために、面接官以外のメンバーとの面談の場を設けることも実現することができました。

  • 応募者の良いところを引き出して聞き出す

会社のフェーズを考えると、データサイエンスに関する施策をドライブしてくれる人が活躍できそうという思いがあったため、提案力に優れている人を通す傾向が強かったですが、応募要件を上げてしまう大きな要因となっていたような気がします。

今後は、始めは提案が少々不得意でも、技術力を活かして活躍できる人も採用したいという想いがあります。課題はたくさんある中で、その人の経験と相性が良さそうなタスクの質問をして、考えを掘り下げる、という質問の組み立てについても、改善していきたいと思います。

一方、できていた部分としては、データサイエンティストの業務経験が想定より不足していても、社内のメンバーとコミュニーケーションが安心して任せられ、学習やそれまでの業務への取り組みを伺い、間違いなく必要なスキルはキャッチアップできると判断できる方は面接を通していました。このケースでは応募者の良いところを上手く引き出せていたのではないかと思います。

さいごに

その1,2がコミュケーションに関する話題、その3,4が人的リソースに関する話題になります。大きな枠組の話題としては、如何にスキルや知識をアップデートするか、という日々のインプット・アウトプットも挙げられますが、また機会があれば書くかも、、しれません。

なお、今回できていないことを中心に書いてしまったせいか、ちょっと胃もたれしそうな気分になりました。ただし、やりたいのにやれてないことが多いということは、伸び代がすごい!とも言えるのかと思います。一つ一つやるべきことに取り組んでいくしかしないですね! こんな私でも暖かく受け入れてくださり、(きっと)裁量を最大限に与えてくれる弊社なので、共に切磋琢磨してEnigmoをグロースさせたい仲間を絶賛募集中です。

株式会社エニグモ 正社員の求人一覧

hrmos.co

Rubyでのデザインパターンの使用例を説明する!!

こんにちは、サーバーサイドエンジニアの平井です。

こちらは、Enigmo Advent Calendar 2019 、24日目の記事です。

昨年の1月にエニグモインターンとして入社してから一年が経とうとしています。早いもので、新卒の肩書きもそろそろ無くなってしまいますね。

今回は、Rubyによるデザインパターンを読んで、デザインパターンを勉強したので、そのアウトプットをさせていただきます。 タイトルの通り、デザインパターンについて実際の使用例を探してみました。そのパターンと使用例は以下になります。

まずは、Strategyパターンから説明します。

Strategyパターン

Strategyパターンとは

メソッドの中に溶け込んでいるアルゴリズムを別クラスとして分けて、切り替えができるようにするパターンです。 サンプルコードを利用し、悪い例を修正していく形で、さらに、Strategyパターンについて説明していきます。

適用前
class Report
  def initialize
    @title = '月次報告'
    @text = [ '順調', '最高の調子' ]
  end

  def output_report(format)
    if format == :plan
      puts("#{title}")
    elsif format == :html
      puts("<title>#{@title}</title>")
    else
      raise "Unknown format: #{format}"
    end

    @text.each do |line|
      if format == :plan
        puts(line)
      else
        puts(" <p>#{line}</p>")
      end
    end
  end
end

問題点としては以下が挙げられます。

  • output_reportメソッドについて、formatの種類が増えるにつれて、メソッドが長くなる。

Strategyパターンを適用すると、以下のようになります。

class HTMLFormatter
  def output_report(title, text)
    puts("<title>#{@title}</title>")
    text.each do |line|
      puts(" <p>#{line}</p>")
    end
  end
end

class PlainTextFormatter
  def output_report(title, text)
    puts("#{title}")
    text.each do |line|
      puts(line)
    end
  end
end

class Report
  def initialize(formatter)
    @title = '月次報告'
    @text = [ '順調', '最高の調子' ]
    @formatter = formatter
  end

  def output_report
    @formatter.outpout_report(@title, @text)
  end
end

report = Report.new(HTMLFormatter.new)
report.ouput_report

このように、同じ目的(レポートをフォーマットする)を持ったオブジェクトを、ストラテジとして定義し、そのストラテジをごっそり交換できるようにするのがStrategyパターンです。ストラテジは同じインターフェースを持っているので、ストラテジの利用者(コンテキストと呼ぶそうです)は、どのストラテジを利用するかを知らずにすみます。適用前は、ReportクラスはHTMLとPlainText、それぞれの出力方式を知っていました。ただ、Strategyパターンを適用した後は、出力方式に関する知識はReportクラスからストラテジオブジェクトに移りました。

実例

Strategyパターンは、Wardenという認証フレームワークで使われています。

github.com

Warden::Strategies::Baseのサブクラスに認証の方法を実装することで、その認証方法を切り替えることができます。 そのWardenを使った、Deviseというgem(ログイン方法を簡単に実装できる)のソースコードを見ると、実際にどのようにStrategyパターンが使われているのかを確認できます。 下のソースコードを見るとわかるように認証するオブジェクトはwardenですが、認証方法に関する知識はDatabaseAuthenticatableクラスRememberableクラスが持っています。

module Devise
  module Strategies
    class DatabaseAuthenticatable < Authenticatable
      def authenticate!
        # データベースにあるデータで認証を行う処理
      end
    end
  end
end
module Devise
  module Strategies
    class Rememberable < Authenticatable
      def authenticate!
        # クッキーを使って認証を行う処理
      end

DatabaseAuthenticatableは、データベースにあるメールアドレスとパスワードで認証します。Rememberableは、クッキーからデータベースにあるレコードを探してきて認証します。

Observerパターン

Observerパターンとは

あるオブジェクトの状態の変化を、そのオブジェクト自身がその変化を知りたいオブジェクトに対して知らせる仕組みがObserverパターンです。 システムの各部分が、あるオブジェクトの状態を知りたいとき、例えば、誰かの給料が変わった時に、その変更を経理部門が知る必要がある時を想定します。 悪いパターンのサンプルコードを見ていきます。

class Payroll
  def update(changed_employee)
    puts("#{changed_employee.name}の給料が#{changed_employee.salary}に変わりました")
  end
end

class Employee
  attr_reader :name, :employee

  def initialize(name, title, salary, payroll)
    @name = name
    @title = title
    @salary = salary
    @payroll = payroll
  end

  def salary=(new_salary)
    @salary = new_salary
    @payroll.update(self)
  end
end

payroll = Payroll.new
taro = Employee.new('Taro', 'President', 200, payroll)
taro.salary = 300

このソースコードの悪い点は以下になります。

  • 他のオブジェクトが、財務情報を知る必要が出た時に、実際に変化したのは、Employeeではなく他のオブジェクトなのにも関わらず、Employeeクラスを修正する必要がある。

Rubyによるデザインパターンに書かれていた設計原則として、変わるものを変わらなものから分離すると述べられています。この原則を当てはめて、変わるもの(誰がtaroの財務状況を知る必要があるか)を変わらないもの(Employeeクラス)から分離させます。 Observerパターンを当てはめると以下のようになります。

class Payroll
  def update(changed_employee)
    puts("#{changed_employee.name}の給料が#{changed_employee.salary}に変わりました")
  end
end

class TaxMan
  def update(changed_employee)
    puts("#{changed_employee.name}に新しい請求書を送ります")
  end
end

class Employee
  attr_reader :name, :employee
  include Subject

  def initialize(name, title, salary)
    super()
    @name = name
    @title = title
    @salary = salary
  end

  def salary=(new_salary)
    @salary = new_salary
    notify_observers
  end
end

module Subject
  def initialize
    @observers = []
  end

  def add_observer(observer)
    @observers << observer
  end

  def delete_observer(observer)
    @observers.delete(observer)
  end

  def notify_observers
    @observers.each do |observer|
      observer.update(self)
    end
  end
end

payroll = Payroll.new
taxman = Taxman.new

taro = Employee.new('Taro', 'President', 200)

taro.add_observers(payroll)
taro.add_observers(taxman)

fred.salary = 300

このように、ニュースを持っているオブジェクトとそれを受け取る側にきれいなインターフェースを作るアイデアがObserverパターンです。ニュースを持っているオブジェクトはSubjectと呼ばれ、それに関心のあるオブジェクトはオブザーバーです。今の状態だと、Employeeはどれだけのオブザーバーが自分の給料の変更に関心があるかを知らなくて済みます。なので、新しくtaroの給料の変更を知る必要があるオブジェクトが出てきた場合は、そのオブジェクトがオブザーバーとしての共通のインターフェースであるupdateメソッドを実装して、add_observersするだけで、Employee自身は何も変化しません。

実例

こちらは実際に使われている例ではなく、rails-observersを使うと簡単にActiveRecord用のobserverクラスを作ることができるという説明をします。 githubのREADMEのサンプルコードですが、

class CommentObserver < ActiveRecord::Observer
  def after_save(comment)
    Notifications.comment("admin@do.com",
                          "New comment was posted",
                           comment).deliver
  end
end

この場合、Comment#saveが終了した際に、after_save内の処理を実行します。 サンプルコードでは、Subjectのセッターに、オブジェクトが変更した際に実行するメソッドを追加する必要がありましたが、rails-observersを使えば必要ありません。

Iteratorパターン

Iteratorパターンとは

Iteratorパターンとは集約オブジェクトが子オブジェクトのコレクションにアクセスする方法を外部に提供するテクニックです。 外部イテレーターと内部イテレータがあります。 外部イテレーターについて、サンプルコードを使って説明します。

class ArrayIteratoor
  def initialize(array)
    @array = array
    @index = 0
  end

  def has_next?
    @index < @array.length
  end

  def item
    @array[@index]
  end

  def next_item
    value = @array[@index]
    @index += 1
    value
  end
end

array = ['red', 'green', 'blue']
i = ArrayIterator.new(array)

while i.has_next?
  puts("item: #{i.next_item}")
end
=>item: red
item: green
item: blue

外部イテレーターの場合は上のように、子オブジェクトに対して処理内容を伝えます。 一方、内部イテレーターとは、eachのように、イテレーター自身がブロックを受け取って、 その処理内容を子オブジェクトに伝えます。

実例

内部イテレーターが使われている箇所を、railsソースコード内のactionpack/lib/action_dispatch/http/mime_type.rbから簡単に探すことができました。

module Mime
  class Mimes
    include Enumerable

    def initialize
      @mimes = []
      @symbols = nil
    end

    def each
      @mimes.each { |x| yield x }
    end

    def <<(type)
      @mimes << type
      @symbols = nil
    end

    def delete_if
      @mimes.delete_if { |x| yield x }.tap { @symbols = nil }
    end

    def symbols
      @symbols ||= map(&:to_sym)
    end
  end

  SET = Mimes.new

  class Type
  
  def register
  # 他の処理
  
  SET << new_mime
  # 他の処理
end

上の例の場合、Mimesクラスの子オブジェクトに対して処理内容を伝える場合は、コードブロックを利用します。 また、Enumberableモジュールをincludeしてeachメソッドを定義することで、include?, map, selectなどの配列を走査する際に便利なメソッドを使うことができます。 actionpack/lib/action_dispatch/http/mime_types.rbに下のような処理がありました。

Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
Mime::Type.register "text/plain", :text, [], %w(txt)
Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript )
Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "text/vcard", :vcf
Mime::Type.register "text/vtt", :vtt, %w(vtt)

# 他のMimeタイプをMimeクラスに追加する処理が続く

Typeクラスのregisterメソッド内でMimesクラスに追加をする処理があり、actionpack/lib/action_dispatch/http/mime_types.rbでMimeタイプを追加していました。

Builderパターン

Builderパターンとは

オブジェクトの構築のロジックに対して責任をもつBuilderを作り、そのBuilderを使ってオブジェクトを作成するパターンです。 例としては、以下になります。

class Computer
  attr_reader :drives

 def initialize(drives=[])
    @drives = drives
  end
end

class Drive
  attr_reader :type
  attr_reader :size
  attr_reader :writable

  def initialize(type, size, writable)
    @type = type
    @size = size
    @writable = writable
  end
end

class ComputerBuilder
  attr_reader :computer
  
  def initialize
    @computer = Computer.new
  end

  def turbo(has_turbo_cpu = true)
    @couputer.motherboard_cpu = TurboCpu.new
  end

  def add_cd(writer = false)
    @computer.drives << Drive.new(:cd, 760, writer)
  end

  def add_dvd(writer = false)
    @computer.drives << Drive.new(:dvd, 4000, writer)
  end

  def add_hard_disk(size_in_mb)
    @computer.drives << Drive.new(:hard_disk, :size_in_bm, true)
  end
end

builder = ComputerBuilder.new
builder.add_cd(true)
builder.add_dvd
builder.add_hard_disk(1000000)

computer = builder.computer

上の場合は、Computerを作るために必要なcdのsizeが760であるというような、Computerクラスのインスタンスを作成するためのロジックが、ComputerBuilderのクラスに集まっています。Computerを作るための実装の詳細をBuilderクラスが隠蔽しています。

実例

BuilderパターンはBuilderでグレップすれば良いので、簡単に見つけることができました。 以下のソースコードは、mastodonという短文投稿型のSNSサイトのソースです。

github.com

# app/lib/rss_builder.rb

class RSSBuilder

  def initialize
    @document = Ox::Document.new(version: '1.0')
    @channel  = Ox::Element.new('channel')

    @document << (rss << @channel)
  end

  def title(str)
    @channel << (Ox::Element.new('title') << str)

    self
  end

  def link(str)
    @channel << (Ox::Element.new('link') << str)

    self
  end

 ## 他のメソッドが続く

RSSとは、Webサイトのニュースやブログなどの、更新情報の日付やタイトル、その内容の要約などを配信するため技術で、XML形式で記述されます。 このRSSBuilderは、titleがchannelの子要素であるなどのXMLの構成の詳細を隠蔽しています。 このRSSapp/serializers/rss/account_serializer.rb上でRSSBuilderクラスを使って作成されています。

class RSS::AccountSerializer

  def render(account, statuses, tag)
    builder.title("#タイトルの名前")
            .link("#タグのurl")
#他のRSSを作成する処理が続く

    builder.to_xml
  end

このAccountSerializerクラスはRSSxmlの構成の詳細を知りません。タイトルやlinkの情報を与えるだけでRSSを完成させることができます。

まとめ

Rubyによるデザインパターンを読んでみて、実際の使用例を探してみようと意気込んでみたものの中々見つけることができませんでした。 普段から、ソースコードを読む際にデザインパターンを意識して読んでみるのもアリなのかなと思いました。

また、実際に使用例を見つけるためのソースコードリーディングを通して、デザインパターンに対する理解を深めることができただけでなく、雑多に新たな知識を得ることができました。 そして、モヤモヤしていたことをはっきり理解できた時の快感がソースコードリーディングの楽しみだなと改めて感じたので、積極的に読んでいこうと思いました。

最後まで読んで頂き誠にありがとうございました。

参考

株式会社エニグモ 正社員の求人一覧

hrmos.co

STI、Polymorphic関連を実際に使用した話

こんにちは!サーバーサイドエンジニアの@hokita222です!
有酸素運動は脳を活性化させると聞いて、最近は朝会社に出社せずにランニングしております!

それはさておき、これは Enigmo Advent Calendar 2019 23日目の記事です!

今回は弊社が運営するサイトのBUYMA (Ruby on Rails)に追加した機能で、STIポリモーフィック関連を使ってみたので、どういう設計にしたかを書いていこうと思います。
※使ってみたって話で、それぞれどういう特徴なのかなどの詳しい説明はしておりません。

どんな機能作ったの?

「〇〇キャンペーン」などの施策で、その日あった取引の中で特定の条件(商品ID、カテゴリーID、何円以上など)のものを絞り込み、その対象の取引に対して特定のアクションをさせます。

今回はこの機能の「特定の条件で絞る」の設計を説明していきたいと思います。

設計するなかで実現したかったこと

「特定の条件で絞る」機能を作るなかで重要視していたのは、「条件が増える可能性が高い」ことです。今は商品ID、カテゴリーIDで絞れるけど、将来的にはブランドID、購入数などで絞れるようにするかもしれません。なので条件が容易に追加できることを意識しました。

テーブル設計

テーブル名 説明
promotions 施策テーブル(施策名、施策期間など)
rules 施策の条件テーブル(どの取引を対象にするかの条件)
actions 施策のアクションテーブル(対象の取引に対しての行うアクション)※今回こちらは割愛
rule_targets 施策の条件テーブルとターゲット(itemsやcategoriesなど)との中間テーブル
items 商品テーブル(元々あるテーブル)
categories カテゴリーテーブル(元々あるテーブル)

※promotions, rules, actionsの形はsolidusを参考にさせてもらいました。

STI

f:id:hokita:20191216185531p:plain:w400

rulesテーブル(ruleモデル)に対してitem, category, price_gteというサブタイプクラスを作成しました。

なぜSTI

  • STIだと条件が増えたときにクラスの追加のみで済む。
    • 具象テーブル継承、クラステーブル継承だとテーブルまで作らないといけないので面倒くさい。
  • 将来的に追加されるカラムが少ない。
    • あるサブタイプクラスしか使わないカラムが増えていくと、テーブルにカラムが増えすぎるってこともあると思いますが、今回は問題ないと判断。
  • サブタイプクラスで共通のメソッドでも、それぞれ処理が異なる。
    • 処理が同じなら親クラスで共通のメソッド生やせばすむし、結果enumを使って異なる処理だけを例外的に書けばいいよねってなりますが、今回はそれぞれ異なります。

RailsSTIの機能は使わなかった

Rails標準のSTIの機能は使いませんでした。理由としては「継承」を使いたくないから。今回は委譲で対応しております。(RailsSTIを期待されてた方すみません。)
※と言ってみたは良いものの、Railsから外れるツラミも結構大きいです。今回の機能では問題ないのですが、あまりおすすめはしないです。

ソースコード

# 施策(調整予約)ルールテーブル
#
# カラム
#  - type: どの条件か(どの条件のクラスを使用するか。例: item, category, price_gte)
#  - value: 条件に必要な任意の値(〇〇円以上とか。商品IDとかはここには入らない。)
# 
class Rule < ActiveRecord::Base
  self.inheritance_column = nil

  belongs_to :promotion
  has_many :rule_targets

  delegate :build_relation to: :sub_rule

  private

  # サブタイプクラス
  def sub_rule
    "Rules::#{type.classify}"
      .constantize
      .new(self)
  end
end

継承を使用しないので、サブタイプクラスを呼ぶメソッドを作ってます。

module Subtypeable
  extend ActiveSupport::Concern

  attr_accessor :rule

  delegate :value, :rule_targets, to: :@rule

  def initialize(rule)
    @rule = rule
  end

  # 特定のrelationをactiverecordのメソッドを使用して絞り込む
  def build_relation(relation)
    relation
  end
end

rubyではダックタイピングできるので基本インターフェースは不要ですが、他のエンジニアにもこのメソッド使ってくれという願いを込めてmodule作りました。(ちょっとインターフェース以上のこと書いているのはご愛嬌)

# 商品ルール
class Syohin
  include Subtypeable

  # override
  def build_relation(relation)
    targetable_ids = rule_targets.pluck(:targetable_id)
    relation.where('trades.item_id in (?)', targetable_ids)
  end
end

引数のrelationに取引のRelationを渡すことによって中間テーブルにある対象のIDたちで絞ることができます。(正確にはRelationに条件を付加します。)

# 価格(以上)ルール
class PriceGte
  include Subtypeable

  # override
  def build_relation(relation)
    relation.where('trades.price >= ?', value)
  end
end

こちらは商品ルールとは異なり、rulesテーブルのvalueカラムを使用します。

Polymorphic関連

f:id:hokita:20191216185605p:plain:w400

なぜPolymorphic関連か

  • rule_targetsモデルに対して、複数のモデルを紐付けたかったため。
  • rule_targetsからはitemscategoriestargetsという同類の関係
  • rulestargetsは多対多なので中間テーブルrule_targetsとのPolymorphic関連

ソースコード

# 中間テーブル
class RuleTarget < ActiveRecord::Base
  belongs_to :rule
  belongs_to :targetable, polymorphic: true
end
class Item < ActiveRecord::Base
  has_many :rule_targets, as: :targetable
end
class Cateogry < ActiveRecord::Base
  has_many :rule_targets, as: :targetable
end

※本当はSTIと同じくインターフェース用のmodule作ったほうがいいのですが、共通のメソッドがないので作ってません。

ちなみに

promotionモデルはこうなっております。

# 施策テーブル
class Promotion < ActiveRecord::Base
  has_many :rules
  has_many :actions

  # いろんな条件をまとめたrelationを受け取ることができる。
  def detect(relation)
    rules.inject(relation) do |rel, rule|
      rule.build_relation(rel)
    end
  end
end

Promotionモデルが窓口となっており、コントローラーなどの呼び出し元はdetectメソッドを呼ぶだけで条件での絞り込みが可能になります。

各クラスがそれぞれの仕事を担ってくれているので結構シンプルなのではないでしょうか。またSTI、Polymorphic関連で実装したおかげでif文, case文が全くありません。

さいごに

現状新しい施策条件が増えた場合でもクラス一つ作るだけで解決するので、小一時間あれば条件の追加が可能となりました。

明日は弊社新卒の平井くんです!どんな記事書くんでしょうね。楽しみ!わくわく


弊社では一緒にレガシーコード脱却を目指すエンジニア大募集中です! hrmos.co

GitLabCI+ArgoCDを使って、「マージしたら5分でKubernetesへデプロイ」を実現する

こんにちは。Engimo インフラチームの夏目です。 この記事はEnigmo Advent Calendar 2019の22日目の記事です。

最近はこちらのインタビューでも触れたとおりKubernetesクラスタを作ったり壊したりしていまして、今日の記事はKubernetesにおけるアプリケーションデプロイに関してのお話です。

Kubernetesの継続的デリバリ、どうしてますか?

Kubernetesをプロダクション環境で利用されているそこのあなた!アプリケーションをどうやってデプロイしていますか?

  1. ローカルでDockerImageをビルド
  2. DockerHubのプライベートリポジトリへプッシュ
  3. kubectl editでDeploymentsのイメージタグを最新のものへ変更

といった人の手による温かみのあるデプロイをしている?
それはそれで心がこもった良いやり方かもしれませんが、おそらく少数派ですし、システムに心は必要ありませんのでやめましょう。みなさん基本的にはSpinnakerJenkinsXといったCDツールを利用されているかと思います。

現在私達が開発中のシステムにおいては、デプロイパイプラインのCDツールとしてArgoCDを利用しています。

以下のような特徴があり、シンプルにGitOpsデプロイを実現することができるツールです。

  • GUICLIでmanifestの適用状態が確認できる
  • kustomizeに対応している
  • Sync/Hook機能でデプロイフローを柔軟にコントロールできる

そもそもGitOpsとはどういった概念なのか、という点に関しては提唱元であるWeaveworksのblogがわかりやすいのでご参照ください。

www.weave.works

なお、Weaveworksが開発しているFluxもGitOpsを実現するためのCDツールなのですが、ArgoCDと比べると公式ドキュメントがややわかりづらい印象があります。
また、kustomizeには対応しているものの、他の機能(イメージタグ更新検知)とあわせて利用しようとするとmanifestの構成が複雑になる(選定時の実装では)、といった理由もあって採用には至りませんでした。

EnigmoにおけるArgoCDを使ったGitOpsデプロイフロー

ArgoCDのアーキテクチャは公式ドキュメント記載の以下の図のように、GitOpsの全ての領域をカバーするわけではなく、他のCIツールと併用することを前提としています。

f:id:enigmo7:20191220215726p:plain:w450
ArgoCD architecture

CIツールについては図のようにTravisCIやCircleCI、最近であればGithub Actionsなども候補になるかと思いますが、EnigmoではメインのGitサービスとしてGitLabソースコードを管理しており、GitLabにビルトインされたCI機能であるGitLabCIをフローに組み込みました。

GitLabCIとArgoCDを組み合わせたデプロイフローは以下のような構成です。

f:id:enigmo7:20191220215731p:plain
GitOps DeployFlow

ArgoCDの提唱するBestPracticeに則り、アプリケーションのコードリポジトリKubernetesのmanifestリポジトリは分離させています。
以下にフェーズごとの具体的な流れを書いてみます。

Application Image Build Phase

アプリケーションリポジトリmasterブランチにDockerイメージのバージョンアップMRをマージすると、GitLabCIで以下のジョブが実行されます。

  1. Dockerイメージのビルド・タグ設定
  2. ECRへDockerイメージをPush

これでアプリケーション側のデプロイ準備は終わりました。

Application/Kubernetes Config Deployment Phase

更新したイメージタグを利用するようmanifestファイルを修正し、manifestリポジトリにMRを作成→マージすると、以下のフローで処理が走ります。

  1. GitLabのmanifestリポジトリをCodeCommitRepositoryへミラーリング(参考:GitLab Repository Mirroring)
  2. Kubernetes上で稼働しているArgoCDがCodeCommitRepositoryを参照し、クラスタの状態とmanifestファイル定義を比較
  3. イメージタグがクラスタとmanifestで異なることをArgoCDが検知して、クラスタのDeploymentsを更新

このように、アプリケーションとKubernetes manifestのコードをそれぞれGitにマージするだけで、kubectlを使うことなくデプロイが実行されました。

なお、ArgoCDのチュートリアルや概要的な部分は公式ドキュメントにわかりやすくまとめられているため省略させて頂きます。

argoproj.github.io

ArgoCDを使ったデプロイフローのPros/Cons

Pros

デプロイ作業の省力化

システム開発の初期は冒頭で記載したような、manifestファイルを修正→kubectl diffクラスタとmanifestの差分を確認→kubectl applyクラスタへ適用!という原始時代のようなオペレーションを行っていました。有り得ませんね。

継続的デリバリという方式なので当たり前ではありますが、このフローを構築してからはGitのマージ操作のみでデプロイ作業が完結し、タイトルのようにおよそ5分でアプリケーションがデプロイされるようになりました。

柔軟なデプロイフローの実装

開発中のシステムはRailsアプリケーションで構成されているのですが、Railsアプリケーションにつきもののdb:migrateをどうやって実施するか?という点についても、ArgoCDのSync Waveで解決することができました。

db:migrateを実行するJobを作成し、annotationで他のDeploymentよりも先にSyncが実行されるように設定することで、他のアプリケーションよりも先に確実にdb:migrateが実行され、スキーマ関連のエラーが回避できます。

また、現時点では上記デプロイフローには実装していませんが、Hookの仕組みを使うことでデプロイ後にSlackへ通知を飛ばしたり、正常終了したJobを削除する、といった振る舞いを加えることもできます。

Cons

デプロイスピード

これはArgoCDの短所ではないのですが、このデプロイフローは構成図でもわかるようにオンプレミスのGitLabと、AWSのマネージドサービス群の2つにわかれています。

本来であればArgoCDから直接GitLabのmanifestリポジトリを参照し、GitLabのContainerRegistryからDockerImageをPullするようにすれば、オンプレミスからのイメージPushやRepositoryミラーリングが必要なくなり、もっとシンプルなフローになります。
ただ、AWS側からオンプレミスのGitLabへアクセスするよう設定すると、オンプレミス側への依存が発生することと、KubernetesクラスタからはあくまでAWSのサービスのみとやりとりをするのが、リソースアクセス権限設定の観点からもわかりやすいだろう、という理由からこういった構成になっています。

なお、manifestリポジトリミラーリングはリアルタイムではなくおよそ5分間隔で実行されます。これに加えて、ArgoCDがクラスタとmanifestの差分を3分間隔(調整可能)でチェックしているため、マージしてから最長8分待たないとデプロイされない、という状態になっています。

いざデプロイしようと思ってマージをしてから実際にアプリケーションが更新されるまでの時間がやはり長過ぎるのではないか、という印象は否めないため、前述した理屈を否定する形にはなりますが、GitLabをAWSへ移行してArgoCDから直接参照させる、といった方法の改善を検討しています。

イメージタグ更新修正の手間

デプロイフローに記載したように、アプリケーションリポジトリの更新だけではなく、manifestファイルのイメージタグも都度修正しなければなりません。アプリケーションのコードを更新したら即デプロイ!ということにはならず、もうワンアクションが必要になります。

プロダクション環境であれば、意図しないデプロイを防ぐためにもそういったアクションが挟まるのも良いかもしれませんが、テスト環境のようにスピーディーにデプロイして検証をしたい!という場合は手間でしかありませんね。

ArgoCDの競合プロダクトであるFluxはそういった手間が省略できる、イメージタグの自動検知・適用機能があるため、同じような機能がほしい!というIssueがArgoCDのリポジトリに報告されていました。

FluxとArgoCDは設計思想が違うから、というコメントとともにあえなくクローズされる……かと思いきや、ArgoCDとFluxがそれぞれの機能を統合したGitOpsツールの開発をすすめる旨の発表をしたため、緩やかに期待してArgoCDを使い続けよう、という状況です。

www.weave.works

まとめ

  • KubernetesのGitOpsツールとしてArgoCDを利用しています
  • Dockerイメージのビルドとプッシュ用CIツールとして、GitLabCIを利用しています
  • ArgoCDを利用することで柔軟なデプロイフローを実現することできます

この記事では我々のシステムではどのようにGitOpsデプロイを実現しているか?という構成を紹介させて頂きました。
Kubernetesの運用ツールはまだまだデファクトと呼べるようなものが揃っていない状況ですが、日々新しい機能が盛り込まれたり統廃合される様子は、ウォッチしていて楽しいものでもありますね。

EnigmoではKubernetesをはじめとしたCloud Native Computingの運用に一緒に立ち向かう仲間を募集しています。

hrmos.co


明日の記事の担当はサーバサイドエンジニアの @hokita222 さんです。おたのしみに。

平成Ruby会議01 に登壇しました

こんにちは。サーバサイドエンジニアの伊藤です。

Enigmo Advent Calendar 2019 、21日目の記事です。

先週末の12月14日(土)、平成Ruby会議01 に登壇し、「Play with Ruby」という題で発表してきました。

タイトルからはわかりにくいのですが、parse.y をライブコーディングで操作し、雑な感じに右代入を実装するという話です。

TL;DR

とっても楽しかったです。

内容

RubyKaigi2019 で聴いた

「Play with local vars」by Tatsuhiro Ujihisa/@ujm

トークに影響を受けて、右代入を実装した話です。

詳しくはこちらのスライドを御覧ください。

speakerdeck.com

発表に至るまで

実は今回が初めての人前での発表でした。

2019 年中に人前で発表することが1つの目標だったので、とりあえず CfP*1 を出してみました。

CfP を出した時からずーっと気が休まる暇がなかったように思います。CfP が採択されるまでは心のどこかで却下されることを祈っていましたし、採択されてからは発表当日インフルエンザにでもなってくれないかと思っていたほどです。。。

結果的には有難いことに採択され、幸運なことにインフルエンザにもならず、2019 年中に人前で発表するという目標を達成することができました。

ちなみに、CfP を提出した時点では parse.y をまともに読んだこともありませんでした。右代入の具体的な実装などは皆目見当がついておりませんでした。完全な勢いです。。。

CfP Advent Calendar 2019 というものを発見しましたので、 CfP の内容はこちらに公開してあります。

sean0628.hatenablog.com

こちらを見ていただけるとわかると思うのですが、右代入を実装することはこの時点で決めていました。

ただ、右代入の実装方法が思いつかない最悪のケースが頭をよぎり、チキリにチキリきった結果、発表のタイトルを「Play with right-assignment」ではなく「Play with Ruby」としてしまいました。

わかりにくいタイトルになってしまいすみませんでした。。。

平成Ruby会議01 当日

トークセッション

いろいろな角度からの発表があり、とても刺激的でした。正直、自分の発表のことで頭がいっぱいで、全体の半分くらいしか発表を見ることはできませんでした。。。

聴講だけで、もう一度参加したいくらいです。

個人的に1番印象に残っているのは、金子さんのキーノート「What is expected?」です。 内容はパーサーの話でした。ちょうど1ヶ月前に聞きたかったです。かなり深いところまで解説されていて、理解できなかったところもありました。時間を見つけて復習したいと思います。

発表

さて、自分の発表ですがみなさまのおかげで全体的にはスムーズに進めることができたかなと思っております。

今回初めてライブコーディングにも挑戦しました。

スライドのメモ確認のために敢えてミラーリングしてなかったのですが、スクリーンの角度が急すぎてエディターが何も見えないというハプニングが発生しました。 さらには、ライブコーディング中の1番緊張していたタイミングで、 Siri が起動するという奇跡的なトラブルにも見舞われました。

(今年1番 、いや人生の中で1番 Siri に怒りを覚えました。)

そんなトラブルもありましたが、発表を聴きに来てくださった方々が暖かく見守ってくださったお陰でなんとか最後まで辿り着くことができました。 ありがとうございました。

f:id:sean0628:20191217094018j:plain

懇親会

懇親会では RubyRails のコミッタさんやコントリビュータさんから、 OSS との関わり方や RubyKaigi の裏話など貴重なお話を伺うことができました。

また、キーノートスピーカーの金子さんからは、右代入の実装に関して具体的なアドバイスをいただきました。 今回いただいたアドバイスをもとに、また Play したいと思います。

最後に

運営・スポンサー・参加者の皆さん、ありがとうございました。

みなさまのおかげで素敵な1日を過ごすことができました。

f:id:sean0628:20191217094002j:plain

株式会社エニグモ 正社員の求人一覧

hrmos.co

*1:Call for Proposal

LINE Front-end Framework(LIFF) v2でQRコードを読み取るよ

こんにちは!
冬が苦手なディレクターの神吉です。

この記事はEnigmo Advent Calendar 201920日目の記事です。

LINEの開発者情報をチェックしていて、ちょっと前にLIFF v2がリリースされていました。
https://developers.line.biz/ja/docs/liff/release-notes/#spy-releasedate_20191016

f:id:enigmo7:20191218130815p:plain:w300

LIFFとは

LIFFとはLINE Front-end Frameworkのことで、LINEが提供するウェブアプリのプラットフォームです。
JavaScriptを書いて開発する感じです。

LIFFはけっこう前から提供されていましたが
LIFF v1 → LIFF v2になって主に下記ができるようになりました。

  • 外部ブラウザでLIFFアプリが動作する。
    • v1はLINE内ブラウザでのみ動作していました。
  • ユーザーのプロフィール情報とメールアドレスを取得できる。
    • LINEログインでできる仕組みに近い仕組みなのかなと思います。
  • QRコードを読み取れる。
    • 新機能ですね!
  • LIFFアプリの動作環境を細かく取得できる。
    • 外部ブラウザで動作するようになって細かい情報を取得する必要がでてきたのかと思います。

LIFF v1もちゃんと触っていなかったのですがこの機会にv2を触ってみたいと思います。
とりあえずLIFFでQRコードを読み取りをやってみます。

事前準備

こちらを事前に準備してください。

LIFFアプリの追加

LINE Developersコンソールよりの 「LIFFアプリを追加」より必要事項を入力してLIFFアプリの追加をしてください。
※今回はQRコードを読み取りをするのでScan QRの設定はONにしてください。

f:id:enigmo7:20191219115402p:plain:w500

またLIFFサーバーAPIでもLIFFアプリを追加することができます。
https://developers.line.biz/ja/reference/liff-v1/#add-liff-app

追加後LIFF URLが発行され、LIFFを開くことができるようになります。

f:id:enigmo7:20191219105854p:plain:w300

LIFFアプリの開発

ここからは実際の開発手順です。

LIFF SDKを読み込み

LIFF SDK https://static.line-scdn.net/liff/edge/2.1/sdk.js を読み込んでください。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>sample</title>
  </head>
  <body>
    <script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script>
  </body>
</html>

LIFFアプリを初期化する

sdk.js 読み込み後に追加してください。

liff
  .init({
    liffId: "XXXXX" // liffIdを指定。line://app/XXXXXの部分
  })
  .then(() => {
    // 処理はここから
  })
  .catch((err) => {
    // エラー処理
    console.log(err.code, err.message);
  });

QRコードを読み取り処理

document.getElementById('qr_button').addEventListener('click', function() {
    if (liff.isInClient()) {
        liff.scanCode().then(result => {
            document.getElementById('qr_text').textContent = result.value;
        }).catch(err => {
            document.getElementById('qr_text').textContent = err.message;
        });
    }
});

liff.scanCode()はLINEのQRコードリーダーを起動し、読み取った文字列を取得するメソッドです。
#qr_buttonにクリックするとLINEのQRコードリーダーが立ち上がり、 QRコードをスキャンすると#qr_textQRコードで読み取ったテキストが挿入されます。

f:id:enigmo7:20191219105829p:plain:w300

注意点

  • iOS版LINEバージョン9.19.0以降では、liff.scanCode()の提供を一時停止しています。(2019/12/18時点)
  • liff.scanCode()は、外部ブラウザでは利用できません。

どう活用する?

手軽にQRコードリーダーを使えるのは良いです。QRの読み取りも速いです。
またLIFFはLINEのuserIdと紐づけてイベントログを取得でき、データ活用できます。
そういったところ活かしオンラインとオフラインをつなぐような施策使えればと思います。
(今のところBUYMAでの具体的な活用案が浮かばずすみません。。)

感想

  • かなり簡単に試すことができました!
  • LIFFを使用する上で実行環境による挙動の違いはしっかり把握しないといけないなと感じました。
  • 今後もLINEの開発者情報はこまめにチェックして、新機能は早めに試してみたいと思います。

参考

株式会社エニグモ 正社員の求人一覧

hrmos.co