株式会社エニグモのブログ - TECH PLAY

TECH PLAY

株式会社エニグモ

株式会社エニグモ の技術ブログ

252

article p { text-align: justify; } .entry-content h3 { font-size: 140%; } Hello, this is Fernand from the Global Buyma Team. As an engineer, I am responsible for maintaining the English version of Buyma . I recently had the opportunity to attend an AI conference along with my manager. I’m excited to share our experiences and insights with you. So fasten your seatbelt, relax, and enjoy the journey through the world of AI that we embarked upon. Merhaba! On May 10-11, 2024, I had the privilege of immersing myself in the vibrant intersection of technology and tradition at the two-day AI conference organized by Mad Street Den in Istanbul. This city, which uniquely straddles two continents—Europe and Asia—offered the perfect backdrop for an event that brought together global leaders to explore the forefront of artificial intelligence across various sectors. AI on Global Buyma Global Buyma employs the capabilities of Vue.ai's AI service to curate the most suitable product images for display in search results. The AI evaluates and identifies the best quality image from those uploaded by sellers to represent each product. Bridging Cultures, Advancing Technology The conference kicked off with an insightful tour around Istanbul's historic centre, embodying the perfect blend of cultural heritage and modernity. Then we headed to the Sarnıç, a 1500-year-old cistern lying at the heart of Istanbul, which set the stage for an evening of engaging dialogue. Over dinner, we forged meaningful connections with fellow attendees, delving into spirited discussions about the transformative potential of AI and sharing diverse perspectives on its future impact. ・Dinner at The Sarnıç. Honoring the special guests of the event. Key Highlights from the Conference The following day, we journeyed to the magnificent Sait Halim Pasha Mansion, where leaders from diverse sectors, including pharmaceuticals, e-commerce, and logistics, shared their insights on implementing AI within their industries. Ashwini Asokan and Anand Chandrasekaran, the founders of Mad Street Den, provided a comprehensive overview of how their tools are automating the way businesses operate, shifting the focus from infrastructure to innovation. Then, it was followed by a thought-provoking panel discussion that shed light on the banking industry's cautious stance on migrating to cloud services, highlighting the need for policy changes and compliance measures to ensure security. And much wide array of topics, extending from Zenyum's innovative application of AI in dental health assessments to the exciting reveal of Vue.ai's advanced personalization engine, designed to elevate the shopping experience. Their presentations vividly demonstrated the revolutionary capabilities and the expansive future of AI technologies. ・Panelists delved into AI's transformative impact and its common use cases ・Timo Weiss, Nithya Subramanian, and Sandal Kakkar engaged in a compelling discussion about "Gen AI: Existential Crisis or Competitive Advantage?" Evening Soirée: A Sunset to Remember After absorbing a plethora of information, it was time to let loose. The soirée, set against a breathtaking Istanbul sunset, provided a relaxed atmosphere for us to unwind, dance, and revel in the sumptuous dinner prepared for us. ・Turkish performing their traditional dance, Sufi whirling Looking Ahead for Global Buyma Discussions about these AI applications sparked ideas for enhancing the Global Buyma system, such as implementing auto- tagging based on product page content. Additionally, the personalization engine where AI analyzes shoppers' interactions would be perfect, allowing for dynamically tailored product displays and recommendations to cater to individual preferences. However, some guests noted that the use of AI for language translation in page displays requires further refinement.   Bye for now! ・From right: Ashwini Asokan, CEO & Founder of Mad Street Den, Hibaru Maywood, Head of Global Buyma 株式会社 エニグモ すべての求人一覧
Argo Workflowsを使ったPersistentVolumeの定期バックアップ こんにちは。 インフラグループ Kubernetes チームの福田です。 今回はPV(PersistentVolume)の定期バックアップシステムについて紹介したいと思います。 PVのバックアップについて PVのバックアップといっても色々とありますが、本記事ではスナップショットの取得を意味します。 スナップショットの取得は CSI が対応していれば、 external-snapshotter を利用することで、CustomResourceを書くだけで実現できます。 extenal-snapshotterを全く知らない方は以下の AWS の記事が概要を掴む参考になるかと思います。 aws.amazon.com バックアップシステムの仕組み 概要 システムの概要は以下のようになっています。 Argo Workflowsが定期的にVolumeSnapshotリソースを作成し、それをGitLabへプッシュする。 GitLabのCIが マニフェスト をバリデーションする。 Argo CDが マニフェスト のあるコード リポジトリ の変更を検出して、それを Kubernetes にデプロイする。 詳細 定期的に実行する処理 定期的な処理の実行はCronWorkflowで行っています。 単にCronJobとせず、CronWorkflowとした理由は、一度だけの実行や、定期処理の一時停止などが GUI で簡単にできるからです。 VolumeSnapshotリソースの生成 VolumeSnapshotリソースの生成を行うアプリケーションは Golang で実装した自前のコンテナアプリになります。 コンテナアプリは前述したCronWorkflowで実行されます。 世代管理機能 コンテナアプリはVolumeSnapshot マニフェスト を生成すると共に、古いVolumeSnapshot マニフェスト を削除するローテーション機能を持っています。 何世代分までのVolumeSnapshot マニフェスト を維持するかは 環境変数 で指定可能になっています。 VolumeSnapshot マニフェスト 間の新旧の比較はVolumeSnapshot マニフェスト 生成時のタイムスタンプを独自の アノテーション に記載し、それを比較することで実現しています。 管理対象フラグ ローテーション管理の対象とする否かのフラグを表現する独自の アノテーション もあります。 原則、コンテナアプリで生成されたVolumeSnapshotはこのフラグが立っています。 手動で作成したVolumeSnapshotやコンテナアプリによって作成された永続的に残しておきたいスナップショットについては、このフラグを下ろすことでローテーションの管理対象から外れて自動で削除されないようにできます。 生成される マニフェスト のサンプル apiVersion : snapshot.storage.k8s.io/v1 kind : VolumeSnapshot metadata : name : SAMPLE_NAME namespace : SAMPLE_NAMESPACE annotations : snapshot-tools.enigmo.co.jp/rotation : "true" snapshot-tools.enigmo.co.jp/rtime : 12345678 spec : source : persistentVolumeClaimName : SAMPLE_PVC volumeSnapshotClassName : SAMPLE_VSC snapshot-tools.enigmo.co.jp/rtime アノテーション は マニフェスト 生成時のタイムスタンプを表現している。 snapshot-tools.enigmo.co.jp/rotation アノテーション は世代管理の対象とするか否かのフラグになっている。 コードプッシュ コンテナアプリはVolumeSnapshot マニフェスト を作成、削除するとその変更をプッシュします。 プッシュ時のオプションでMergeRequest( github でいうPullRequest)を作成し、CIが成功したら自動でMRをマージするようにしています。 CIの実行 MRに対しては手動による マニフェスト のデプロイと同じ条件でCIが周り、問題がなければそれをマージするようになっています。 これにより、システムが生成した マニフェスト コードを特別扱いすることなく、統一したポリシー(例えば、フォーマッタやセキュリティチェック等の マニフェスト に対するバリデーション)を適用できます。 Argo CDによるデプロイ masterにマージされた変更を検出し、それを クラスタ へ自動で適用するようにauto-syncを有効化しています。 また、世代管理機能によってVolumeSnapshot マニフェスト の削除される場合もあるので、auto-pruneも有効化しています。 以下がArgoCD Applicationのサンプルになります。 apiVersion : argoproj.io/v1alpha1 kind : ApplicationSet metadata : name : snapshots spec : generators : - list : elements : - namespace : SAMPLE_NS1 - namespace : SAMPLE_NS2 - namespace : SAMPLE_NS3 template : metadata : name : '{{namespace}}-snapshot' spec : project : SAMPLE-PROJECT source : repoURL : GITLAB_URL targetRevision : master path : SAMPLE_PATH destination : server : K8S_ENDPOINT namespace : '{{namespace}}' syncPolicy : automated : prune : true 純粋なApplicationでなく、ApplicationSetとしている理由は我々は複数ネームスペースの運用をしているためです。 (Argo CDのApplicationは1つのネームスペースしかデプロイ先として指定できない。) まとめ Argo Workflowsを使ったPersistentVolumeの定期バックアップシステムについて紹介させていただきました。 実装方法としてオペレータパターンで開発する選択肢もありましが、cronの一時停止やカウントダウンタイマーなどを GUI で管理できる点からArgo WorkflowsのCronWorkflowを利用する実装を選択しました。 今回の記事が Kubernetes ネイティブな自動化システムの開発の参考になれば幸いです。 エニグモ ではエンジニアを含む各種ポジションで求人を募集しております。 株式会社 エニグモ すべての求人一覧 hrmos.co
「安心して何度も利用したくなる マーケットプレイス 」を作る!UXデザイングループを紹介 エニグモ でTech職種の採用や、採用広報を担当している 廣島 です。 この記事は、 エニグモ で新入社員向けのオンボーディング研修として実施する 部署紹介プログラム の中で プロダクトマネージャーやディレクター、UI/UXデザイナーが所属するグループであるUXデザイングループマネージャーの山田さんがグループの説明をした内容をまとめた記事です。 グループのミッションや、チーム体制、カルチャー、どのように他チームと連携しながらプロジェクトを推進しているかについて説明します。最後に、 BUYMA サービスやUXデザイングループの今後の展望や、UXデザイングループで仕事を行う魅力についてもお話しします。 ※この記事は Enigmo Advent Calendar 2023 の25日目の記事です※ BUYMA サービスについて まずは、 エニグモ が運営するサービスの BUYMA について説明します。 BUYMA は累計会員数1000万人超えの海外ファッションNo.1の通販(EC)サイトです。 世界中に在住する約20万人のパーソナルショッパー(出品者)から、ファッションを中心とした世界中のアイテムを購入できる「売り手」と「買い手」が個人であるCtoCのグローバル マーケットプレイス です。 主に個人の海外在住の日本人のネットワークを構築して海外から商品を購入することが可能で、多種多様な購入者の要望に答えています。商品の約半分は海外から届きます。 BUYMA が間に入ることで安心安全に海外の買い物ができるプラットフォームとして運営しています。 出品アイテムの特徴としては高級品×希少品の商品のラインナップが充実しており、海外限定デザインや、国内未上陸ブランド、国内完売入手困難品など高付加価値のアイテムを世界中から購入することができます。 BUYMA はファッションを主力にしつつも、インテリア・アウトドア、旅行などカテゴリーを拡大しています。 BUYMA のビジネスモデルの詳細は下記をご覧ください。 https://enigmo.co.jp/business/ UXデザイングループのミッション・業務内容とは? エニグモ で ディレクション を担うグループはUXデザイングループ(以下UXDグループ)と呼びます。 (UXとは「ユーザーエクス ペリエ ンス」のことで、ユーザーが商品やサービスを通じて得られる体験を指します) UXDグループとは名前の通り、ユーザーのPain(痛み)に向き合いながら、サービスをより使いやすく・より良くしていくために解決すべきことを考え、実行するグループです。 上記により、ユーザー体験を向上させ、安心して何度も利用したくなるような マーケットプレイス へ BUYMA を成長させることがミッションです。 さらに、UXDグループは BUYMA サービスのプロダクト側(エンジニア・デザイナー)とビジネス側(MD、 マーケティング ・広告、データ、CS、オペレーション等)のちょうど中間に位置して両者を支えサービスを前進させる役割を担います。 具体的な業務領域は? 実際にどういった業務領域を担当しているかについてお話しします。 BUYMA の新機能・既存機能の改修・新サービスの企画・ ディレクション からUI/UXと幅広く担当します。一般的なWeb業界の言葉でいうと具体的には下記のような業務です。 プロダクトマネジメント プロジェクトマネジメント 制作 ディレクション / 進行管理 各施策・プロジェクトの効果検証 UX視点からの企画提案 / サービスデザイン 定性 / 定量 調査 UIデザイン フロントエンドプログラム(主にインタ ラク ションデザイン部分)の実装 上記からも分かるようにUXDグループは ディレクション を行うグループではあるが、UIデザインやフロントエンドプログラムなどの制作も行っていることが特徴的です。 フロントエンドプログラムは、Reactでの実装などはエンジニア部署のフロントエンドエンジニアが行いますが、インタ ラク ションデザインなど実際に画面上で動く CSS や JavaScript の実装はUXDグループがおこないます。 その為、企画から制作までをシームレスにグループ内で行えユーザーの反応や外部環境に合わせてスピーディーに柔軟に対応することが可能です。 色々言いましたがまとめると業務内容は下記となります。 BUYMA の様々な課題に向き合い最適なスコープでユーザーに価値を提供する ユーザーの反応を定性、 定量 の両側面から分析して企画・施策に繋げる スキルとスキル(企画と開発)の間に立ち仲介・翻訳を行い、場合によっては制作も行う グループの体制について Webサービス における様々なスキルを持ったメンバーが集まっています。特徴としては、ディレクター部門であるが、制作するメンバーもいることです。一般的な職種で言うと、プロダクトマネージャー、ディレクター、 SEO ディレクター、UI/UXデザイナー、フロントエンドエンジニアが所属します。 前項で説明した業務領域全てを1人のメンバーが網羅しているのではなく、それぞれのメンバーの得意分野を活かしながら多岐にわたる領域をカバーし業務を行っています。 さらにグループは下記2つのチームに分かれています。 ・ プロダクトマネジメント チーム : マーケティング戦略 系の施策を担当するチーム ・ UI/UXチーム :UX戦略系の施策を担当するチーム グループの体制を示した図です。 明確にチームが別れているのではなく、UXDグループのマネージャーがUI/UXチームのマネージャーを兼任していることもあり、定例は一緒に行っています。週1回グループの定例の他、プロジェクトベースで進めています。 実際の・ プロダクトマネジメント チームとUI/UIXチームの業務は下記のインタビュー記事をご覧いただくと、よりイメージできるかと思います。 ・ BUYMAのWebディレクター(PdM)紹介/エニグモの魅力は?UXDグループとは?インタビューしました! ・ UI/UXデザイナーインタビュー/ユーザーによりよい体験を届けプロダクト価値の向上に取り組む! 開発フロー・プロジェクトの中でのUXDグループの役割 プロジェクトの開発のフローの中で、UXDグループのメンバーが他職種メンバーと協力しながらどういった役割を担うのかについてお話しします。 プロジェクトや施策に伴う開発はエンジニアやビジネスサイドなど他チームメンバーと連携してプロジェクト型チームで進行します。 開発全体の流れは、UXDグループメンバー(ディレクター)が企画をまとめプロジェクトがスタートします。ディレクターとエンジニアのリードメンバーやビジネスサイドのメンバーで企画をどのようにシステムに落とし込むのかを要件定義します。その後、エンジニアのリードメンバーがシステム設計を担当し、タスクを分解してエンジニ アメンバー を アサイ ンします。 プロジェクトや施策はディレクターから発案されることもあれば、 マーケティング やカスタマーなど他の部門からの提案もあります。 要件定義はプロジェクトの特性により異なりますが、通常は主にディレクターが他の ステークホルダー との ヒアリ ングを通じて要求を洗い出し、システムをどう作るかをエンジニアと共に要件定義を進めるのが主流です。 プロジェクトの目標やKPIの策定もディレクターが中心に行います。各プロジェクトでは、新規機能を開発する際に、「なぜこの新機能を開発するのか」「新機能リリースにより、ユーザーにどのような体験や行動を期待するか」「新機能リリース時の成功の定義は何か」などを、各プロジェクトの ステークホルダー と明確にしています。 最後に サービスの課題や今後の展望、 エニグモ でUXDにジョインする魅力、どういった方がマッチするかについて、マネージャーの山田さんにインタビューしました。 サービスの課題や今後の展望 今期の重点テーマである「 BUYMA サービスをより安心できる体験にする」ことは引き続き進めていきます。安心と一言でいっても様々な側面が存在します。 たとえば、高級ブランドを扱っているため、ユーザーが商品が本物であるか不安に感じることもあります。これに対処するため、来期も偽物の不安を払拭するための施策を推進していきます。 また、ビジネスモデル上、高級品および希少品を扱っていることが強みですが、日用品と比べて購入頻度が低い傾向があります。そのため、サイトに訪れるタイミングが少なくサイトにユーザーが定着しにくく、サービスとユーザーの距離が遠くなることが課題です。 この課題に焦点を当て、ユーザーがサイトに定期的に訪れる動機づけや、サービスとの継続的な関係を築くための取り組みにも注力していきたいと考えています。 UXデザイングループの魅力 プロジェクトごとに各職種のメンバーが アサイ ンされ、小規模チームが結成されます。そのためメンバー、一人ひとりが大きな裁量を持ち、エンジニア、デザイナー、ビジネスメンバーと密に連携し、距離が近く風通しも良く別部門という感じはしません。良い人(互いを尊重し、前向きなメンバー)が多いため、仕事上の人間関係による変なストレスがあまりなく、プロダクト開発方法や体制を共に進化させるマインドが根付いています。 また、グローバル×CtoCサービスという、 BUYMA 独自の仕組みやUXを構築するフェーズに携わることができます。出品者側と購入者側の双方のデータやユーザ―行動に基づくデータドリブンな開発が可能です。プロジェクトの成果を数値(売上、CVRなど)やユーザーの声によって直接実感することができます。 どういった方がマッチするか 経験やスキルも大事ですが、熱意やマインド面、カルチャーへの共感、 エニグモ へのバリューへのマッチを大切にしています。※バリューについての詳細は こちら 業務領域が幅広いため、俯瞰して物事を柔軟にアプローチできる方や、自らイニシアチブを取り推進できる方が活躍できるかと思います。 決まった案件をこなすだけでなく若手から裁量を持って働け、新規機能や新規事業の企画を自ら発案しユーザーに届け、世の中を変える可能性があります。 課題を見つけて積極的にア イデア を提案し進めていく方がマッチすると思います! BUYMA や関連サービスのサービス品質を常に進化させ、国内および世界各国のユーザーがますますサービスを利用して喜びや満足を得られるような機能を、楽しみながら共に考え抜いていただける方にジョインいただけると嬉しいです。
こんにちは、 エニグモ 嘉松です。 BUYMA のプロモーションや マーケティング を行っている事業部に所属、その中のデータ活用推進室という部署で会社のデータ活用の推進や マーケティング ・オートメーションツール(MAツール)を活用した販促支援、 CRM などを担当しています。 この記事は Enigmo Advent Calendar 2023 の 25 日目の記事です。 はじめに この記事では Google から提供されているBigQueryのオンライマニュアル「関数のベストプ ラク ティス(Best practices for functions)」を試してみた結果を紹介していきます。 「関数のベストプ ラク ティス」(日本語版) https://cloud.google.com/bigquery/docs/best-practices-performance-functions?hl=ja 「関数のベストプ ラク ティス」では、以下の4つのベストプ ラク ティスが紹介されています。 文字列の比較を最適化する 集計関数を最適化する 分位関数を最適化する UDF を最適化する はじめは4つ全てを1つの記事で紹介するつもりでしたが、記事を制作していく中で1つでもそれなりの記事ボリュームがあることが分かったので、読みやすさを重視して1つの記事で1つのベストプ ラク ティスを紹介していくことにしました。 ということで、この記事は「その1」として「文字列の比較を最適化する」を試した結果を紹介していきます。 文字列の比較を最適化する ベストプ ラク ティス 可能であれば、 REGEXP_CONTAINS ではなく LIKE を使用します。 BigQuery では、 REGEXP _CONTAINS 関数または LIKE 演算子 を使用して文字列を比較できます。 REGEXP _CONTAINS は多くの機能を提供しますが、実行に時間がかかります。 REGEXP _CONTAINS ではなく LIKE を使用すると、処理時間が短くなります。 特に、 ワイルドカード 一致など、 REGEXP _CONTAINS が提供する 正規表現 をフルに活用する必要がない場合には処理時間が短くなります。 次の REGEXP _CONTAINS 関数の使用を検討してください。 SELECT dim1 FROM `dataset.table1` WHERE REGEXP_CONTAINS(dim1, '.*test.*'); このクエリは次のように最適化できます。 SELECT dim1 FROM `dataset.table` WHERE dim1 LIKE '%test%'; なるほど。やはり機能の多い関数、複雑なパターンに対応できる関数よりも、単純なことしか出来ない関数の方が処理は軽い(速い)と。言われてみれば当たり前といえば当たり前ではありますが。単純なことしてできない関数が利用できるケースであれば、そちらを使ったほうが良い、 正規表現 による検索が必要ない場合は LIKE を使いましょう、ということですね。そもそも REGEXP_CONTAINS より LIKE を使うことを最初に考えるとは思うけどww( REGEXP_CONTAINS の方が汎用性が高いので常に REGEXP_CONTAINS を使っています、みたいな人は注意が必要です!) 試してみた 検証方法 REGEXP_CONTAINS と LIKE を使ったクエリを実行して比較していきます。 比較する候補となる値は 経過時間 と 消費したスロット時間 の2つ、クエリの「実行の詳細」から取得できます。 それぞれの意味は以下の通りです。 経過時間 クエリが開始されてから完了するまでの時間 サーバの使用状況などによる待機時間も含まれます 消費したスロット時間 「スロットとは、 SQL クエリの実行に必要な演算能力の単位です。」(「BigQueryのコンソールの(?)」より) スロットについての詳細は以下の Google のマニュアル「スロットについて」を参照ください。 https://cloud.google.com/bigquery/docs/slots?hl=ja BigQueryスロットは、BigQueryでSQLクエリを実行するために使用される仮想CPU ということで、ざっくり言うとクエリ( SQL の実行)に使用したCPU使用量です。 今回の検証では 消費したスロット時間 を比較ます。( 経過時間 だと Google のその時のサーバの負荷といった外部要因が加わるため) クエリはそれぞれ5回ずつ実行してその平均値を比較します。 クエリを実行するとクエリの結果がキャッシュされるので、キャッシュを使用しないように設定を行います。 なお、対象のテーブルは約1億件のテーブルを対象にしました。 キャッシュクリアの方法 検証中はキャッシュを使用してしまうと正しい計測ができないので、キャッシュを使用しないように設定を行います。 https://cloud.google.com/bigquery/docs/best-practices-performance-functions?hl=ja#optimize_string_comparison 検証結果 消費したスロット時間(時間:分:秒) 試行回数 REGEXP _CONTAINS LIKE 1 0:03:06 0:02:50 2 0:03:22 0:02:29 3 0:03:02 0:02:36 4 0:03:06 0:02:20 5 0:02:56 0:02:18 平均 0:03:06 0:02:31 REGEXP_CONTAINS を使用した場合の平均の「消費したスロット時間」は 3分6秒 だったのに対して、 LIKE では 2分31秒 と35秒短縮、減少率は 80.79% と約80%に短縮、約20%改善されました。 この20%の改善をどう取るか。かなり短縮できたと取るか、さほど変わらないと取るか。 個人的には、思ったほど変わらないな、という印象でした。 といのも、 SQL ではテーブルの結合方法(JOIN)や、絞り込み条件(WHERE句)をチューニングすると実行時間が半分になったり、場合によっては1/10になったりすることもざら、珍しくないため。 ただ、単純に LIKE を使うだけで20%削減されるのであれば、それは価値ありますよね。 このクエリで検索の対象としているカラムの平均の文字数は33文字と少なかったため大きな差が出なかった、もう少し文字数が多いカラムを対象にしたらもう少し差が出るのでは、と考え文字数の多いカラムを使って検証を実施しました。 追加検証結果(時間:分:秒) 平均文字数が758文字のカラムを使って比較 消費したスロット時間(時間:分:秒) 試行回数 REGEXP _CONTAINS LIKE 1 0:59:53 0:55:26 2 1:02:00 0:53:43 3 1:02:00 0:51:51 4 1:01:00 0:50:43 5 1:04:00 0:52:02 平均 1:01:47 0:52:45 まずカラムの平均文字数が増えたことで「消費したスロット時間」も増加しています。 平均文字数が33文字では約3分だったのに対して、758文字は約60分と約20倍に。 当然ですが検索する文字数が増えればその分として処理の時間も増えますね。 REGEXP_CONTAINS を使用した場合の平均の「消費したスロット時間」は 1時間1分47秒 だったのに対して、 LIKE では 52分45秒 と9分2秒短縮、減少率は 85.39% と約85%に短縮、15%改善されました。 平均文字数が33文字のときは約80%に短縮されたのに対して、758文字では約85%と短縮の率は減少しました。 この辺りの差は文字数のバラツキや検索する文字によっても差が出るのかもしれません。 文字数の多いカラムでは LIKE を使うことでよりパフォーマンスに差が出ると思いましたが、大きな違いはありませんでした。 まとめ REGEXP_CONTAINS と LIKE を使ったクエリを実行して比較してみた結果、LIKEを使った方がクエリのパフォーマンスは確かに改善された! その改善率は今回の検証では約20%だった!! 可能であれば、 REGEXP_CONTAINS ではなく LIKE を使用しましょう!!! 本日の記事は以上になります。 エニグモ Advent Calendar 2023 もこの記事で最後になります。 最後まで読んでいただきありがとうございました。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは! 今年7月に中途入社しました、コーポレートエンジニア(コーポレートIT[CO-IT]チーム) のフルセです! 今年も終盤(早いですねぇ、、)ということで、 Enigmo Advent Calendar 2023 の季節になりました!! クリスマスイブである 24 日目 を担当する私は入社エントリ・振り返りなど中心に自由に書きたいと思います! なお、この記事が少しでもコーポレートエンジニアに興味がある方や入社を検討してくださっている方の参考になれば幸いです。 Embed from Getty Images window.gie=window.gie||function(c){(gie.q=gie.q||[]).push(c)};gie(function(){gie.widgets.load({id:'eGD2T_vEQIpzxevVRkRvfQ',sig:'E4aHXJwgZtXDaAqpL9J550lu8QN5ONksi1IwL5kG1WY=',w:'556px',h:'311px',items:'531047383',caption: true ,tld:'com',is360: false })}); 簡単な経歴紹介 そもそもコーポレートエンジニアって何? 実際に入社してみて感じたこと 入社してからの仕事とその感想 内容 感想 こんな方に向いているかも(個人見解) 最後に 簡単な経歴紹介 私の経歴についてざっくり下記のようにまとめました。 ① 新卒でシステムインテグレータ企業にSEとして入社 →ITの基礎知識・技術を獲得! and IT業界の厳しさを痛感、、(大袈裟) ② システムインテグレータ企業を退職 →主にシステム相手の業務だったので、より人と接する仕事がしたいと考え決意 ③ 某アパレル商社の情シスへ転職 →培った知識とスキルを活かし新たな業種へチャレンジしたかった! →情シスが意外と性に合っていることに気づくきっかけに! ④ カナダへ留学 →ずっと挑戦したかった海外留学に挑戦! →語学はもちろん人との出会いや経験から人生にプラスになった! ⑤ エニグモ にコーポレートエンジニアとして入社 →帰国後にご縁があり、経験を活かしコーポレートエンジニアとして入社! そもそもコーポレートエンジニアって何? コーポレートエンジニアとは? コーポレートエンジニアとは、企業内のIT活用や運用を担当するエンジニアのことです。 企業のIT環境の変化は著しく、テレワークの普及などによって クラウド サービスの利用も活発になり、 SaaS 型のID管理や統合認証サービスを利用する企業が増えてきました。そのなかでコーポレートエンジニアは、社内ITの構築・運用をはじめとして、社内業務の課題解決のための企画立案や部門間の調整まで幅広い業務を担います。 もっと詳しく知りたい方は、チームメンバーの記事を読んでみてください! tech.enigmo.co.jp 実際に入社してみて感じたこと コーポレートエンジニアとして働く以上、社員の方々とのコミュニケーションをかなり重要だと考えていました *1 。 しかし、 エニグモ はリモートワーク中心の会社のため、Face to Face でのお話しする機会が少なくどのように交流の輪を広げていこうか少し不安(そもそも入社直後というのもあり。。)に感じていました。 そんなときに、2M(Monthly Meet-up) *2 に参加させていただきました。 そこでは色々な部署や役職の方と分け隔てなくフランクにお話しすることができ、一瞬にして不安が和らぎました。 やはり、新しい環境でのスタートはどうしても孤独感や不安がつきものだと思いますが、こういった交流会があると気持ちよくスタート ダッシュ が切れますよね!そもそも、こういった交流会を定期的に行なっている企業というのは少ないと思うので、これぞ エニグモ の良いところだと思っております。 それからというもの、毎月可能なかぎり参加し交流の輪を広げております!(無料で美味しいお酒とご飯が得られることも理由の一つなのですが笑) 2Mについて気になる方は、レポート記事がありますので是非ご覧いただければと! www.wantedly.com 入社してからの仕事とその感想 入社してからのざっくりとした仕事内容とその感想を書かせていただきました! 内容 社内ヘルプデスク対応 内容:PCの故障や不具合、各 種IT サービスのトラブル対応等 対応頻度:1〜2件/日 アカウント管理 内容:アカウントの付与・削除から数量管理・購入 対応頻度:1〜2件/週 IT機器の管理・キッティング 内容:PCやモバイルデ バイス の発注・管理・キッティング 対応頻度:10〜20件/月 社内ナレッジの作成・整備 内容:手順書やルールの新規作成・ブラッシュアップ 対応頻度:2回/週 中途入社者向けのオリエン 内容:PCのセットアップ方法の説明、社内ITサービスルールの共有 対応頻度:1〜2回/月 社内不要OA・IT機器廃棄対応 内容:廃棄物回収対応 対応頻度:2回/年 カオスマップ作成 内容:社内で利用中サービスを整理と今後導入が必要なサービスの洗い出し 対応頻度(見直し):2回/年 感想 一覧にしてみると、今年は色々なことをやらせていただいたと改めて感じています。 裁量を任せていただいているため、日々に マルチタスク をこなしておりますが、 それもまた自分のタスク管理力や遂行力の向上につながっていると感じます。 個人的にやりがいがあった業務としては、「社内不要OA・IT機器廃棄対応」です。 業者選定から始まり、やりとり(契約締結や回収日の調整など)、社内ナレッジ作成、各方面との連携など、これぞコーポレートエンジニアと感じるようなタスクだったため、とてもやりがいがありました。 それと山積みだった廃棄端末類をスッキリさせることができたという達成感が大きかったですね笑 またタスクとして面白かったものとしては、「カオスマップ作成」です。 初めてカオスマップというものを作成したので、そもそもカオスマップとは?からスタートし、他企業のカオスマップの調査、社内ITサービスの整理、今後導入が必要なITサービスの洗い出しを行いました。その後は、カオスマップをパズルのように作成する大変な作業がスタートし...と、ここでは書ききれないので、詳しい作成プロセスなどは機会があればまた書かせていただきたいと思います。(今回は割愛させていただきます。) それからというもの、試行錯誤を繰り返し、レビューを重ねてやっとの思いで完成させることができました。 結果として、時間をかけて取り組んだからこそ、サービスに対する理解や知見を広くできましたし、社内の課題(足りていないサービス)も炙り出すことができました。とはいえ、まだまだ未熟なカオスマップだと思っているので、これからもっと成熟させていきたいと思います。(楽しみ!) こんな方に向いているかも(個人見解) コーポレートエンジニアとしてのキャリアをスタートしたばかりの私ですが、 そんな私だからこそ考えるコーポレートエンジニアにはこんな人が向いているのではないかというのをまとめてみました! 私が思うに下記の3つの要素が肝になってくると思います。 コーポレートエンジニアの必要要素 新しい情報・技術に興味があり探求するのが好きな方(好奇心) 日々進化する IT技術 やサービスをいち早く自社へ展開・導入するため 最新の情報に敏感であれば、サービスなどがアップデートされたとしても柔軟に対応することができるため 人とコミュニケーションするのが得意・好きな方(コミュニケーション力) 業務上(問い合わせ対応・情報共有など)何かとコミュニケーションが必要になるため 問題を切り分けて解決することが得意・好きな方(柔軟性) コーポレートエンジニアは企業の多くの課題を与えられるので、一つ一つ解決し柔軟に対応することが求められるため ここで書かせていただいた3つの要素は、かなり重要なポイントになると考えます。 コーポレートエンジニアの業務は、IT関連の何でも屋のようになりがちだと思っております。 そのためタスクを挙げ出したらキリがないうえ、日々変化します。 そのため、柔軟性はもちろん、社内外連携のためのコミュニケーション力やITサービスに関する知見を広げるための好奇心が重要になってきます。 もちろん、足りてないと感じている要素があったとしても、業務を通じてレベルアップすることは可能だと思います!(私も絶賛レベルアップ中...) ※あくまで個人の見解なのでご了承ください。 最後に 私もまだまだ未熟なので、日々進化する IT技術 ・情報に置いていかれないよう自己研鑽を続けつつ、 社内の課題を一つずつ着実に解決し、より良い社内環境づくりに励みたいと思います! 最後まで読んでいただき、ありがとうございました!! 株式会社 エニグモ すべての求人一覧 hrmos.co *1 : 情シス時代に業務上のコミュニケーションがかなり重要だと理解していたため *2 : 毎月月初に開催される社内交流会
こんにちは、インフラグループ Kubernetes チームの福田です。 この記事は Enigmo Advent Calendar 2023 の22日目の記事です。 皆さんは Kubernetes のアップグレード、どうしていますか? Kubernetes は4ヶ月に一回、新しいマイナーバージョンがリリースされ、最新の3つのマイナーバージョンのみサポートされます。 つまり、原則は4ヶ月に一度、アップグレードをやらなければなりません。(最新バージョンであれば最大12ヶ月はサボれるという考え方もありますが。。。) 弊社では Kubernetes 環境としてEKSを使っており、 Kubernetes 本体のリリースと微妙に間隔が違いますが、最新を維持するために大体3〜5ヶ月毎にアップグレード作業をやっています。 この Kubernetes (EKS)のアップグレード作業をワークフロー化したのでそれを紹介します。 ワークフローとは 本記事でいうワークフローとはワークフローエンジンを使って自動化された作業のまとまりです。 ワークフローエンジンとは、作業プロセスをいい感じに管理してくれるツールです。 代表的なものとして、 Apache Airflow , digdag , Argo Workflows などがあります。 我々の場合、 Argo Workflows を利用しています。 EKSアップグレードワークフローの概要 上図は実際のワークフローのキャプチャです。 3段階の構造(上から順に実施される)になっていて、以下を実施する内容となっています。 廃止される API を使っていないかチェック EKSアドオンのアップグレードバージョンを特定 CloudFormationテンプレートを更新するPullRequestを出す。 元々、弊社では AWS リソースをCloudFormationで管理する運用になっていたことから、ワークフローのゴールがCloudFormationコードの修正(Pull Request作成)となっています。 ワークフローの入力 上図の通り、ワークフローには入力パラメータがあります。 ticket-id JIRAのチケットIDを指定します。 コード修正時のコミットメッセージやブランチ名の プレフィックス として利用されます。 kubernetes -cluster アップグレードを実施する対象の クラスタ をプルダウン形式で選択します。 eks-version Kubernetes versionをマイナーバージョンまで指定します。例: 1.28 ami EKSノードで使用するAMIを指定します。 EKSアップグレードワークフローの詳細 1. 廃止される API が無いかチェック Kubernetes ではマイナーバージョンの更新に伴って、機能追加や古い機能の削除のために API Versionが更新され、既存のものが廃止されることがあります。 そのため、 Kubernetes アップグレードを実施する前に廃止される API が無いかチェックしています。 pluto という OSS を使って、 クラスタ 上にあるリソースで廃止されるものを使っていないかチェックをしています。 2. EKSアドオンのアップグレードバージョンを特定 EKSでは Kubernetes のマイナーバージョンの更新と併せて、EKSアドオンのアップグレードが必要な場合があります。我々の場合、「EKSアドオンのバージョンはデフォルトバージョン※を指定する」というポリシーで運用しています。 ※ EKSではアドオン毎に Kubernetes のマイナーバージョンに紐づくデフォルトのバージョンを公開しています。 具体的な方法としては aws コマンドで、指定した Kubernetes のマイナーバージョンに対応するEKSアドオンのデフォルトバージョンを取得しています。 aws eks describe-addon-versions --addon-name ADDON_NAME --kubernetes-version K8S_NAME --query 'addons[0].addonVersions[?compatibilities[0].defaultVersion==`true`].addonVersion | [0]' 3. CloudFormationのコードを管理している リポジトリ に Kubernetes アップグレードのためのPull Requestを出す このステップについては前のステップで得られるEKSアドオンのアップグレードバージョンとワークフローへの入力値を元に、CloudFormationのコードを修正して、Pull Requestを出します。 また、Pull Requestに人手で実施する作業内容を記載します。 このステップにおける処理はGoで実装した自前の CLI ツールで行っています。 考慮事項 このステップを実装するにあたり、考慮したことを紹介します。 VPC CNIのアップグレード 前のステップでEKSアドオンのアップグレードバージョンを特定していますが、 VPC CNIについてここで問題があります。 VPC CNIのアップグレードは一度に1つのマイナーバージョンのみアップグレード可能です。 例えば、 Kubernetes 1.27に対応する VPC CNIのデフォルトバージョンが v1.12.6-eksbuild.2 で Kubernetes 1.28に対応する VPC CNIのデフォルトバージョンが v1.14.1-eksbuild.1 の場合、予め VPC CNIのバージョンをv1.13系にアップグレードする必要があります。 実際の実装としては VPC CNIのアップグレードバージョンのマイナーバージョンが元のバージョンより2つ以上離れている場合は、本ワークフローを実施する前に VPC CNIのマイナーバージョンをアップグレードするように促すメッセージを出してワークフローはそこで失敗するようにしています。 手順書 Kubernetes アップグレードの実施自体の手順としてはCloudFormationの実行コマンドだけですが、実際の作業手順はもう少し複雑です。 我々の場合はアップグレードの実施コマンドに加えて以下を盛り込む必要がありました。 作業前 作業通知の開始アナウンス 作業時の監視アラートのサイレント設定 作業後 アップグレードが正しく完了したかの確認 作業時の監視アラートのサイレント設定解除 作業通知の終了アナウンス yaml をGoで編集する際の副作用 CloudFormationのテンプレートは yaml ですが、これをGoで書き換えると発生する副作用があります。 それはGoで yaml を扱うために yaml ファイルを エンコード して、プログラム上で何らかの処理をして、再度 yaml ファイルに戻してやると空行やコメント等が消失してしまうというものです。 yaml としては等価なのだからという理由で切り捨ててしまえばそれまでですが、コメントや空行はリーダビリティの観点で有用なものも多いので、これは維持するようにしたいです。 まず、コメントの維持については yaml.v3 を使うことで解決できます。 具体的には以下のように yaml 中にある書き換えたいプロパティをpathで指定して、それをnewValueで更新するようにしています。 func (e *yamlEditor) updateYamlProperty(node *yaml.Node, path [] string , newValue string ) error { if len (path) == 0 { return fmt.Errorf( "invalid path" ) } for i := 0 ; i < len (node.Content); i += 2 { keyNode := node.Content[i] valueNode := node.Content[i+ 1 ] if keyNode.Value == path[ 0 ] { if len (path) == 1 { valueNode.Value = newValue return nil } else if valueNode.Kind == yaml.MappingNode { return e.updateYamlProperty(valueNode, path[ 1 :], newValue) } } } return fmt.Errorf( "invalid path" ) } 空行についてはdiffコマンドのオプションで空行を無視する差分のdiffを生成して、それをpatchコマンドで元のファイルに修正として適用するようにしています。 ## 元々存在するcloudformation template target.yaml ## プログラムによって書き換えが行われたcloudformation template。空行はなくなってしまっている。 target.changed.yaml diff -U 0 -w -b -B target.yaml target.changed.yaml > target.diff patch -i target.diff target.yaml 参考: https://github.com/mikefarah/yq/issues/515#issuecomment-830380295 まとめ 今回はArgo Workflowsを使った Kubernetes (EKS)のアップグレードについて紹介させていただきました。 紹介したワークフローはKubernetes1.27とKubernetes1.28のリリースの間で作成したもので、Kubernetes1.28のアップグレードはこのワークフローを使ってアップグレードしました。 これのおかげで面倒だったアップグレード作業を大分楽にすることができました。 明日の記事の担当は早野さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは。エンジニアの竹田です。 BUYMA の検索システムやMLOps基盤の開発・運用を担当しております。 こちらは Enigmo Advent Calendar 2023 の21日目の記事です 🎄 弊社では2021年頃よりMLOps基盤を Google Cloud PlatformのAI Platform Pipelines上に構築して開発・運用を行っています。 この度、Vertex AI Pipelinesへの移行を全面的に進めることになりましたので、ご紹介も兼ねて記事にしたいと思います。 背景 2024/07にAI Platform Pipelinesが非推奨になるという通知を受けたことがきっかけです。 AI Platform Pipelines deprecations  |  Google Cloud 非推奨の通知が移行を開始するトリガーではあったものの、かねてからGKE クラスタ の運用をどうにかできないかなと考えていました。 Vertex AI Pipelinesへの移行によりフルマネージド化できるのは大きなモチベーションとなっています。 移行における課題 この機会にKubeflow Pipelines SDK (以下、kfp) v1からv2へのアップグレードを進めています。 移行ドキュメント も提供されているため、それほど苦労はしないものと考えておりました。 が、結果としてこの選択が多くの苦労を抱えることになってしまいました 😢 実際に kfp v2 利用してみて、良かった点・苦労している点を交えてご紹介いたします。 ※下位互換で動作させることも可能でしたが、kfp v1での機能拡張は行われない、そのうち書き換えが必要になる、という点を考慮してkfp v2への書き換えに踏み切りました。 kfp v1からv2へ kfpは、 kubernetes 上で 機械学習 パイプラインを動作させるためのツールキットです。 コードベースは Python です。 An introduction to Kubeflow パイプラインを作成してVertex AI Pipelines上で動作させると、動作フローが視覚的に分かりやすく表現されます。 Python で記述したコードを コンパイル して利用する性質上、kfp v1の頃からかなりクセが強いなとは感じていました。 実際に利用してみた上での良い点、苦労している点を列挙し、所感を書いていこうと思います。 kfp v2の利用バージョン # pip list | grep kfp kfp 2.3.0 kfp-pipeline-spec 0.2.2 kfp-server-api 2.0.3 kfp v2の良い点 入出力に利用する Input[xxx] / Output[xxx] が便利 ParallelFor による並列処理の結果を Collected で受け取れるようになった @dsl.component や set_accelerator_type が直感的 入出力に利用する Input[xxx] / Output[xxx] が便利 kfp v2では入出力に利用するオブジェクトがこの形(基本的に Input[Artifact] / Output[Artifact] の利用)にほぼ統一されており、GCS上のパスを意識せず利用できるため非常に便利です。 https://www.kubeflow.org/docs/components/pipelines/v2/data-types/artifacts/ ParallelFor による並列処理の結果を Collected で受け取れるようになった kfp v1でも ParallelFor は利用できましたが、fan-in(複数の入力を一つにまとめること)が厄介でした。 kfp v2では最近になって Collected が利用可能となり、 ParallelFor の後に呼び出すことで結果をリスト形式でfan-inできるため、コードの可読性も飛躍的に向上します。 @dsl.component や set_accelerator_type が直感的 個人的な好みの部類かもしれませんが、kfp v2は定義周りが直感的になった印象があります。 https://www.kubeflow.org/docs/components/pipelines/v2/migration/#create_component_from_func-and-func_to_container_op-support kfp v2で苦労している点 変数展開がおかしくなることがある 型指定の厳密化により、何を渡せばよいのか分からなくなることがある 変数展開がおかしくなることがある kfp v2への移行で最も困っている点です。以下のようなissueも挙がっています。 https://github.com/kubeflow/pipelines/issues/10261 変数内に何らかの文字列や数値を入れているはずが、実際に利用する場合に以下のような展開がされてしまいます。 {{channel:task=;name=g;type=String;}} コンポーネント の出力結果をうまく展開できない場合は以下のような内容です。 {{channel:task=term-calc;name=list_date;type=typing.List[str];}} 機械学習 の初回実行プロセスの多くがBigQueryからのデータ取得を行っており、データ取得期間や特徴量を変数で管理しているため、既存のパイプラインコードでは Python のformatメソッドによる書式変換を多用しています。 この書式変換のほとんどが正常に動かなくなってしまい、試行錯誤を繰り返すことになってしまいました。 以下、期待した変数展開とならずにエラーとなるパターンの一部です。 パイプライン引数や コンポーネント の返却結果をdictに加えて コンポーネント に渡した場合 @ dsl.component def convert_str (tmpl: str , value: dict , output: Output[Artifact]): with open (output.path, "w" ) as f: f.write(tmpl.format(value)) @ dsl.pipeline (name= "test" , description= "test prediction" ) def test_pipeline (table_name: str = "sample" ): value = { "table_name" : table_name } sql = "SELECT * FROM {0[table_name]}" convert_str_op = convert_str(tmpl=sql, value=value) コンパイル 時のエラー内容 ValueError: Value must be one of the following types: str, int, float, bool, dict, and list. Got: "{{channel:task=;name=table_name;type=String;}}" of type "<class 'kfp.dsl.pipeline_channel.PipelineParameterChannel'>". パイプライン引数をパイプライン本体で利用しようとした場合 @ dsl.pipeline (name= "test" , description= "test prediction" ) def test_pipeline (periods: str = "1m" ): target_file = f "periods_{periods}.yaml" with open (target_file, mode= "r" ) as f: periods_conf = yaml.safe_load(f) コンパイル 時のエラー内容 FileNotFoundError: [Errno 2] No such file or directory: 'periods_{{channel:task=;name=periods;type=String;}}.yaml' 型指定の厳密化により、何を渡せばよいのか分からなくなることがある パイプライン引数を利用しようとして以下のようなエラーが出たり TypeError: PipelineParameterChannel is not a parameter type. 特定 コンポーネント にinputを渡した場合に以下のようなエラーが出たり ValueError: Constant argument inputs must be one of type ['String', 'Integer', 'Float', 'Boolean', 'List', 'Dict'] Got: <kfp.dsl.pipeline_task.PipelineTask object at 0x7f8a03f89880> of type <class 'kfp.dsl.pipeline_task.PipelineTask'>. といったことが割と発生します。 自分としては正しい型での引き渡し、および参照をしているつもりのため、どう対処してよいか分からなくなることが多いです。 事前にキャストすることで正常に動作することもあれば、そもそもデータ型の扱いを見直す必要があったりします。 所感 上述の苦労している点での引っ掛かりが多いのが難点で、残念ながら使いやすさは感じられていません。 ですが、一度形としてできてしまえばテンプレート化できると思われるため試行錯誤しながら進めている、というのが現状です。 コンテナイメージ化している コンポーネント 内部の挙動はほぼ変更なしで動作しており、ほとんどが「変数展開がおかしくなる」部分の障壁により思うように進捗していないといった状態です。 具体的にこうすれば良い、といったアプローチが見つけられたら何かしらの形で記事にできればと考えております。 引き続き、Vertex AI Pipelines移行による 機械学習 基盤のフルマネージド化を目指して邁進していく所存です 💪 おわりに 明日の記事の担当はインフラチームの福田さんです。EKS周りのお話です。お楽しみに!! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、エンジニアの川本です。 主に BUYMA の決済・配送を担当しているチームでバックエンドの開発をしています。 この記事は Enigmo Advent Calendar 2023 の 20 日目の記事です。 個人開発でPlanetScaleという MySQL 互換のサーバーレスデータベースを使用しているのですが、特筆すべき仕様として外部キーのサポートがありません。 planetscale.com 外部キー制約はDBレベルで強い整合性を担保できる便利な手段ですが、PlanetScaleではその機能が利用できないので、アプリケーションレベルで整合性を担保する必要があります。 MySQL の外部キーのオプションにはいくつか種類がありますが、これらが使えない場合にアプリケーション側ではどのように担保すればよいのでしょうか? 今回は、 Rails を例にしてアプリケーション側で MySQL の外部キーに相当する機能をどのように担保できるのかを検証してみようと思います。 余談:PlanetScaleについて 最近PlanetScaleはベータ版で外部キーをサポートし始めましたが、残念ながら Hobbyプラン ではまだサポートされておりません。 PlanetScaleの基盤である Vitess はOnline DDL の機能を提供しており、それが原因で外部キーのサポートが長らく難しかったようです。 以下のドキュメントやブログには、PlanetScaleが外部キーをサポートできるようになるまでの背景や課題、そしてその克服方法についての詳細な情報が記載されています。興味がある方はぜひ読んでみてください。 外部キーのサポートが難しかった理由 Operating without foreign key constraints Online DDL: why FOREIGN KEYs are not supported 外部キーをサポートするための取り組み Foreign key constraints beta The challenges of supporting foreign key constraints 親子関係のテーブルを作成 まず親子関係にある、Parent, Childテーブルを作成してサンプルデータを入れる。 -- テーブル作成 mysql> CREATE TABLE parent ( -> id INT NOT NULL , -> PRIMARY KEY (id) -> ) ENGINE=INNODB; mysql> CREATE TABLE child ( -> id INT NOT NULL , -> parent_id INT NOT NULL , -> PRIMARY KEY (id) -> ) ENGINE=INNODB; -- テストデータをインサート mysql> INSERT INTO parent (id) VALUES ( 1 ), ( 2 ); mysql> INSERT INTO child (id, parent_id) VALUES ( 1 , 1 ), ( 2 , 1 ), ( 3 , 2 ); -- データ構造の確認 mysql> SELECT * FROM parent p JOIN child c ON p.id = c.parent_id; + ----+----+-----------+ | id | id | parent_id | + ----+----+-----------+ | 1 | 1 | 1 | | 1 | 2 | 1 | | 2 | 3 | 2 | + ----+----+-----------+ MySQL の外部キー制約 MySQL では以下4つの ON DELETE 副次句で指定できる参照アクションがあります。 ON UPDATE 副次句もありますが、今回は ON DELETE に限定することにします。 dev.mysql.com ON DELETE CASCADE 親テーブルから行を削除し、子テーブル内の一致する行を自動的に削除する。 -- ON DELETE CASCADEを指定して外部キー制約を設定 mysql> ALTER TABLE child -> ADD CONSTRAINT fk_parent -> FOREIGN KEY (parent_id) -> REFERENCES parent(id) -> ON DELETE CASCADE; -- parentのid = 1のレコードを削除する mysql> DELETE FROM parent WHERE id = 1 ; -- parent_id = 1のchildのレコードも削除されていることを確認できる mysql> SELECT * FROM child; + ----+-----------+ | id | parent_id | + ----+-----------+ | 3 | 2 | + ----+-----------+ ON DELETE SET NULL 親テーブルから行を削除し、子テーブルの外部キーカラムを NULL にする。 ※ この設定をするときは、 child の parent_id は NOT NULL にしない。 -- ON DELETE SET NULLを指定して外部キー制約を設定 mysql> ALTER TABLE child -> ADD CONSTRAINT fk_parent -> FOREIGN KEY (parent_id) -> REFERENCES parent(id) -> ON DELETE SET NULL ; -- parentのid = 1のレコードを削除する mysql> DELETE FROM parent WHERE id = 1 ; -- parent_id = 1のchildのレコードのparent_idはNULLになっていることを確認 mysql> SELECT * FROM child; + ----+-----------+ | id | parent_id | + ----+-----------+ | 1 | NULL | | 2 | NULL | | 3 | 2 | + ----+-----------+ ON DELETE RESTRICT or ON DELETE NO ACTION or 指定なし 親テーブルに対する削除操作は拒否されます。また、 ON DELETE RESTRICT or ON DELETE NO ACTION or ON DELETE 指定なし は同じ挙動になります。以下の例では ON DELETE 指定なし で例を示します。 -- ON DELETE指定なしで外部キー制約を設定 mysql> ALTER TABLE child -> ADD CONSTRAINT fk_parent -> FOREIGN KEY (parent_id) -> REFERENCES parent(id) -- parentのid = 1のレコードを削除する -- childにはparent_id = 1のレコードがあるので削除拒否される mysql> DELETE FROM parent WHERE id = 1 ; ERROR 1451 ( 23000 ): Cannot delete or update a parent row : a foreign key constraint fails (`myapp_development`.`child`, CONSTRAINT `fk_parent` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`)) -- parentもchildも削除されていない mysql> SELECT * FROM parent p JOIN child c ON p.id = c.parent_id; + ----+----+-----------+ | id | id | parent_id | + ----+----+-----------+ | 1 | 1 | 1 | | 1 | 2 | 1 | | 2 | 3 | 2 | + ----+----+-----------+ Rails 側の実装方法 Rails では、Active Recordの dependentオプション を使用して、 MySQL の外部キー制約に相当する機能を実現できます。 dependentオプション は親レコードに対して ActiveRecord::Persistence#destroy が実行されたときに、紐ずいている子レコードに対して実行されるメソッドのことです。 ON DELETE CASCADE ON DELETE CASCADE に相当することは、 delete_all , destory , destory_async のいずれかで実現することができます。これら3つは全て最終的に実現できることは同じですが、それぞれで以下のように挙動の違いがあります。 delete_all delete_allは、parentに関連付けられたchildが一括で1つの SQL で削除します。 また、childに対して ActiveRecord::Persistence#delete が実行されるので、 ActiveRecord::Persistence#destroy 実行時に作用するbefore_destroyやafter_destroyといったコールバックや孫クラスのdependentオプションが実行されません。 そのため、単純に削除 SQL を実行するだけなので関連するchildが多い場合にはdestroyよりパフォーマンスが向上する可能性があリます。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :delete_all end irb(main): 002 > parent = Parent .find( 1 ) irb(main): 054 > parent.destroy TRANSACTION ( 0 .7ms) BEGIN Child Delete All ( 1 .2ms) DELETE FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 Parent Destroy ( 0 .7ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 1 .9ms) COMMIT => #<Parent:0x0000ffffaf032050 id: 1> destroy destroyは、parentに紐づくchildを全て取得して1件ずつ削除します。 ActiveRecord::Persistence#destroy が実行されるため、before_destroyやafter_destroyなどのコールバックも実行され、孫クラスにあるdependentオプションも実行されます。 そのため、関連するchildが多いと発行される SQL も増え、コールバックの実行や孫クラスのdependentオプションの実行が多くなり、delete_allよりもパフォーマンスが低下する可能性があります。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :destroy end irb(main): 002 > parent = Parent .find( 1 ) irb(main): 062 > parent.destroy TRANSACTION ( 0 .3ms) BEGIN Child Load ( 1 .0ms) SELECT ` child ` .* FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 Child Destroy ( 1 .3ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 1 Child Destroy ( 1 .1ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 2 Parent Destroy ( 0 .9ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 1 .2ms) COMMIT => #<Parent:0x0000ffffafdad888 id: 1> destroy_async destroy_asyncは、parentに関連する全てのchildを非同期で1件ずつ削除します。 紐づくchildが非常に多く、即時での削除を求められない場合に有効です。紐づくchildが多いと処理が最悪の場合は タイムアウト する可能性もあります。そのような場合、まずparentを削除してクライアントにレスポンスを速やかに返し、残りの紐づくchildは非同期で削除することで問題を解決できます。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :destroy_async end irb(main): 002 > parent = Parent .find( 1 ) irb(main): 070 > parent.destroy TRANSACTION ( 0 .3ms) BEGIN Child Load ( 0 .8ms) SELECT ` child ` .* FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 Parent Destroy ( 0 .8ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 2 .0ms) COMMIT Enqueued ActiveRecord ::DestroyAssociationAsyncJob ( Job ID : 63fc4528-934a-405c- 9311 -7bee9fb706b1) to Async(default) with arguments : { :owner_model_name => " Parent " , :owner_id => 1 , :association_class => " Child " , :association_ids =>[ 1 , 2 ], :association_primary_key_column => :id , :ensuring_owner_was_method => nil } => #<Parent:0x0000ffffae761700 id: 1> irb(main): 071 > Performing ActiveRecord ::DestroyAssociationAsyncJob ( Job ID : 63fc4528-934a-405c- 9311 -7bee9fb706b1) from Async(default) enqueued at 2023 - 12 -16T09: 16 :43Z with arguments : { :owner_model_name => " Parent " , :owner_id => 1 , :association_class => " Child " , :association_ids =>[ 1 , 2 ], :association_primary_key_column => :id , :ensuring_owner_was_method => nil } Parent Load ( 3 .0ms) SELECT ` parent ` .* FROM ` parent ` WHERE ` parent ` . ` id ` = 1 LIMIT 1 Child Load ( 5 .1ms) SELECT ` child ` .* FROM ` child ` WHERE ` child ` . ` id ` IN ( 1 , 2 ) ORDER BY ` child ` . ` id ` ASC LIMIT 1000 TRANSACTION ( 0 .3ms) BEGIN Child Destroy ( 0 .9ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 1 TRANSACTION ( 2 .0ms) COMMIT TRANSACTION ( 0 .3ms) BEGIN Child Destroy ( 1 .0ms) DELETE FROM ` child ` WHERE ` child ` . ` id ` = 2 TRANSACTION ( 1 .7ms) COMMIT Performed ActiveRecord ::DestroyAssociationAsyncJob ( Job ID : 63fc4528-934a-405c- 9311 -7bee9fb706b1) from Async(default) in 63 .81ms ON DELETE SET NULL nullify ON DELETE SET NULL に相当することは nullify で実現できます。 parentに紐づくchildのparent_idをnullに更新して、parentを削除しています。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :nullify end irb(main):088> parent = Parent .find( 1 ) irb(main):090> parent.destroy TRANSACTION ( 0 .3ms) BEGIN Child Update All ( 5 .0ms) UPDATE ` child ` SET ` child ` . ` parent_id ` = NULL WHERE ` child ` . ` parent_id ` = 1 Parent Destroy ( 3 .3ms) DELETE FROM ` parent ` WHERE ` parent ` . ` id ` = 1 TRANSACTION ( 1 .3ms) COMMIT => #<Parent:0x0000ffffae66f9a0 id: 1> ON DELETE RESTRICT or ON DELETE NO ACTION or 指定なし ON DELETE RESTRICT または ON DELETE NO ACTION に相当することは、 restrict_with_exception または restrict_with_error のいずれかで実現することができます。これら2つは全て最終的に実現できることは同じですが、それぞれで以下のように挙動の違いがあります。 restrict_with_exception parentに紐づくchildが存在することを確認して、処理を ロールバック して ActiveRecord::DeleteRestrictionError という例外を発生させます。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :restrict_with_exception end irb(main):088> parent = Parent .find( 1 ) irb(main):094> parent.destroy TRANSACTION ( 0 .6ms) BEGIN Child Exists ? ( 1 .0ms) SELECT 1 AS one FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 LIMIT 1 TRANSACTION ( 0 .5ms) ROLLBACK / usr / local/bundle/gems/activerecord- 7.0 . 8 /lib/active_record/associations/has_many_association.rb: 16:in ` handle_dependency': Cannot delete record because of dependent child (ActiveRecord::DeleteRestrictionError) restrict_with_error parentに紐づくchildが存在することを確認して、処理を ロールバック してfalseを返します。 class Parent < ApplicationRecord self .table_name = ' parent ' has_many :child , dependent : :restrict_with_error end irb(main):088> parent = Parent .find( 1 ) irb(main):098> parent.destroy TRANSACTION ( 0 .5ms) BEGIN Child Exists ? ( 0 .6ms) SELECT 1 AS one FROM ` child ` WHERE ` child ` . ` parent_id ` = 1 LIMIT 1 TRANSACTION ( 0 .4ms) ROLLBACK => false 最後に ここまでの紹介で、 Rails アプリケーションで MySQL の外部キー制約の参照アクションを実現する手段が理解できました。 ただし、データ整合性が担保されるのは、外部キー制約に準拠したアプリケーションからの実行時に限られます。もし、同じDBを参照するが外部キー制約に準拠していないアプリケーションが存在する場合、どのような影響が生じますでしょうか? 外部キー制約のないアプリケーションからの実行により、データ整合性が維持されなくなる可能性があります。このような事態を避けるためには、できるだけDBレベルで整合性を担保する方が望ましいです。 Planet Scaleのような外部キー制約をサポートしていないDBでは、今回紹介したようなアプリケーションの実装が有効であるかもしれません。しかし、外部キー制約がサポートされているDBでは、DBレベルでの制御が安全であると言えるでしょう。 明日の記事担当はデータエンジニアリングチームです!お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、サービスエンジニアリング本部の平井です。 こちらは Enigmo Advent Calendar 2023 の 20日 目の記事です。 私は、エンジニア部門で取り組んでいる開発生産性分析について紹介します。 開発生産性分析を試みた経緯 現在、 エニグモ では開発組織体制の変更、メンバー増強など様々な組織強化を目指した動きが加速してきています。ただ、そのような施策が開発組織のパフォーマンスを向上させているのか 定量 的な指標で測ることができませんでした。 また、開発組織としては、開発を通して一定のスピードでユーザーに十分な価値を届ける責任を負っているものの、開発スピードを測る良い方法がありませんでした。 このように組織パフォーマンス向上施策の結果を確認するため、開発組織としての開発スピードを図るために開発生産性分析の取り組みが始まりました。 また、この取り組みは開発生産性に興味がある有志のメンバーが集まり進めていきました。 指標選定 分析する指標としては、 Google が提案している Four Keys を参考にしました。 Four Keysを参考にした理由は以下になります。 開発スピードを測る指標が含まれている。 自分達で計測、分析の基盤を整えられそう。 一般的な指標である。 そして、 指標の中で速度に関わる指標であり、計測が容易そうな 変更のリードタイム と デプロイ頻度 を計測することが決まりました。 デプロイ頻度に関しては、営業日や開発メンバーの増減による影響を少なくするために、 @hiroki_daichiさんが紹介されていた d/d/d というやり方で 1日あたりの1開発者あたりのデプロイ回数を計算することにしました。 また、現在 BUYMA は基幹システムと複数のマイクロサービスで構成されていますが、どの開発チームも修正することが多い基幹システムにおけるこれらの指標を計測していくことになりました。 各指標の定義 前提として、 BUYMA の基幹システムはGitlabを ホスティング サービスとして利用しています。 本番環境へのデプロイは内製アプリケーションを通して行われ、開発者が各々デプロイ依頼を作成します。 本番環境へのデプロイプロセスは1日3回実行されるため、各開発者は作成した依頼をそのどれかのプロセスに乗せて本番化します。 Four Keysを参考にしつつ、このような BUYMA の特徴を考慮して各指標の定義をチームで相談して決めました。 その上でリードタイムは以下の定義で計算しています。 MR内の最初のコミットからそのMRが本番環境に反映されるまでの時間 各開発者によって開発手法は異なるため最初のコミットタイミングが微妙にずれる可能性はありますが、 開発 -> レビュー -> QA -> デプロイ という一連の 開発プロセス を計測できるためこのような定義にしました。 d/d/dに関しては、 デプロイ回数/営業日/開発者数 となっていて、それぞれ以下のような定義になっています。 デプロイ回数: 本番化された本番化依頼の数 BUYMA の本番化の特徴として、1デプロイに複数の修正が含まれるためその一つ一つの修正を個別に数えたかったためこのような定義にしました。 営業日: 土日祝日を抜いた平日 営業日は特に一般的に使われているものです。 開発者数: マージされたMRに参加したユニークGitLabユーザー数 エンジニアリング部門に属していても「基幹システムに関わっていない人は含めたくない」、「過去の特定期間の開発者数も出したい」など考慮する点が多く難しかったのですが、「MRに関わった人数は開発に関わる人数と等しいだろう」という考えのもとチームで相談してこのような定義になりました。 データ収集、可視化の仕組み 次に、指標の計測と可視化の仕組みについて説明します。 Airflowにワークフローを構築して、MRに関わる情報をGitLabの API から収集しBigQueryに格納しています。 デプロイに関する情報は内製本番化アプリケーションが利用しているデータベースに永続化されているため、BigQueryに連携してGitLabの情報とJOINできるようにしました。 可視化に関しては、BigQuery上のレコードを SQL で加工してLookerの ダッシュ ボードを使うことで実現しています。 開発性生産性分析システム構成図 リードタイムに必要なデータ収集 リードタイムを計測するために以下のGitLab API を利用しています。 基本的には API のレスポンスから日次の差分データを取得して、そのままBigQueryのテーブルに保存しています。 List all projects API GitLabのプロジェクトのマスターデータを収集するために利用しています。 収集したデータはLookerで可視化する際にプロジェクト名を表示するなどに利用しています。 List group merge requests API MRデータを収集するために利用しています。 マージ済みMRの情報のみ必要なので API パラメータを使ってマージ済みMRの情報のみ収集しています。 Get single merge request commits API MRに紐づくコミットの情報を収集するために利用しています。 ここで取得したデータを利用してMRに紐づく最初のコミットを特定して、リードタイムを計算します。 d/d/d に必要なデータ収集 リードタイムを計測するために以下のGitLab API を利用しています。 こちらも日次の差分データをそのままBigQueryのテーブルに保存しています。 List a Project’s visible events API GitLabのイベント情報を収集するために利用しています。 マージされたMRに参加したユニークGitLabユーザー数を計算する際にここで収集したデータを利用しています。 可視化について 先述したように可視化にはLookerの ダッシュ ボードを利用しています。 LookML で SQL を組み立てて指標を計算しています。 エニグモ では BUYMA をメインの機能で分割して開発チームを組織していて、それらを ドメインチーム と呼んでいます。指標の監視や分析は各 ドメイン チームにやってもらっているため各 ドメイン チーム毎に ダッシュ ボードを作成しています。 可視化したあるチームのリードタイム 運用について 指標の監視や分析は、細かいやり方を指定せずに ドメイン チームに依頼しています。 例えば私が所属しているチームでは毎週の振り返り時にリードタイムとd/d/dを確認して、何か問題があれば改善案を考えるという運用をしています。 また、開発生産性指標の計測にあたり、 開発者にMRへの ラベル 付与を依頼しました。MRに付与されたラベル情報をもとに ドメイン チーム毎のリードタイムを計測しています。 今後の課題 今後の課題としては以下になります。 BUYMA 基幹システム以外でまだデータ収集できていないマイクロサービスがあるため正確に開発組織全体の生産性を測れていない。 要件定義、仕様決定などの ディスカバリー フェーズにかかっている時間を図れていない。 変更障害率、サービス復元時間など安定性の指標を図れてない。 現状だとリードタイムやd/d/dが向上した際に、それが障害発生による細かい修正が増えたのが原因なのかわからない。 終わりに 今回はエンジニア部門で取り組んでいる開発生産性分析について紹介しました。自分自身、データ収集処理の開発、可視化を担当し、自分達の開発活動が 定量 データとして表現される面白さを感じました。 最後までご覧頂きありがとうございました。明日の記事の担当は検索エンジニアの竹田さんです。お楽しみに。 現在、 エニグモ ではこのような開発生産性に関わる取り組みを行っています。 興味のある方は以下の求人をご参照ください!! 株式会社 エニグモ すべての求人一覧 hrmos.co
.blog-figure{ width: 70%; margin-left: auto; margin-right: auto; display: block; } .blog-footer{ background: rgb(193,54,47); } .footer-icon{ width: 2rem; display: inline-block; } .figure-caption{ text-align: center; color: goldenrod; font-size: 0.8rem; } .figure-source{ opacity: 0.7; font-size: 0.6rem; color: gray; } .greetings { font-size: 0.8rem; padding: 2rem 1rem; text-align: center; display: block; margin-top: 2rem; background: rgb(250,248,236) -webkit-gradient(linear, 100% 0, 0 0, from(#acacac), color-stop(0.5, #ffffff), to(#acacac)); background-position: -4rem top; /*50px*/ background-repeat: no-repeat; -webkit-background-clip: text; -webkit-text-fill-color: transparent; -webkit-animation-name: greetings; -webkit-animation-duration: 2.2s; -webkit-animation-iteration-count: infinite; -webkit-background-size: 4rem 100%; /*50px*/ } @-webkit-keyframes greetings { 0% { background-position: -4rem top; } 70% { background-position: 30rem top; } 100% { background-position: 12.5rem top; } } .blog-emphasis{ font-weight: 600; color: goldenrod; } .blog-parts{ margin: 1.2em 0 1.5em 0; color: #3c3c3c; text-align: justify; } .blog-notes{ font-size: 0.8rem; } .entry-categories > a{ color: #888888; } こんにちは、グローバルチームのエンジニアのFernandです。 この記事は Enigmo Advent Calendar 2023 の19日目の記事です。私は、15年近く フルスタ ックエンジニアとして働いてきました。さまざまなプロジェクトに関わってきましたが、、新しいチームに参加するたびに、技術的なスキルだけでなく、特にソフトスキルについても新しく学ぶことが多くあります。今回は、職場のミーティングで外国人として直面した課題を共有したいと思います。 (jump to English version) ある日、ITエンジニアの妻が彼に頼んで、「スーパーで牛乳を1パック買ってきて。もし卵があれば1ダース買ってきて」と言いました。そこでエンジニアは買い物に行き、言われた通りに行動し、帰宅しました。手には牛乳パックが12個ありました。 妻は驚愕しました 。何が間違いだったのでしょうか? プロジェクト・マネジメント協会(PMI)の報告によると、ビジネス目標を達成しないプロジェクトの半数以上は、効果的なコミュニケーションの欠如によるものとされています。実際に、不十分なコミュニケーションにより、1500億円を投じたプロジェクトの場合には 約100億円がリスクとなる のです。ビジネスでは、12個の牛乳パックが大きな損害を生み出してしまうかもしれないのです。 コミュニケーションの齟齬による経済的リスク ソース: ©2013 Project Management Institute, Inc. Pulse of the Profession In-Depth Report: The High Cost of Low Performance: The Essential Role of Communications, May 2013. PMI.org/Pulse 私が日本の企業で働き始めた当初、さまざまなチームとのコミュニケーションに苦労しました。私と同じように、日本に住む外国人の多くが直面する経験です。 外国人が日本で働くうえでぶつかる壁(言語の壁が半数以上) ソース: NikkeiAsia 私が最初に入社したスタートアップ企業では、お客様の満足度が最も重要であり、彼らを 神様のように対応すべき ということを教わりました。そのため、ミーティングに参加する際、私は静かに座り、より理解するために一層耳を傾ける必要がありました。私は日本語に自信がなかったため、質問をすることや間違いを犯すことを恐れていました。自分が話すことで、クライアントから私たちの会社がネガティブに評価されるのではないかという不安がありました。 しかしそれでも、各ミーティングの後に反省点を見直して、気づいたことを改善してから自信を持って質問をするようにしました。そして、 効果的なコミュニケーションを大事 にする日本のチームに参加する機会を得たおかげで、私はミーティング中に質問をする勇気を持ち、プロジェクトやクライアントの要件をより理解できるようになりました。日本人の同僚は忍耐と理解力をもって複雑な概念を簡素化するために時間をかけて説明を繰り返してくれました。 しかし、言語の壁は、誤解を招く障害となることもあることも私は理解しています。それにより、会話の流れが乱れ、自分の意見を正確に表現することが難しくなります。思いを正確に表現することへの取り組みは、話す人と聞く人の双方にとっても苦悩の源となっています。できるだけこれを避けるために、事前に読み込んで準備し、質問をまとめて整理することが大事です。 外国人のメンバーがミーティングをする場合、一人だけに発言の責任が押し付けられないように、協力し合いながら取り組みます。通常、そこでは、生産的に結果を出すために、 話の途中で割り込むことが歓迎され、許容されます 。曖昧さを解消するために、ア イデア を効果的に共有して質問をするこの戦略は時に混沌としたものになりますが、コミュニケーションの理解を促進し、より協力的で生産的な働き方を促進するのに非常に効果的だと感じています。誤解や心配事が生じた際に即座にそれらを明確化することは、コミュニケーションの質を向上させ、より協力的かつ生産的な職場環境を生み出します。参加者全員が議論に貢献することを促すことで、創造性が高まり、ア イデア が高められ、より豊かでダイナミックな成果が生まれます。 しかし、文化的な習慣やエチケットにも配慮することが重要です、特に話し手が日本人の場合です。このような場合、 割り込むことは失礼とされ 、質問はミーティングの最後まで取っておくことが通例です。そういったミーティングでは、私はより受動的なアプローチを取る傾向があります。ただし、質問や懸念事項を早急に解決させたい場合、日本語の能力に自信がなくても話すように努めています。日本語の能力に自信がなくても、ただ聞くだけに頼るのではなく、積極的な参加こそが助けを求めて理解を深める唯一の方法であることを心に留めています。言語の壁を乗り越え、異文化間のコミュニケーションの複雑さをより効果的に乗り越えるためには、 日本語の勉強を続けることが重要 です。 私は現在、さまざまな国のメンバーから成るチームに所属しています。私たちの主要なコミュニケーション手段は日本語ですが、必要に応じて英語も使用しています。さまざまな時差の課題にも取り組んでおり、私たちのマネージャーはその違いがミーティングのスケジュールに影響しないよう柔軟に対応してくださいます。彼女は流暢な英語と日本語を活かし、私たちの質問や懸念事項をサポートし、チーム内の円滑かつ効果的なコミュニケーションを容易にしてくれます。私は定期的に他のチームとも交流しなければなりません。日本語を使ってのコミュニケーションは難しいですが、貴重な機会であり、コミュニケーションスキルを実践し向上させるチャンスでもあります。しかし、通常業務に追われて、学習に費やす時間の確保がなかなか難しくなっています。プライベートと仕事、そして 言語学 習のバランスを取ることは大変な努力を要します。まるでジャグリングをするようなもので、集中して言語の練習だけに時間を割くことはなかなか難しいものです。 言語の不完全さや課題があっても、ミーティングでの同僚たちはそれを判断することはありません。むしろ、言語の壁にとらわれず、積極的に参加して貢献する姿勢は評価されるでしょう。しかし、質問をする際には、ただ質問を投げる、自己アピールのためにするということは避けるべきです。そのような行動は、ビジネス上での評判に悪影響を及ぼすだけでなく、私たちが維持しようとしている協力的な雰囲気にも支障をきたす可能性があります。 さらに、会議を進行する際は、コミュニケーション能力や自信のレベルに関係なく、全参加者が積極的に参加できる包括的で支援的な雰囲気を育むことも重要です。間違った質問や関連性のない質問をする人がいても批判するべきではないでしょう。そのような場合に他者を批判すると、プロジェクトが始まる前から失敗したも同然です。前述したように、効果的なコミュニケーションが生産性を向上させるという認識はとても重要です。研究によれば、効果的なコミュニケーションは生産性を最大25%向上させることができます。最終的には、プロジェクトの成功は効果的かつ効率的なコミュニケーション能力にかかっています。卵と牛乳の個数を間違えることもないことでしょう。不確実な点が生じた場合には、積極的に話して明確化することが重要です。積極的なリスナーであり、共感を持って接することを心がけましょう。英語や日本語の流暢さだけに焦点を合わせるのではなく、彼らの質問やア イデア の価値がプロジェクトを前進させ成功させる上でとても重要です。 (English version) Communication in Workplace An IT engineer was asked by his wife to go to the store and buy a carton of milk, and if there are eggs, buy a dozen. So the engineer goes shopping, does as she says, and returns home with 12 milk cartons. Oh boy , But what went wrong? According to the Project Management Institute (PMI) report, over half of projects that fail to meet business goals can be attributed to ineffective communication. In fact, poor communication puts approximately $75 million at risk for every $1 billion spent on projects. Well, it seems that one dozen cartons of milk can potentially result in a substantial financial loss. The amount at risk for every US$1 billion spent on a project. Source: ©2013 Project Management Institute, Inc. Pulse of the Profession In-Depth Report: The High Cost of Low Performance: The Essential Role of Communications, May 2013. PMI.org/Pulse When I first started my journey working at Japanese companies, I often struggled to communicate effectively with various teams. Unfortunately, this burden is not unique to me but a shared experience among many foreign residents in Japan. Barriers foreigners encounter when working in Japan. Language barrier is the most prominent obstacle. Source: NikkeiAsia Working in startup companies, I quickly learned that client satisfaction was held in the highest regard, often considering them as king or god (o-kyaku-sama wa kamisama desu) whose every request must be fulfilled. Therefore, when I joined meetings, I had to sit quietly and listen harder to understand the discussions. I feared asking questions and making mistakes because of my Japanese language proficiency. My company might be judged by the client whenever I attempt to speak up. I was able to manage this by researching sample implementations after each meeting and creating prototypes before confidently raising a question. Then, I had the opportunity to join a Japanese team that fostered effective communication . Encouraged by my supportive environment, I mustered the courage to ask questions during meetings, improving my comprehension of projects and clients' requirements. My Japanese colleagues displayed immense patience and understanding, taking the time to reiterate their explanations and simplify complex concepts, ensuring everyone understood the information. But don't get me wrong, despite the positive environment, the language barrier often presents obstacles that could lead to misunderstandings and misinterpretations. It disrupts the conversation flow and makes it challenging to express oneself fully. The struggle to articulate thoughts accurately has become a source of frustration for both the speaker and the listener. In order to avoid this as much as possible, I read and prepared beforehand, collating and organizing my questions. In situations where the speaker is a non-Japanese teammate, I collaborated, ensuring that the onus of speaking does not solely rest on one person. Usually, in meetings that consist of non-Japanese participants, interruptions are welcomed and accepted to foster a productive environment where ideas can be effectively shared, questions can be asked, and clarification can be provided. While this strategy may seem chaotic at times, I find that it is incredibly effective in facilitating comprehension and generating deeper insights. Obtaining immediate clarity on any misunderstandings or concerns enhances the quality of communication and promotes a more collaborative and productive work environment. By establishing a culture where all participants are encouraged to contribute to the discussion, creativity surges, and ideas are elevated, creating a richer and more dynamic outcome. However, I am also mindful of cultural norms and etiquette, particularly when the speaker is Japanese. In these situations, it would be considered rude to interrupt , and it is customary to reserve questions until the end of the meeting. I tend to adopt a more passive approach during such meetings. Nevertheless, if I do have pressing clarifications or concerns, I try to speak up regardless of my lack of confidence in my Japanese proficiency. I remind myself that the only way to seek assistance and gain a better understanding is by actively participating in the conversation rather than solely relying on attentive listening. However, continuing to study Japanese remains paramount in overcoming these language barriers and navigating the intricacies of cross-cultural communication more effectively. Currently, I am fortunate to be a part of a diverse team with members from different parts of the world. Although our primary mode of communication is Japanese, we also embrace the use of English when needed. Despite the challenges of various time zones, our manager has done an exceptional job ensuring that these differences do not impede our meeting schedules. With her fluency in English and Japanese, she adeptly accommodates our questions and concerns, facilitating smooth and effective communication within the team. I also have to interact with other teams periodically. Communicating using the Japanese language is challenging, but it also gives me valuable exposure and the chance to practice and enhance my communication skills. Though I have to admit that the demanding nature of daily life and work commitments has restricted the amount of time available for study. Balancing work, personal responsibilities and language learning can be a juggling act, making it quite challenging to allocate uninterrupted time for focused language practice. Ultimately, the success of our projects hinges on our ability to communicate efficiently and effectively. If uncertainty arises, it is crucial to speak up and seek clarification. Rest assured that in meetings, colleagues will not judge any language imperfections or challenges you may encounter while expressing yourself in Japanese or English. Instead, your willingness to participate and contribute despite any linguistic limitations will be appreciated and acknowledged. However, it is also essential to avoid asking questions solely for the sake of asking or to showcase your skills. Engaging in such behavior may have a detrimental impact on your professional reputation and hinder the collaborative atmosphere we strive to maintain. In addition, it is equally important for the meeting host to take accountability in fostering an inclusive and supportive meeting environment that encourages active participation from all attendees, regardless of their communication skills or confidence levels. It is critical to refrain from criticizing individuals who may ask questions that might seem incorrect or irrelevant. Criticizing others in such instances, the project had already failed even before it started. Be an active listener and practice empathy. Fluency in English or Japanese should not be the sole focus; instead, the value of their questions and ideas truly matters in moving the project forward and achieving success. May the spirit of the holidays fill your home with love and peace. 明日の記事の担当はSellチームの平井さんです。お楽しみに。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、 iOS エンジニアの池田です。 この記事は Enigmo Advent Calendar 2023 の 18 日目の記事です。 この記事では担当のプロジェクトにおいて日々の開発を快適にするために実施している細々したことについて紹介します。 私は BUYMA の iOS アプリ開発 を担当しています。 iOS アプリ開発 に限らずプロジェクトを進める中では、コアタスク(ここでは iOS アプリの開発作業と定義)とノンコアタスク(コアタスク以外の作業)が発生します。 開発の効率を上げるため、開発者の開発の快適性を上げるためにノンコアタスクは極力削っていきたいものです。 ここではノンコアタスクの中でもチケット管理、Pull Requestの管理に関わる部分について触れていければと思います。 現在のプロジェクト環境 現在の担当プロジェクト内では、チケット管理にJIRA、開発用のプラットフォームには GitHub を利用しているので、それらに関わる内容について触れていきます。 また、コミュニケーションツールとしてSlackを利用しているため、こちらとの連携も少し触れます。 JIRA 自動化 JIRAのチケット管理では、各チケットの開始日や終了日、担当者、チケットの作業状況など様々な情報の日々の更新が必要になってきます。 一つ一つの作業は作業時間も短く単純作業ではあるのですが、数が多くなると作業時間ももちろん累積されますし、何にも考えずにできるわけではないため、じわじわ地味に体力を削られます。 そういった作業を減らし、少しでも快適性を上げるためにはJIRAの「自動化」が有効です。 以下は私が担当するプロジェクトで設定している「自動化」のルール一覧です。 様々設定されていますが、この中でも「プルリク エス トがマージされる → 課題を完了にする」が結構気に入っています。 Pull Request のコードレビューを受けた後自分でマージする運用をしている場合、このルールが設定されていないと、以下のような手順が必要になります。 ブラウザで GitHub を開く GitHub の画面上でPull Requestの一覧を開く 該当のPull Requestを開く Pull Requestをマージする ブラウザでJIRAを開く JIRAのボード等でチケットを完了に トランジション させる これらの手順の中で、4.から 5.では別サービスへの切り替えが必要で、6.の作業では ドラッグ&ドロップ かオプションメニューからの選択が必要だったりで、キーボードからマウスへの操作の切り替えも余分に必要になります。 4.で作業に満足して 5.以降忘れることもあり、JIRAのカンバン管理者から完了/未完了の問い合わせが来たとしたらコミュニケーションコストもかかります。 これだけでも意外と積み重ねるとコストになるので、設定することによる快適度は上がっている感じがします。 Gitブランチ作成 もう一つJIRAの機能で細かいけどよく使っている機能がチケットのブランチ作成機能です。 以下チケットの画面キャプチャです。 チケットの「開発 > ブランチを作成 > GITで新しいブランチを作成してチェックアウト」の欄で、チケットIDが入ったブランチ名を自動生成してくれます。 また、入力欄の右側のボタンで クリップボード にコピーできるため、 CLI でブランチ作成の際にコピー&ペーストでブランチを作成することができます。 こちらを利用することでブランチ名を考える手間や、 CLI で入力する手間を省くことができます。 名前を考えるのって決めの問題なのですが、意外と考えるコストがかかる作業だと思っているので個人的にはすごく助かっています。 GitHub PULL_REQUEST_TEMPLATE.md Pull Request を作成して開発されている場合、設定している リポジトリ が多いかと思います。 本プロジェクトでは、以下のようなトピックをフォーマット化して記載するようになっており、記載時の手間を少し減らしています。 関連チケット Pull Request でやったこと 懸念点・注意点 相談事項 また、JIRAと GitHub が連携されるようになっているため、チケットのURLを記載するとJIRAのチケットに GitHub のPull Requestが連動するようになっています。 Slack SlackとJIRA、Slackと GitHub も連携されており、チケットやPull Requestの条件に応じた更新があったタイミングで通知が来るようになっています。 JIRAの通知 JIRAの通知設定は以下のようになっており、コメントやステータスの更新等通知が来るように設定されています。 GitHub の通知 GitHub の通知設定は以下のようになっており、コメントの見落としが少なくなるように comments:'channel' の設定をしているところが個別に設定した部分だったかと思います。 まとめ チケット管理、Pull Requestの管理に関わる部分についてノンコアタスクを削減するため細々やっていることをご紹介しました。 プロジェクトの環境によって合う、合わない部分があったりしますが、見て頂いた方の何かのご参考になれば幸いです。 日々の変化している状況や、気づいたちょっとした不要な手間など、アンテナを高くしつつさらなる改善、日々の開発の快適度を上げていければと思います。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、コーポレートエンジニア(コーポレートIT[CO-IT]チーム) の 横川 です。 この記事は Enigmo Advent Calendar 2023 の 17 日目の記事です。 この記事では社内ITサービスを支えるチームの組織作りをテーマにどのような観点でチーム運営を行っているかをご紹介したいと思います。 コーポレートIT領域は、年々取り扱うサービスや技術が広くなり、様々なスキルセットが要求されてきていますが、本記事は特定技術の話ではなく組織運営にフォーカスした内容となります。 これから社内SEを目指される方や現在コーポレートエンジニアとして活躍されている方々に少しでも参考にしていただける内容となっていれば幸いです。 はじめに 自己紹介とCO-ITチームの紹介 コーポレートエンジニア・社内SE・情シスについて 1.組織運営の前提を整理する ミッションを理解する 会社の方針と現在のフェーズをすり合わせる プロダクト、スコープ、役割を整理する メンバー構成(チームの総リソース)を整理する 裁量と責任、働き方 2.現状を俯瞰する 課題を整理する サービスを整理する コストを整理する 手続き・運用を整理する 会議コストを考え、会議体を整理する 情報共有・ナレッジ化を意識する タスクを整理する、バックログ化する CO-ITチームの課題 3.将来を想像する 見える化、定量化に向けた(KPI・KGI策定を見据えた)足固め 業務改善の話(SaaSのインテグレーションだけが全てじゃない!) サービス導入・施策を開始することについて考える チームの安定した運営を考える 終わりに はじめに 自己紹介とCO-ITチームの紹介 私はITベンダーでネットワークエンジニアとしてキャリアをスタートし、その後セキュリティ製品の代理店にてプリセールスエンジニアを担当してきました。その後、キャリ アチェ ンジし、 デベロッパ ーでの社内SE(情シス)、 SaaS 提供企業でのコーポレートIT担当を経て、昨年10月よりenigmoにjoinしました。 コーポレートITという大きな領域全体を俯瞰して自社の最適なコーポレートITを自由に作ることができるという環境に惹かれて入社を決断しており、実際にそのミッションに日々向き合える環境で働いています。 CO-ITチームはコーポレートオペレーション本部の人事総務グループに属し、社内ITサービス全般を担当しています。 ブログ執筆時点(2023年12月)では、マネージャ[人事総務グループ部長]を除くとチームメンバーは私を含め3名で現在4人目のエンジニアを募集している状態です。 最終的な意思決定はマネージャが担いますが、当社のコーポレートIT領域の対応は現在私がリードする役割を担っています。 コーポレートエンジニア・社内SE・情シスについて 近年、コーポレートエンジニアやコーポレートITといったワードを見かけることも多くなってきており、従来の社内SEや情シスという表現との明確な違いを説明するWebページ等も増えてきているかと思います。 ざっくり両者の違いは、「見る範囲・視点の違い」とされていることが多く、コーポレートエンジニアは従来の社内SEや情シスのスコープよりも広範囲に及ぶ(企業全体を視野に入れ、ITスキルを活用して提案・企画を行う)と定義されています。 *1 コーポレートエンジニアはバックオフィス部門に所属し、会社の売上に直接貢献することはできません(プロダクト部門メンバーの業務効率向上に資することで間接的に貢献する)。企業によっては、プロダクト側に強い優遇(力が強い傾向)があったり、バックオフィス側は利益を生まない部門として不遇な環境になっている場合も見聞きしますが、プロダクト側・バックオフィス側どちらかに優劣があってはならないし、どちらも(会社の成長に貢献する部門として)リスペクトし合える企業風土が醸成できていることが望ましいと感じます。 *2 そして、シンプルにコーポレートIT部門が会社から求められることを表現すれば、 自社に最適なコーポレートITをいかに低コスト・最速で実現し、安定稼働を継続できるか になります。 予算が無限であれば、何も考えず最新で優れたサービスをフルライセンスで導入し、外部の専門ベンダーに要件定義〜運用まで全て外注して、トラブル対応や今後のアップデートも含めて24h365dでフルサポートされるオプションを付帯すれば他社を圧倒するコーポレートITの運用が可能となります。 *3 しかしながら、自社の社内ITサービスに実プロダクト以上の予算を割ける企業がどれだけあるでしょうか?この点にコーポレートエンジニアの存在意義があるものと考えられます。コーポレートエンジニアは、しっかり自社の状況を俯瞰して会社のフェーズに合わせて、必要十分なサービス提供に日々向き合うことが求められる職種です。 1.組織運営の前提を整理する 私自身が実際に実施していることを含め、組織運営にあたりクリアすべきと考えていることについて記載していきます。 ミッションを理解する 以下にMVVを掲載します。当社のミッションは  こちら  の通りですが、CompanyのMVVに応じてCO-ITチームでもMVVを定義しています。 MVV(ミッション・ビジョン・バリュー) このMVVは、私が入社前から定義されていたものですが、現時点で変更の必要性はないと判断し、そのまま継続利用しています。 近年、特にスタートアップ企業では、MVVを掲げている企業が多くなっている印象を受けますが、会社側がただ掲げているというよりは、従業員がそのMVVを自分事として意識するようにMVVに向き合う時間を業務時間の中に組み込んでいる企業も増えてきているのではないでしょうか。MVVというのは、非常に重要であり、各自の担当タスクは全てMVVに紐づいたものと考えることができます。 会社の方針と現在のフェーズをすり合わせる ワーディングは違っても、多くの企業のコーポレートIT部門のMVVは当社のものと重なる部分が多いのではないでしょうか。 それは企業活動における基本的な役割が同じであることに起因しますが、MVVが同じでも各企業・担当者の業務内容は例えば下記のような理由によって大きく異なります。 会社の規模や方針によって対応範囲やチーム構成が様々である 企業規模の拡大や事業成長に伴って縦割りが進み、サービスや製品毎にチームや担当者が限定されている 要件定義だけ・設計構築だけ・運用だけのようにスコープが限定されている したがって、具体的なチームビルディングに移る前にまず現在のフェーズを整理し、組織運営方針についての合意形成が必要になります。その大前提として、社内ITサービスを外注するのか内製するのかの判断を行います。一昔前までは専門のエンジニアが担当せず総務担当者が兼務や片手間で社内システム(情シス)を担当することも多かったように感じますが、昨今では求められるスキルや要件の複雑化によってそういった体制では対応しきれなくなってきているのではないでしょうか。事業規模の比較的小さな企業やスタートアップ企業などプロダクト側にフルコミットする必要がある場合は、社内ITサービスの外注化を行う場合もあるかと思いますが、以下のようなリスクがあるため慎重な判断が求められます。 顧客伴走型と謳っているサービスであっても顧客視点の小回りがきくサービスにはなりにくく、他企業共通のテンプレ利用による対応となるリスク システム化や仕組み化のベースとなるプラットフォームを顧客側ではなく発注先ベンダー側環境で構築され、顧客側では関知できないリスク 設計や設定、ナレッジ自体が顧客側に蓄積されず基本的に ブラックボックス 化してしまうリスク 各種リスクを解決するような契約を試みた場合、自社で専門のエンジニアを採用するよりコストが高くなるリスク 発注先都合で突然サービス終了となり、自社の社内ITサービスが停止してしまうリスク また、社内ITサービスというのは、自社独自の要件を年々積み重ねて構築していく要素も大きいため、例えばIDaaS製品の設計だけ、社内NWの構築だけを切り出すのではなく、社内ITサービス全体のバランスや連携も視野に入れた高い視座で最適なサービス運営を行う必要があります。加えて、各種サービスの発注先ベンダー等との価格交渉や要件すり合わせについても一定の知識が必要になりますので、その意味でも自社で専門のエンジニアを採用して運営していくことは重要であると言えます。 CO-ITチームは、私が入社する直前まで暫定的にマネージャ自身がプレイヤーを兼務し、多くの実務は(ヘルプデスク業務を外注できるような)外部サービスを利用することで社内ITサービスを維持管理している状態でしたが、私は業務状況を確認した上でチーム運営を再設計するフェーズ(初期フェーズ)と判断しました。なお、マネージャとは定期的に「As is」/「To be」について大枠の目線合わせを行っています。 *4 プロダクト、スコープ、役割を整理する 大枠の方針がフィックスした後は、少しずつ具体化していきます。まず、プロダクトとスコープの定義です。 一般的には、事業活動において顧客に提供するものをプロダクト(当社の場合、 BUYMA というサービス)と呼び、コーポレートITがプロダクト?と違和感を感じられるかもしれませんが、顧客(従業員)に対してサービスを提供する部門である以上当然プロダクトがあって良いと思いますし、意識すべきと考えています。そして、そのプロダクトを提供するためのスコープの定義も併せて必要です。 プロダクト/Job Titleとスコープの マッピング 上記がCO-ITチームのプロダクトと大枠のスコープになります。スコープは大項目のみ表記していますが、例えば今回のような「techブログへの投稿」業務は「 ブランディング 貢献」の中に含まれるという感じです。 また、スコープは視座や技術的な難易度等々によってピラミッド型になるので、メンバーの役割の大雑把なプロットも行っています。 *5 メンバー構成(チームの総リソース)を整理する ここまででチームが目指す方向性が見えてきたので、次に具体的なメンバー構成や必要リソースの整理に移ります。 なお、この項目については、各企業の人員計画・予算・業績・既存のメンバー構成等々複雑な要素を踏まえた 経営判断 になるかと思います。 結果だけの記載となりますが、CO-ITチームは、4名体制を目指すべく現在4人目のエンジニアを募集している状態です。 まとめますと、CO-ITチームは以下のように年間の投下リソースで表現される Value (アウトプット)が会社から求められる期待値をアウトパフォームすることを意識しなければなりません。 チーム(総リソース[年間約7,680h *6 ])の Value  > 会社からCO-ITチームへの期待値 裁量と責任、働き方 採用業務を行っているとよく質問を受ける項目ですので、ここで触れたいと思います。 CO-ITチームは、各担当者毎にスコープを限定するようなことはなく、基本的に各自のミッションサイズ(会社が各自に期待すること)に応じてコーポレートIT領域全てに携わる方針としております。 *7 ミッションサイズの中で最大限の裁量と責任を持っていただき、自由に業務時間を使うことで最大の Value を発揮いただくのが最良という考えに基づいています。 また、当社はオフィスワーク・リモートワークの選択も一定の裁量が許容されていますので、CO-ITチームでもその範囲内で自由選択としています。 基本的にオフィスワークが必要な業務(デ バイス のキッティング等)を中心に行うメンバーは出社頻度が高くなりますが、リモートワークすることでオフィスワークする以上のアウトプットが出せるのであればリモートワークの頻度が高くても良いという方針です。なお、業務スコープの特性上(ITサービスデスクを担当しているため)、必ず一人以上のチームメンバーがオフィスワーク(本社に出社)することで物理的なインシデントやユーザサポート対応を可能にしています。 2.現状を俯瞰する チーム運営の前提の整理が終わった後は、現状の把握に移ります。 課題を整理する サービスを整理する プロダクトを理解した上で顧客に提供するサービスの整理が必要です。近年のコーポレートIT領域はオンプレから クラウド 化の流れが急速に進んでいるため、 SaaS のことばかりに目が行きがちですが、従来の社内NW運用業務をはじめ、企業によってはオフィスファシリティやセキュリティ領域も含まれます。 また、 SaaS によっては他のバックオフィス部門( 経理 等)やプロダクト側が慣例的に運用しているサービスもあり、各サービス毎で主管している部門は企業によって差異があり、正確な把握が必要です。 コストを整理する 主管するサービスが洗い出せた後は、具体的なコスト感の把握です。 自チームが提供するサービスのコスト感がパッと答えられないというのは問題外であり、そのコスト感に見合うメリットを会社にもたらしているのかを常に意識する必要があります。 また、 SaaS であったとしても、単純にライセンス費用や月額利用料といった費用だけでなく、導入から日々の維持管理に必要な人的リソースのコスト(1人日の単価を具体的に設定した上で年間何人日、何人月の 工数 が掛かりどのような金額になるのかまで想定)を含めて該当サービスのコスト妥当性が判断される必要があります。 *8 CO-ITチームでは、下記のような主管サービスを網羅した一覧を作成・運用しており、提供サービスの総合計を従業員数で割ったサー ビスコ ストの算出を行っています。 この一覧には、サービスのコストをはじめ、サポート窓口の情報や稟議・請求書対応、更新のタイミング等々を含め、サービス別に串刺しして比較でき、これだけを確認すれば運用に必要な情報が取得できるように一元管理しています。 提供サービス一覧 なお、現時点では 見える化 したところまでしか到達しておらず、コストの最適値はどのくらいなのか、今のコストは妥当なのかという分析や評価を実施するフェーズには至っておりません。 近い将来、このコストを一つのパラメータとして適切なサービス導入の ガイドライン を策定することを見据えております。 手続き・運用を整理する 次に見積・稟議・発注・請求のような一連のサービス導入手続き、導入後の実運用について把握します。実現したいことは各企業大体同じであるにも関わらず、企業によって利用しているワークフローは様々であり、実現するまでに理解すべきことのボリュームに大きな違いがあります。 会議コストを考え、会議体を整理する ファシリテーション や会議に関するビジネス書で数多く述べられていますが、「会議コストは非常に大きなもので意味のある会議をすべき(無駄な会議は廃止すべき)である」といった主旨のトピックが記載されています。 例えば、4人のエンジニアが1hの MTG をする場合、そのコストは超ざっくり¥25,000程度になります。 *9 ですので、その1hの MTG のアウトプットに会社はその金額を払うことになるので、その期待値を超える Value が必要になります。特に会議は複数人で行うものなので、各自が単独で行うTodoよりも大きな人件費が計上されてしまうことになります。また、とりあえず参加するだけで仕事をした気になってしまうので注意が必要です(会議に参加すること自体に Value はなく、その会議でどのようなアウトプットを出したかが重要)。 ということで、CO-ITチームでは必要な会議体を整理し、チームのフェーズに合わせた会議運営を行っています。 情報共有・ナレッジ化を意識する まだまだ続きますが、続いてはチームレベルの ナレッジマネジメント です。一人情シスのような状態でなければ、基本的に業務を進めていくとチームメンバーへの具体的な手順共有は当然として、各種設計指針やそもそもの要件定義内容等々、チーム独自のナレッジを複数人で共有するシーンが多々あるかと思います。ナレッジ共有を全く行わない(何もかも属人化)のような組織はないと思いますが、共有の仕方に課題感が残っている組織は少なくないかと思います。CO-ITチームでは以下のポイントを実行し運用しています。 共有の仕方を定義する 共有すべき粒度を定義する ナレッジ化に惜しみなくリソースを投下する タスクを整理する、 バックログ 化する そして、最後にタスクの整理です。PJ管理やタスク管理を実施するためのツールや方法は様々なので省略しますが、当社では私が入社前の段階から Asana を利用していたため一旦そのまま継続利用しています。タスク整理にあたり、ポイントとしたことは、以下の通りです。 必ず各タスクの目的を明確化する 不明瞭な(誰も自分事としていない)タスクが管理ツール上に表示されていない状態 マネージャは意思決定のみを担当(タスク担当者にマネージャをプロットしない) 全体を同じ箱の中に含め他メンバーの業務状況も 見える化 する *10 スポット、割り込みタスクが入ってもチーム全体のタスク一覧に起票する 個人のローカルTodoで処理しない、オープンにする 基本的に起票する(よほど数分でクローズするようなタスクは例外) 本来であれば、 アジャイル の考え方に沿って、チーム全体で スクラム を用いてスプリント毎に同じMissionに向かって組織運営( バックログ 消化)を行いたいのですが、CO-ITチームではまだまだそういったフェーズにないと判断し、現状は バックログ 化することまでに留まっています。 CO-ITチームの課題 以上の課題整理を行った結果、CO-ITチームでは以下のような課題が見えてきました。 既にある程度クリアできてきているものもありますが、日々最適化すべく現在も業務推進中です。 何をどのように解決したかの話は、また機会があればご紹介させていただきます。 資産管理の精度が低い、管理できていないものが存在する サービスのスコープ、 責任分界点 が不明瞭 ナレッジが点在している、不足している、陳腐化している ブラックボックス 化(実務の外注サービス依存にも起因)、属人化している 業務分析できる基盤が整っていない( 定量 化できていない) 意思決定した背景・経緯・ポイントが体系化されていない 中長期的な視点で要件定義・設計・設定が行われていない 暫定対応でタスククローズした内容のまま形骸化している ユーザ要望を単純に解決することにフォーカスされている 3.将来を想像する 前項の通り、課題は山積みであったため、各課題に対してできる範囲(新規サービスの導入やシステム化・インテグレーションは後回し)で将来を見据えた改善を行う方針としました。 見える化 、 定量 化に向けた(KPI・KGI策定を見据えた)足固め 冒頭で記載したようにコーポレートIT領域というのは取り扱うサービスや技術が広く、様々なスキルセットが求められると言えますが、個々のPJやタスクサイズは小さいことが多々あります。 しかしながら、タスクサイズが小さいからと言って、個々のタスクを全て点で捉えて個別にクローズし続けるといつまで経っても組織運営が最適化されません。 この観点において、CO-ITチームでは将来の対応リソースの削減も見据え、タスクを 定量 化することに注力しています。 見える化 や 定量 化は多くの企業で実施されているかと思いますが、その粒度や精度については様々かと思います。 CO-ITチームも私が入社する前から全くできていないということではなく、粒度や精度に課題があったという話になります。 私が特に重要視しているのは提供サービス全体での体系化と最小限のリソースを目指す運用サイクルの構築になります。 この観点で言えば、CO-ITチームではまだまだ足固めが必要な状態であり、新規サービスの導入や SaaS 間のインテグレーションを考えるフェーズにないと判断しました。 ですので、CO-ITチームではこの一年立ち止まって当たり前のことを実現できる最低限の準備を進めてきました(導入済みサービスはリプレース検討フェーズに移らず基本的に一旦継続利用して精査する)。その中のほんの一例が下記になります。 資産管理の精緻化 問い合わせ対応のワークフロー改善、 見える化 ・ 定量 化 提供デ バイス のラインナップ整理とユーザへの ガイドライン 提供 キッティング業務のワークフロー化 備品貸与 ガイドライン の策定 デ バイス 提供スピードの向上、提供コストの削減 入退社復職休職のような人事イベントに伴うアカウントフローの整理 社内手続き・稟議の整理 サービスの保守期限の統一、サポート窓口の整理 ナレッジ共有方法の統一、ナレッジの質・量の向上 業務改善の話( SaaS のインテグレーションだけが全てじゃない!) 採用業務で様々なエンジニアの方とお話する機会があるのですが、コーポレートエンジニアを目指されている方には「業務改善」にやりがいや面白さを強く感じられていて、プログラミングを行って自動化処理の仕組みを作ることが全て(若干誇張していますが・・・)という志向をお持ちである方(そういった印象を受ける方)にお会いすることがあります。確かにコーポレートエンジニアの業務の中で SaaS のインテグレーションというのは、より技術的な要素が強く自身の満足につながる、かつ他の方(特に非エンジニア職の方)からの評価が高くなる傾向がありますので合理的ではあります。 しかしながら、一度立ち止まって以下の質問に向き合うことも必要ではないでしょうか。 本当にシステム化、自動化することだけが正なのでしょうか? 本当に自動化することでコストは下がっていますでしょうか? 参考: 運用自動化、不都合な真実 CO-ITチームでは、 SaaS 運用だけでなく全てのコーポレートIT領域に含まれるサービスにおいて業務改善を行うことが必要であり、実現する方法(コードを書くことが全てではない)に優劣はなく、アウトプットに対する評価は常にフラットでありたいと考えています。例えば、CO-ITチーム内の一例を紹介させていただきますが、以下のような対応も十分一つの業務改善であり、同等に評価されるものと考えています。 業務改善の一例 左側の写真:会議室のモニタスタンドを壁寄せモニタで可動式に変更 レイアウト変更を容易にするという要件の充足と机上スペースの拡張を実現 右側の写真:管理する サーバル ーム内の棚に共通の収納ボックスを導入 備品管理を最適化(必要な備品に最短でアクセスできる導線の確保とコストの最適化) サービス導入・施策を開始することについて考える CO-ITチームではこの一年立ち止まって足固めをしているため主だった新規サービスの導入は見送っている(他にもっとやるべきことが多い)のですが、新規サービスの導入(リプレース含む)業務に魅力を感じるエンジニアの方は多いのではないでしょうか。逆に既存の ブラックボックス ・複雑化した環境を考慮して体系的に再整理するような業務は(やりたくないとは口には出さないものの)敬遠される傾向が多いように感じます。 この傾向は、自身の直接的な スキルアップ や評価へのつながりやすさに起因するものと思います。加えて、ゼロベースでの設計難易度に比べ、複雑な既存環境の再設計は難易度が非常に高く、ボリュームが大きくなる割にはアウトプットが(新規サービス導入に比較すれば)地味になります。また、実際に稼働しているサービスの場合が多く、放置してもスポットでは課題感が多少あるかもしれないが何とかやり過ごせることが多いことも要因の一つにあるように感じます。 ビジネス書( amazonのすごい会議 )の中にもまさに同じような視点のコラムが掲載されていましたのでご紹介させていただきます。 PDCAサイクル において Amazon 社ではPDだけの人(立ち上げ屋[企画業務])は重視されないが、日本企業ではPDが重視されがちというものです。まさにコーポレートIT領域でも同じように感じます(下記のような状態です)。 サービス導入することにだけ大きなコスト・アテンションが払われる *11 導入担当者の主観・感覚のままローンチされることがある 肝心の設計・設定・運用はレビューが薄い *12 問題が顕在化した後に再設計を検討する(再設計の中心人物がヒーローになる。。。)  *13 CO-ITチームでは、近い将来新規サービスの導入を必要に応じて行いますが、上記のような課題が残らないようなPJ運営を行うつもりですし、一部の声の大きなメンバー *14 だけが新規サービス導入に携わるような組織にならないよう最適な仕組みづくりを日々模索していきます。 チームの安定した運営を考える チーム運営を安定化するポイントとしては、システム化(人に依存しない運用を構築する)やリソースの安定化(メンバーの頻繁な入れ替わりを防ぐ)といったことが挙げられます。前者については各所で述べられているように感じますが、後者はそれに比較すると情報量が少ない印象です。ここでは後者について深掘りします。 各種サービスのシステム化・自動化が非常に重要であることは自明ですが、それ以上にリソースの安定化は非常に重要です。以下にチームメンバー入れ替わりに関するデメリットを整理してみます。 一時的にリソース不足に陥る 新規メンバーの採用コストが必要となる 新メンバーの立ち上がりに1ヶ月〜半年程度を見込む必要がある では、逆にメリットがないかと言えばそうではなく、以下のようなチームに新陳 代謝 が起こるメリットはあります。 旧メンバーより更に良い人材がチームに加入するかもしれない これまで慢性化や形骸化していた業務に良い影響を与えるかもしれない しかしながら、安定したリソースでの運用を目指す方がチームとしての総合戦闘力は高いと感じますし、各自の スキルアップ やチームの新陳 代謝 はチーム運営方法によりカバーできるものと考えますので、メンバーの不要な入れ替わりは避けたいものです。メンバーが離脱する理由を考えると、 待遇面(給与や ワークライフバランス [会社の規定])の問題 会社や部署方針との根本的な価値観の不一致 会社の業績、業種の市場動向 ジョブチェンジ を目指す、またはプライベートな問題 部署内でのコミュニケーションの問題 志向の問題(自社では スキルアップ が見込めないと感じる) 足元の担当業務の問題(同じ業務ばかり、丸投げばかりされる、不公平感を感じる) といったところでしょうか。 この中で1-4まではチーム運営ではどうすることもできないものになりますが、5-7を理由にメンバーの入れ替わりが発生するというのは、当該チームのマネージャやリード担当者に責任があると感じますし、こういった理由が発生しないよう常に改善を続ける必要があります。CO-ITチームでは、将来的なチームの安定運営を目指すために日々  カイゼン  に向き合っています(具体的な話は機会があれば・・・)。 終わりに 今回は具体的なサービスの設計や設定内容、サンプルコード等のご紹介にフォーカスできませんでしたが、CO-ITチームの将来に向けた足固めは着々と進みつつあります。 来年以降ようやく新規サービス導入や施策などのコーポレートITサービス拡充をスコープとしていくフェーズを見込んでおりますので、(個人的には)ようやく楽しい業務にリソースを大幅に割けるタイミングとなります。 2023年12月現在、一緒にコーポレートITを推進いただける方を募集中です。 上記チーム運営に共感いただける方や同じような視座で物事を捉えられる方とともに一緒に当社のコーポレートITを作っていけることを楽しみにしています♪ 今回の記事は以上になります。 最後まで読んでいただき、ありがとうございました。 株式会社 エニグモ すべての求人一覧 hrmos.co *1 : 当社でも数年前まで「情シス」というワードが各所で用いられていましたが、少なくとも私が入社後は意図的に情シスというワードは使用せず、コーポレートITという表現を使用するようにしています。 *2 : 私個人の視点ですが当社はフラットな環境に感じます。 *3 : 当然ベンダーには自社の独自要件を踏まえたオーダーメイドの最適な設計でチューニングを行うように追加コストを支払う *4 : CO-ITチームの場合、マネージャと私の方向性が元々大枠で一致していたことと、経営陣とマネージャが既に同様のコミュニケーションを行っていたことで非常にスムーズにすり合わせが完了しています。企業によっては、このすり合わせが非常に難航し、そもそもすり合わせせずに実務に移ることがありますが、後々重大な課題につながるのでオススメできません。 *5 : あくまで目安であり、ピラミッドのベースにある項目が頂点にある項目に劣るというような優劣は全くありません。全ての項目がコーポレートIT領域には重要です。また、これは一般的に定義されたものではなく、私自身の経験その他から表現したものとなりますので一意見としてご理解ください。 *6 : 8時間 × 20営業日 × 12ヶ月 × 4名 *7 : ミッションサイズは、全社共通の指標があり、入社時を含め半期毎に行われる評価によって適宜決定されます *8 : あくまで私の個人的な意見ですが、業務として責任を負っている(報酬(給与)を獲得している)以上は合理的な説明責任があります( 定量 化することは必要です)。 *9 : SE費¥50,000/人日 × 0.5人日[0.125人日 × 4名] *10 : チームは同じMissionに向き合っているため、各タスクを項目分けすることはあっても ブラックボックス 化しない(隠さない)。各メンバーの Value をそれぞれが参考にしたり、評価するために必須。 *11 : 導入すること自体にバリューはなく、そのサービスで何を実現するかにバリューがある。 *12 : マネージャやリードエンジニアは、実担当でなくても高い視座でシステムの重要トピックや仕様を明確化してレビューを行う必要がある *13 : そもそも新規導入時の考慮不足・レビュー不足に起因して問題が発生する場合が多い。問題が発生する前にチームで再設計が議論され、 バックログ 化されているのが正しい運営であり、ヒーロー(救世主)のような特定個人のスキルセット頼みになるのは問題です。 *14 : 誤解を恐れずに記載すると、いいとこ取りだけして面倒事は他メンバーにぶん投げるような自分本位のようなメンバーのことを指しますが、業務への慣れ等によって知らず知らずの内に意図せずそのような振る舞いになることもあるかもしれません。お互いリスペクトし合える組織を目指したいものです。
こんにちは、エンジニアの太田です。 この記事は Enigmo Advent Calendar 2023 の16日目の記事です。 はじめに TurboはRails7からデフォルトで搭載されており、VueやReactなど javascript の記述が必要だったDOMの更新を javascript を(あまり)書かずに実現させてくれます。 フロントエンドにあまり触れない方にとっては、SPA風のwebアプリへのとっつきやすさが出たと思います。 本記事では、私が初めてTurboに触れて使い方を覚える際に作成したサンプルの一部と使った感想を備忘録的にまとめたものになります。 各公式ドキュメントでも使い方を確認できます。 Rails で JavaScript を利用する - Railsガイド *1 Turbo Handbook *2 サンプルコード 以下は私が主に使用した形です。 ransack *3 とkaminari *4 を使ったリストの更新と追加・編集・削除をしてみます。 controllers class CountriesController < ApplicationController def index @q = Country.ransack(params[:q]) @q.sorts = 'name asc' @countries = @q.result.page(params[:page]) # Request HeadersにTurbo-Frameが設定されているとpartialがreturnされて、 # 対象の要素がpartialに置換される if turbo_frame_request? render partial: 'list', locals: { countries: @countries } end end def show @country = Country.find(params[:id]) end def edit @country = Country.find(params[:id]) end def create @country = Country.create!(country_params) end def update @country = Country.find(params[:id]) @country.update!(country_params) end def destroy @country = Country.find(params[:id]) @country.destroy! end private def country_params params.require(:country).permit(:code, :name) end end models class Country < ApplicationRecord # ransackで検索項目にしたカラムを記載したのみ def self.ransackable_attributes(_auth_object = nil) %w[name] end end views index.html.erb # このフォーム内からのリクエストはヘッダーにTurbo-Frameを設定する # レスポンスのpartialによってid属性がlistのturbo-frame要素が置換される <%= search_form_for @q, html: { data: { turbo_frame: 'list' } } do |f| %> <div> <%= f.label :name_cont, 'name' %> <%= f.text_field :name_cont %> </div> <div> <%= f.submit '検索' %> <%= link_to 'リセット', countries_path, data: { turbo_frame: "_top" } %> </div> <% end %> <%= render 'list', countries: @countries %> <%= render 'form' %> _list.html.erb # id属性がlistのturbo-frame要素 <%= turbo_frame_tag :list, autoscroll: true, data: { autoscroll_block: 'start' } do %> <ul id="countries"> <li> <div>国コード</div> <div>国名</div> <div></div> <div></div> </li> <%= render countries %> </ul> <div> # turbo_frame_tag内は自動的に直近の親を対象としたTurbo-Frameのリクエストになる # なのでkaminariにTurbo用の設定は不要 <%= paginate countries %> </div> <% end %> _country.html.erb # id属性がcountry_{country.id}のturbo-frame要素 <%= turbo_frame_tag country do %> <div><%= country.code %></div> <div><%= country.name %></div> # 直近の親 (id属性がcountry_{country.id}のturbo-frame要素)が対象のTurbo-Frameのリクエストになる # edit.html.erbに置換される <div><%= button_to '編集', edit_country_path(country), method: :get %></div> # GET以外のメソッドではTurbo-Streamのリクエストになる # destroy.turbo_stream.erbの処理を実行 <div><%= button_to '削除', country_path(country), method: :delete %></div> <% end %> edit.html.erb # id属性がcountry_{@country.id}のturbo-frame要素 <%= turbo_frame_tag @country do %> <div> <%= form_with model: @country do |form| %> <%= form.text_field :code %> <%= form.text_field :name %> # 直近の親 (id属性がcountry_{@country.id}のturbo-frame要素)が対象のTurbo-Streamのリクエストになる # destroy.turbo_stream.erbの処理を実行 <div><%= button_to '保存', action: :update %></div> <% end %> # GETメソッドなのでTurbo-Frameのリクエストになる # show.html.erbに置換される <div><%= button_to '中止', country_path(@country), method: :get %></div> </div> <% end %> show.html.erb <%= render 'country', country: @country %> update.turbo_stream.erb # id属性がcountry_{@country.id}のturbo-frame要素をupdate後に置き換える <%= turbo_stream.replace @country %> create.turbo_stream.erb # id属性がcountriesの要素の末尾に_country.html.erbを追加 <%= turbo_stream.append 'countries', @country %> # 登録フォームを入力をリセット <%= turbo_stream.replace 'register' do %> <%= render 'form' %> <% end %> _form.html.erb # 登録フォームのid属性registerを設定しておく <%= form_with model: Country.new, id: 'register' do |form| %> <div><%= form.label :code %><%= form.text_field :code %></div> <div><%= form.label :name %><%= form.text_field :name %></div> <div><%= form.submit %></div> <% end %> destroy.turbo_stream.erb # id属性がcountry_{@country.id}のturbo-frame要素を削除 <%= turbo_stream.remove @country %> turbo-frame turbo-frameは画面内のturbo-frameタグを 一つだけ を対象として置換することができます。id属性必須です。 リストの更新が主な使用ケースでした。 turbo-stream turbo-streamは画面内の 複数 要素をid属性で指定して対象とすることができ、それぞれに対して下記の7つの処理 *5 を実行できます。 先頭追加 末尾追加 直前追加 直後追加 置換(対象要素含む) 更新(対象要素含まず中身だけ) 削除 この時、対象とする要素はturbo-frameタグではなくて普通のdivタグなども指定可能です。注意点はGET以外のリク エス トにする必要があることです。 登録時にリストへ要素を追加すると同時にフォームをリセットするなどが主な使用ケースでした。フラッシュを表示したりで複数箇所の更新が必要になりがちなDB操作が絡むリク エス トでの使用機会が多いと思います。 動きのイメージ リストを更新 検索をリセットして初期化 編集フォームに置換 編集を保存して反映 編集をやめる データを削除 データを登録 細かい話 Hotwireとは、Turboとは 下記の公式の説明やimport文からHotwireという開発アプローチがあって、それを実現させるためのパッケージにTurboというパッケージがあるという感じのようです。 Hotwire is an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire . *6 import * as Turbo from "@hotwired/turbo" HotwireにはTurbo以外にもStimulusとStradaがあり、この三つの要素から成ります。 Rails7以降ではデフォルトでTurboとStimulusが使えるようになっています。 # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] gem "turbo-rails" # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] gem "stimulus-rails" ReactやVueとの違い 前述の公式説明には「without using much JavaScript by sending HTML instead of JSON over the wire .」とありますが、これがHotwireがReactやVueと異なるポイントです。 Rails でReactやVueを使う場合は、サーバサイドの rails がcontrollerでデータをreturnし、そのデータを取得するようにクライアントサイドのReactやVueの実装をするかと思います。 一方でHotwireでは、上に挙げたサンプルコードのようにリク エス トに対してcontrollerがHTML(render partial)をreturnするだけになります。 確かに、 JavaScript を使わずに JSON ではなくてHTMLを送信するようになっています。 Turboを使った感想 ransackやkaminariといったviewsを構成するファイルに対するgemをそのまま使えて、わざわざフロントエンド用にnpm installとかしなくて済むのがとてもよかったです。 DOM更新はほとんど JavaScript を記述しなくてもよかったのもあり、作業 工数 もそこそこ少なく済むのではないかと思います。 使いづらいところとしては、turbo-frameタグで全体を囲わなければならないのでスタイルの当て方が少し面倒になる場面がありました。 css フレームワーク を使う場合は結構気を使う必要があるかもしれません。 また、各アクションでhtmlを返す必要があるので、必然的にviews ディレクト リのファイルが多くります。jsの記述が必要無くなった分という感じです 。 一般公開する大きなサービスではちょっと頼りなさそうな感じはしましたが、他の業務もしながら開発する社内ツールくらいの規模であれば十分なものだと思いました。 最後までご覧いただきありがとうございました。 明日の記事の担当は、コーポレートエンジニア(コーポレートIT[CO-IT]チーム) の横川さんです。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co *1 : https://railsguides.jp/v7.0/working_with_javascript_in_rails.html#turbo *2 : https://turbo.hotwired.dev/handbook/introduction *3 : https://github.com/activerecord-hackery/ransack *4 : https://github.com/kaminari/kaminari *5 : https://turbo.hotwired.dev/handbook/streams#but-what-about-running-javascript%3F *6 : https://hotwired.dev/
はじめに こんにちは、株式会社 エニグモ の開発を担当しているグループでエンジニア リングマ ネージャーをしている後藤です。 この記事は Enigmo Advent Calendar 2023 の15日目の記事です。 私は、開発グループでエンジニア リングマ ネージャーという役割をしています。この記事では、チームメンバーとの関わりの中で大切にしている「 心理的 安全性」についてポエムを書かせていただきます。 この記事の対象者 この記事の対象は、「 心理的 安全性」という言葉は聞いたことあるけどモヤッとしかわからないが大切そうだ、みんなが「 心理的 安全性」といっているから上っ面だけでも理解してドヤ顔をしたいという人を対象にしています。 この記事を通して、読者の方に「 心理的 安全性」がどうソフトウエア開発に影響を与えていると感じているのか、どうしたらソフトウエア開発チームにとって良い「 心理的 安全性」のある環境を作れるのかを自分で解釈する手助けになればと思っております。 あくまで、私の解釈ですのでこの記事を参考に自分の考え、理解を勧めていく手助けになれば幸いです。 因みに、私が思っている「 心理的 安全」なチームは以下のイラストのイメージです。 安心できるチーム 心理的 安全性の定義とソフトウエア開発現場における私の理解 まずはじめに、「 心理的 安全性」という言葉ですが、 Google が発表した「成果を出すチーム」の条件に出てきて有名になったと理解しています。私もこの記事で知りました。それからずっとこの「 心理的 安全性」が引っかかっていました。 なぜならマネージャーと名のつく役割を担っている以上、「成果」を出すことを期待されています。それに、最も影響を与えるとなれば理解しないわけにはいきません。 ところがです、「 心理的 安全性(psychological safety)」について調べると、組織の中で自分の考えや気持ちを誰に対してでも安心して発言できる状態のこと、と出てきます。また、そのような環境を作るために、自分の病気のことを話したり、プライベートなことも話せる関係を作っていくと良いと結ばれていました。 そうなのかー、私には無理じゃん、というのが正直な感想でした。エンジニア上がりでエンジニア仕事大好きなどちらかというとコミュ障寄りの私にとって、プライベートな話をしたり雑談をしながらチームを作って行くなんてできっこないと思ったわけです。 それでも、この「 心理的 安全性」を放置しておくわけにもいかないですし、別にプライベートな事が話せる事が必須条件ではないだろうという思いもあり私なりに噛み砕いてみました。 「 心理的 安全性」が低いとどんな問題があるのか 「 心理的 安全性」を理解するにあたり、「 心理的 安全性」が低いとどんな問題があるのかを考えてみました。 いろいろあるのですが、私が考えている一番の弊害は、開発をしていて「この部分少し不安だ」、「あの部分、実は問題がある気がする」といった「もやっと」した気持ちを他のメンバーに伝えることができない、伝えることに抵抗がある、ということではないかと考えました。 なぜなら、「この部分少し不安だ」、「あの部分、実は問題がある気がする」という部分は、なぜだか本番にリリースするとかなりの確率でエラーが起きたりします。そして、分かっていた本人は心のなかで「やっぱりか!」と思いながら修正に追われるわけです。 この「もやっと」を事前に伝えられていれば、トラブル対応に追われるという状況を回避できたはずです。 この、少し不安だが確信が持てない、間違っているかもしれないけど、みたいな事を伝えられるチームである事が生産性に影響を与えるのではないかと考えたのです。 「もやっと」を伝えられるチームにするために何をしているのか 私達のチームでは、毎週金曜日に KPT フレームワーク をつかって振り返りをしています。この場でこの「もやっと」を伝えられる雰囲気を作る事を心がけました。 そうはいっても、「もやっと」を伝えてくださいね、と言ってみたところで意見が出てくるわけではありません。 そのため、1on1で出てきた、困ったことや解決した事を、些細なことでも良いので週次の振り返りに上げてもらうよう伝えることを根気よく繰り返しています。また、私自身が見つけた問題や、解決した事も話すようにしています。 このような事を繰り返していくことで、小さな事でも伝えて良いんだ、という空気ができてくる。また他の人が伝えてくれた小さな事が役に立った!という経験の積み重で、「もやっと」を伝えていこうという空気が出てくるのではないかと思っています。 まとめ ソフトウエア開発の現場で「 心理的 安全性」は、小さな「もやっと」を伝えられるか、に影響すると考えています。 この小さな「もやっと」を意識的に引き出していくことで、ソフトウエア開発で発生する問題の芽を早いうちに摘む事ができ、開発生産性を改善できるのではないかと考えています。 そのために、小さな発見を伝えられる場作りを大切にしています。 おまけ 週末に BIBLIOTHECA というラジオ番組を聞いていたら、この「もやっと」が伝えられないチームの状況を適切に表す言葉に出会いました。この状況は、「集団浅慮(しゅうだんせんりょ)」と呼ばれる状況と一致しているのではないかと思います。 「集団浅慮(しゅうだんせんりょ)」とは、優秀な人達が集まって議論した結果、とても残念な結論を出して大失敗するという状況です。 そのラジオ番組によると、過去に ケネディ 大統領がこの失敗に陥り、そこからどう改善していったのかという事が以下の本に書いてある、と紹介されていました。この本の中に、マネージャーとして気をつけるべきことが潜んでいるのではないかと思っています。もっとも、集団浅慮に陥りやすいのは強いカリスマ的なリーダーがいるとき、というのがあったのでその部分は当てはまらない気がしますが、今度読んでみたいと思っています。 集団浅慮ー政策決定と大失敗の心理学的研究 作者: アーヴィング・L・ジャニス 新曜社 Amazon 一緒に働く仲間を募集しています! 株式会社 エニグモ では一緒に働く仲間を募集しています。興味のある方は以下の求人をご参照ください。 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、デザイナーの 細田 です。 この記事は Enigmo Advent Calendar 2023  の 15日目の記事です。 日頃の業務では BUYMA の様々なコンテンツのデザインを担当しています。 中でもHTMLメルマガのデザインに関しては、数年前から改修に携わるようになり、ABテストによるデザイン検証に取り組んできました。 今回はその中でも、改修によって、お問い合わせフォームへの 流入 数が 前月比で140%増加 し、本施策のコンバージョンとなるお問い合わせ数も 約3倍に増えた 成功事例をご紹介します。 改修前のデザイン まずは、今回改修依頼を受けたメルマガの概要をご紹介します。 対象者 高額な商品を複数回閲覧しているユーザー 内容 “ BUYMA コンシェルジュ ” *1 というサービスを紹介し、お問合せフォームへ誘導する デザイン このメールを見て、皆さんはどう感じましたか? 私は、「なんだか唐突だな」と感じました。 例えるなら、お店で洋服を見ていたら突然声をかけられ、 「なにかお探しですか? コンシェルジュ へご相談ください」と名刺を渡され去っていく・・ そのような唐突さを感じました。この内容では、お問合せしてみようと思う人は少ないかもしれません。 それでは、どうすれば唐突な印象を解消し、 BUYMA コンシェルジュ というサービスに興味をもってもらえるのでしょうか。 改修後のデザイン 改修後のデザインは以下のようになりました。順番に解説していきたいと思います。 【💡POINT 1 】ヘッダーに送信理由を記載する 私が、メルマガにおいて1番重要だと感じていることは、受信者に “自分宛” と思わせる ことです。 メルマガは “チラシ” と ”手紙” の両方の役割を担っています。 サッと見てゴミ箱行きのチラシとなるか、じっくり読んで返事を書きたくなる手紙になるか。 それにはそのメルマガが無作為に送られたものではなく、“自分に宛てて送られたものである” と感じてもらえるかどうかが重要となります。 そのために有効な手段として、上記のようにヘッダーに “どういう理由でこのメールを送信したのか” を記載する、という方法があります。 今回であれば、「高額な商品をご検討中の方へ特別なご案内です。」と記載することで、受信者がなぜこのメルマガが自分へ届いたかを理解し、「自分宛だから見てみよう」と感じる可能性を高めることができます。 【💡POINT 2 】タイトルは1番伝えたいことを簡潔に メルマガ内で1番目を引くタイトルには、そのメルマガで1番伝えたいことを簡潔に記載します。 改修前のタイトルでは、「お探しの商品は見つかりましたか?」とありますが、このメルマガで1番に伝えたい事は BUYMA コンシェルジュ の紹介です。 そこで、改修後のデザインでは「 BUYMA コンシェルジュ がバイマ太郎様のお買い物をサポートいたします。」と変更しました。 そうすることで、「 BUYMA コンシェルジュ って何だろう?」と興味を抱いてもらうことができます。 また、タイトルに受信者の名前を挿入し強調することで(例ではバイマ太郎となっています) 【 POINT 1 】で取り上げた“自分宛である” ことの演出を、より一層強めることができます。 【💡POINT 3 】分かりやすい見出しで離脱を防ぐ 改修前のメルマガには BUYMA コンシェルジュ についての説明がなく、「そもそも BUYMA コンシェルジュ ってなに?」と受信者を戸惑わせてしまう可能性がありました。 そこで改修後のデザインでは、 BUYMA コンシェルジュ についての説明エリアを追加したのですが、重要となるのが見出しです。 今回は、「 BUYMA コンシェルジュ とは?」という見出しをかなり目立つ形で挿入しています。 こうすることで、 BUYMA コンシェルジュ の説明エリアであることを明示し、タイトルを見て「 BUYMA コンシェルジュ って何だろう?」と興味を示したユーザーの目線をそのまま説明へと誘導する役割を担っています。 これが見出しのない、ただの文章としてそこに配置されていた場合、 特に スマートフォン の小さい画面で見た時に、反射的に読み飛ばされてしまったり、「読むのが面倒だ」とメルマガ自体から離脱されてしまう恐れもあります。 しっかり読んでもらいたい文章を配置する時こそ、目立つ見出しで分かりやすく区切ったり、文章自体も極力簡潔にまとめるなどの、細かい気配りが必要となります。 【💡POINT 4 】ボタンはベタ塗りで視認性を高める 改修前はボーダーであったボタンを、改修後はベタ塗りに変更しました。 その理由は、 BUYMA 全体のデザインルールに合わせるという目的もあったのですが、 スマートフォン の小さな画面で見た時の視認性の高さが、やはりベタ塗りの方が圧倒的に高いからです。 また、最近ではメルマガをダークモードで閲覧しているケースも増えており、 ダークモードで見ると特に、ベタ塗りボタンの視認性の高さが顕著になります。 まとめ 以上のような改善を行った結果、 BUYMA コンシェルジュ のお問い合わせフォームへの 流入 数が前月比で140%増加し、本施策のコンバージョンとなるお問い合わせ数も約3倍に増えました。 弊社ではデザイン改修に加え、MAツールの導入により、コンテンツ内容のパーソナライズ化や、配信対象の最適化など、よりユーザー1人1人に寄り添ったメルマガ作成を行なっており、確実に成果を生み出しています。 今後も地道なABテストを積み重ねながら、このような成功例を蓄積し、発信していければと考えています。 株式会社 エニグモ すべての求人一覧 hrmos.co *1 : BUYMA コンシェルジュ とは、特別なお客様限定の招待型プライベートサービスです。ご希望商品のお探しからお取引の相談まで、 BUYMA のご利用に関するあらゆるサポートを専任の コンシェルジュ が行なっています。
こんにちは。株式会社 エニグモ にてフロントエンドエンジニアを務めています新井です。 こちらは Enigmo Advent Calendar 2023 および アクセシビリティ Advent Calendar 2023 の 14 日目の記事です。 はじめに:ウェブアクセシビリティとは? 1. ページ内の見出しの最適化 2. リンクや状態の判別を色の変化だけで行わないようにする 3. キーボード操作時のフォーカスインジケーターは非表示にしない 4. クリッカブルな要素の実装には a タグまたは button タグを使用する 5. 画像の代替テキストの指定と装飾的な画像の取り扱い 6. フォームコントロールには必ずラベルを付与する 7. フォームの入力欄のオートコンプリートを有効にする 8. ラジオボタンやチェックボックスを装飾する際は元の input の隠し方に気をつける 9. アコーディオン UI は可能な限り details 要素を利用する 10. モーダル表示時は背面のコンテンツを読み上げ・選択不可能にする おわりに:BUYMA でウェブアクセシビリティを促進する理由 はじめに:ウェブ アクセシビリティ とは? X(旧 Twitter )や各種テック記事などを見ていると、多くの人がウェブ アクセシビリティ を、障がいを持つ人々、特に視覚障がいのある人々への配慮として認識しています。 これは正しい認識の一部ではありますが、ウェブ アクセシビリティ の範囲はもっと広いです。実際には、全ての年齢層や様々な身体的、精神的条件を持つ人々が、多種多様な環境下でウェブサイトやアプリケーションを利用可能にすることを目指しています。 障がいがない人も、健康問題、マウスや トラックパッド の故障、音声再生の不可能な環境など、様々な状況に直面することがあります。ウェブ アクセシビリティ とは、これら多様なユーザーがウェブサイトやアプリケーションを容易に利用できるように設計することを意味します。 この記事では、ウェブ アクセシビリティ を向上させるための重要な10の項目を紹介します。私たちが運営するショッピングサイト「 BUYMA 」も今年から アクセシビリティ への取り組みを進めており、これらの項目を今後達成することを目指しています。 1. ページ内の見出しの最適化 具体的にはページ内で使用されている h1〜h6 タグを、階層構造の深さに応じて最適化します。 多くの方が見出しの最適化と聞いて SEO の改善を連想するのではないでしょうか?実際に、見出しの整理が SEO に好影響を及ぼすことはあるかもしれません。しかし、もっと注目すべきなのは、この最適化が SEO のみならず アクセシビリティ にも大きく貢献するということです。 スクリーンリーダーなどの支援技術を利用するユーザーは、見出しを通してページ全体の構造を理解します。また、見出しジャンプ機能を使って必要な情報に素早くアクセスすることもあります。WebAIM のアンケートによると、約 7 割のユーザーが見出しジャンプ機能を使用しているとのことです。 webaim.org 見出しを用いてウェブサイトの情報構造を適切に整理することで、ユーザーが情報を迅速に理解できるようになります。 Google Chrome の 拡張機能 「 Web Developer 」を使用することでページの見出しのアウトラインを可視化することができます。見出しレベルを視覚化して検証することで、ページの構造が明確になり、改善点を見つけやすくなります。見出しの正確さや一貫性を確認するために、見出しレベルは可視化して検証することをおすすめします。 参考: 達成基準 2.4.6: 見出し及びラベルを理解する 2. リンクや状態の判別を色の変化だけで行わないようにする リンクや状態(フォーカスやホバーなど)の判別を色のみでデザインしてしまうと、 色覚異常 を持つユーザーにとって判別が困難になる可能性があります。 この問題に対処するためには、色だけでなく形状やその他のビジュアル変化を用いて区別ができるようにすると良いでしょう。 具体的な対策としては次のようなものがあります。 テキストリンクは色の変更に加えて、下線を表示する 状態が変化する際に、アイコンを変更する ボタンがホバーされた時は、背景色を変えるだけでなく枠線も表示する 特にホバーの状態は、透明度の変更のみでデザインされることが多いので、デザイナーと協力して状態の変化が明確に分かるデザインを作成することが望ましいです。 また、以下の CSS コードを デベロッパ ーツールで適用することで、ウェブサイトをグレースケールに変換し、色に頼った UI がないかどうかを検証するのに役立ちます。 body { filter : grayscale( 1 ); } ただし、濃い赤のような色はグレースケールへの変換では確認しにくい場合があるため、検証のコストとのバランスを考慮しながら、色覚特性のタイプごとに見え方をシミュレーションすることも有効かもしれません。 参考: 達成基準 1.4.11: 非テキストのコントラストを理解する 3. キーボード操作時のフォーカスインジケーターは非表示にしない リンクやボタンがクリックされた際に表示されるフォーカスインジケーターは、しばしばデザイン上の理由で非表示にされがちです。しかし、フォーカスインジケーターを非表示にしてしまうと、キーボードでウェブサイトを操作するユーザーはコンテンツの利用が困難になってしまいます。 クリック時のフォーカスインジケーターを非表示にしつつ、キーボード操作時のフォーカスインジケーターを維持するには、以下のような CSS をベースで指定しておくと良いでしょう。 : focus : not( :focus-visible ) { outline : none ; } :focus-visible 擬似クラスはキーボード操作によりフォーカスされた場合に適用され、マウス操作によりフォーカスした場合には適用されません。 もしサイト内に複数の CSS が読み込まれ、各要素に outline:none が適用されているような場合、以下のような CSS を指定することでキーボード操作時のフォーカスインジケーターを復活させることができます。 @layer focus { :focus- visible { outline: revert; } } この方法では、 Cascade Layers という機能を用いて優先度を高めています。 このスタイルは ID セレクタ で定義されたスタイルよりも優先されますが、インラインスタイル( style 属性)や !important 指定されたスタイルよりは優先度が低いため、 !important を使用するよりも安全です。 ただし、 outline プロパティを上書きする場合は新たに @layer を設けて上書きする手間があるので、あくまで応急処置として扱うのが賢明です。 Cascade Layers は、現在すべてのモダンブラウザで使用可能です。 さらに、フォーカス時の状態をより分かりやすくするために、インジケーターに加えてホバー時のスタイルも適用しておくとフォーカスの判別がよりわかりやすくなります。 .button : focus -visible { /* ホバー時のスタイル */ } @media (hover: hover) and (pointer: fine) { .button :where(:any-link , :enabled , summary):hover { /* ホバー時のスタイル */ } } タッチデ バイス でのホバーは動作に支障が出る場合があるため、hover メディアクエリを利用してタッチデ バイス でのホバーは無効化しておくと良いでしょう。 参考: 達成基準 2.4.7: フォーカスの可視化を理解する 4. クリッカブル な要素の実装には a タグまたは button タグを使用する クリッカブル な要素を div タグや span タグを用いて実装すると、支援技術を使用するユーザーやキーボード操作に依存するユーザーがその要素を選択できなくなることがあり、結果としてコンテンツの アクセシビリティ が損なわれる可能性があります。 リンクは a タグを、ボタンは button タグを使用して、適切に マークアップ しましょう。支援技術は a 要素を「リンク」、button 要素を「ボタン」と読み上げます。 既存のコードで a タグや button タグに変更するのが難しい場合、tabindex 属性と keydown イベントを使って クリッカブル な振る舞いを実装することができます。以下は、 BUYMA で使用されている React のコードの一例です。 <tr key= { item.id } className= "catalogs-table__row" onClick= { () => handleSelectItem(item) } onKeyDown= { (e) => { if (e.key === "Enter" || e.key === " " ) { // Enter or Space で実行 handleSelectItem(item); } }} tabIndex= { 0 } > ... </tr> このコードでは、tr要素に onClick イベントと onKeyDown イベントが設定されており、tabIndex={0}によってキーボード操作で選択可能になっています。 5. 画像の代替テキストの指定と装飾的な画像の取り扱い 原則的にコンテンツ上意味のある画像には必ず代替テキストを指定し、装飾的な画像は読み上げしないようにします。 img 要素の代替テキストは alt 属性で指定します。 < img alt = "BUYMA" src = "logo.png" width = "130" height = "25" decoding= "async" /> アイコンフォントや SVG 要素に代替テキストを指定する際には、role="img" と aria -label 属性を併用して指定します。span や div、 svg に aria -label を指定すると、支援技術によっては適切に読み上げられない場合があります。また、 span や div の暗黙のロールが WAI- ARIA 1.2 より generic となったことで aria -label を付与するのは仕様違反となっています。安定した読み上げを確保するためにも、必ず role="img" を指定しましょう。 < span role = "img" class = "fab-icon fab-icon-facebook" aria-label = "Facebook" ></ span > < svg role = "img" aria-label = "X" viewBox= "0 0 20 20" > … </ svg > 装飾的な画像として img 要素を使用する場合は、alt 属性を空にします。alt 属性を省略すると、支援技術は URL を読み上げる可能性があります。 < img alt = "" src = "crown.svg" width = "20" height = "20" decoding= "async" loading= "lazy" /> 装飾的なアイコンフォントや SVG 要素は、読み上げから除外するために aria-hidden="true" を指定します。 < span class = "fab-icon fab-icon-information" aria-hidden = "true" ></ span > < svg aria-hidden = "true" viewBox= "0 0 20 20" > … </ svg > 代替テキストの作成時には、画像が伝える情報を正確かつ適切にユーザーに届けることを心掛けるようにします。また、画像に関連しない情報を SEO のために加える行為はNGです。代替テキストはコンテンツの重要部分であり、他のテキストと同様、コンテンツ設計の初期段階で検討すると良いでしょう。 さらに、意味を持つ画像は CSS の background-image を使って背景として設定しないようにします。背景画像として設定してしまうと、支援技術を使用するユーザーが画像を認識できなくなります。また、background-image は loading 属性や decoding 属性を指定できないため、ウェブサイトのパフォーマンスに影響を及ぼす可能性があります。 参考: 達成基準 1.1.1: 非テキストコンテンツを理解する 6. フォームコン トロール には必ずラベルを付与する フォーム内の入力欄やテキストエリア、セレクトボックス、 チェックボックス 、 ラジオボタン などのフォームコン トロール には、label 要素を使って明確にラベルを付けるようにします。 label 要素の使用には アクセシビリティ の観点から大きな利点があります。フォームコン トロール にフォーカスが合わされた際に要素の名前を読み上げることが可能になり、またクリックやタップの範囲が広がり、利用しやすくなります。 フォームコン トロール とラベルを関連付ける方法としては、ラベルとフォームコン トロール を label 要素で包むか、フォームコン トロール の id 属性を label 要素の for 属性に紐付ける方法があります。 < label > ニックネーム < input type = "text" name = "nickname" /> </ label > < label for = "nickname" > ニックネーム </ label > < input id = "nickname" type = "text" name = "nickname" /> デザイン上、フォームコン トロール のラベルが UI 上に表示されていない場合でも、支援技術利用者のために aria -labelledby 属性や aria -label 属性を使用してラベル付けを行うことが望ましいです。 aria -labelledby 属性を使用した例 < form > < p id = "keyword_label" style = "display: none" > 検索キーワード </ p > < input type = "search" name = "keyword" aria-labelledby = "keyword_label" /> < button > 検索 </ button > </ form > aria -label 属性を使用した例 < form > < input type = "search" name = "keyword" aria-label = "検索キーワード" /> < button > 検索 </ button > </ form > また、 ラジオボタン や チェックボックス など、複数のフォームコン トロール が意味的に一つのグループを形成する場合、fieldset 要素を使用してこれらをグループ化するようにします。この方法を採用すると、支援技術を使用してフォームコン トロール を操作する際に、そのコン トロール が属するグループの名前も読み上げられるようになります。 以下は、fieldset 要素を使用して ラジオボタン をグループ化した例です。性別のコードは ISO 5218 で定められたものに従っています。 < fieldset > < legend > 性別 </ legend > < ul > < li >< label >< input type = "radio" name = "gender" value = "1" /> 男 </ label ></ li > < li >< label >< input type = "radio" name = "gender" value = "2" /> 女 </ label ></ li > < li >< label >< input type = "radio" name = "gender" value = "9" /> その他 </ label ></ li > < li >< label >< input type = "radio" name = "gender" value = "0" /> 不明・回答しない </ label ></ li > </ ul > </ fieldset > また、ラベルを プレースホルダ ーで代用するUIも時折見かけますが、以下の観点から避けたほうがいいでしょう。 記入時にラベルが消えてしまうためユーザーの短期記憶に負荷をかけることとなる フォームの送信前にユーザーがどの項目に何を書いたのか判別できなくなる可能性がある キーボード操作でフォーカスが当たるとラベルが消えることによる利便性の低下 参考: 達成基準 2.4.6: 見出し及びラベルを理解する 7. フォームの入力欄のオートコンプリートを有効にする オートコンプリートを有効にすることにより、ブラウザに保存された補完機能を利用できるようになり、結果としてユーザーは素早くフォームの入力を完了することができます。 < label > 電話番号 < input type = "tel" name = "tel" placeholder = "(例) 090-0000-0000" autocomplete = "tel" pattern = "\d{2,4}-?\d{2,4}-?\d{3,4}" title = "電話番号は正しく記入してください" /> </ label > サービス内でよく使用する autocomplete 属性の値は以下の通りです。 値 補完する要素 on autocompleteを許可する off autocompleteを許可しない name 氏名 given-name ファーストネーム (名前) additional-name ミドルネーム family-name ラストネーム(名字) tel 電話番号 email メールアドレス username ユーザー名orアカウント名 postal-code 郵便番号 address-level1 都道 府県 address-level2 市区町村 address-level3 町域 address-level4 番地など organization 企業・団体・組織名 cc-name クレジットカード登録名 cc-number カード番号 cc-exp カードの有効期限 多くのサービスでは、電話番号やクレジットカード番号などでオートコンプリート属性を活用します。一部の UI デザインでは、電話番号やクレジット番号の各部分を別々の入力欄で要求することがありますが、これは単に使いにくいだけでなく、オートコンプリートの利用を妨げる可能性があるため、デザイナーはこれらの情報を一つの入力欄で収めるよう配慮すると良いでしょう。 8. ラジオボタン や チェックボックス を装飾する際は元の input の隠し方に気をつける display:none や visibility:hidden されている input はキーボード操作でのフォーカスが不可能になってしまうため、 ラジオボタン や チェックボックス を装飾する際は別のアプローチで隠すようにしましょう。 < label class = "checkbox" > < input class = "checkbox__input" type = "checkbox" /> < span class = "checkbox__icon" aria-hidden = "true" ></ span > < span class = "checkbox__text" > 規約に同意する </ span > </ label > .checkbox__input { height : 1px ; opacity : 0 ; position : absolute ; width : 1px ; z-index : -1 ; } さらに、装飾された チェックボックス や ラジオボタン がフォーカスされた際には、その状態がユーザーに明確に認識できるようにしましょう。 以下の CSS は、フォーカスされた際に チェックボックス の周りにアウトラインを表示し、関連するテキストを下線で強調する方法の例です。 .checkbox__input { height : 1px ; opacity : 0 ; position : absolute ; width : 1px ; z-index : -1 ; } .checkbox :has( .checkbox__input : focus -visible) { outline : 1px solid blue ; } .checkbox__input : focus -visible ~ .checkbox__text { text-decoration : underline ; } 9. アコーディオン UI は可能な限り details 要素を利用する アコーディオン UI を details 要素で実装すると、非アクティブな要素内のテキストもページ内検索で見つけることができ、キーボード操作や支援技術によるナビゲーションも可能になります。さらに、 JavaScript を使わないためパフォーマンスが向上し、 JavaScript が動作しない場合でも アコーディオン が利用可能になります。 < details > < summary > 見出し </ summary > < p > コンテンツ </ p > </ details > 例えば、 BUYMA では スマートフォン 表示のフッターメニューに details 要素が使用されています。 ただし、 jQuery の slideToggle のようなアニメーション効果を追加したい場合、閉じる際に CSS の transition が効かないため別途 JavaScript が必要になります。 余談ですが、 jQuery の slideToggle のようなアニメーションを実装する際、現代では折りたたむ要素に display: grid と transition: grid-template-rows .3s ease-out (所要時間とイージングはお好みで調整してください) を指定し、 開閉時に grid-template-rows の値を 0fr ↔ 1fr に変更するだけで対応が可能です。 参考: detailsとsummaryタグで作るアコーディオンUI - アニメーションのより良い実装方法 - ICS MEDIA 10. モーダル表示時は背面のコンテンツを読み上げ・選択不可能にする モーダルコンテンツ(ダイアログやドロワーメニューなど)が表示されている際には、背後にあるコンテンツがキーボードや支援技術からアクセスされないよう設定します。 背面コンテンツに inert 属性を付与することで実現できます。inert 属性があると、ブラウザはその要素に対するユーザーの入力イベント(フォーカスイベントや支援技術からのイベントを含む)を無視します。 < body > < div id = "wrapper" inert> < header > ... </ header > < main > ... </ main > < footer > ... </ footer > </ div > < div role = "dialog" aria-modal = "true" aria-labelledby = "modal_1_title" tabindex = "-1" > < h2 id = "modal_1_title" > モーダルタイトル </ h2 > < p > モーダルコンテンツ </ p > </ div > </ body > inert 属性は多くの現代のブラウザで利用可能ですが、未対応の環境での使用を検討している場合は、Polyfill が利用可能です。 www.npmjs.com 参考: HTMLElement: inert プロパティ おわりに: BUYMA でウェブ アクセシビリティ を促進する理由 ウェブ アクセシビリティ を考慮することにより、サービスをより広範なユーザーに提供することが可能になります。これにより、 アクセシビリティ の問題からサービスの利用を諦めていた人々を取り戻し、機会損失を防ぐことができます。 さらに、使いやすいサービスを提供することで、全体的なユーザー体験を向上させることにもつながります。 弊社では、 アクセシビリティ チャンネルを Slack で設立し、 アクセシビリティ に関する Redmine チケットを作成するなど、取り組みを進めています。まだ完全に対応しきれていない部分は多いですが、近い将来、これらの「必ず達成しておきたい アクセシビリティ 対応」を実現する目標に向けて努力を続けています。 明日の記事の担当は SELL チームの後藤さんです。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、サービスエンジニアリング本部の寺田です! この記事は Enigmo Advent Calendar 2023 の13日目の記事になります。 私はエンジニアとして BUYMA の決済システム・配送システムの保守/運用や、またこれらのシステム領域を中心としたサービスの新規開発などを主に担当しています。 この記事では Emacs Orgmode を使ったタスク管理の方法について紹介していきたいと思います。 みなさんはタスク管理に何を利用しているでしょうか?? 紙とペン、OS 標準の Reminder アプリ、 Google Calendar など、人それぞれ普段お使いのものがあるかと思います。 私は実際に普段の業務で Emacs Orgmode を利用したタスク管理を行っています。 これを使っていく中で私は以下のようなメリットを感じています。 仕事の抜け・漏れを起こさなくなる。 自分のキャパを最大限活かせる。(キャパオーバーになるのも避けられる。) 日々の仕事に達成感を得られる。 今現在のタスク管理の方法がしっくりこない...と感じている方は、 ぜひこの記事をご覧いただき、一つ選択肢に加えてみてはいかがでしょうか? Emacs Orgmode とは Orgmode は Emacs の 拡張機能 の一つです。独自の マークアップ 言語を利用できるシンプルな テキストエディタ ですが、 非常に高機能でタスク管理などに便利な数多くの機能をサポートしています。 https://orgmode.org/ja/ 当然 Emacs を必要とするので、Emacser ではない方には関係ない...とお思いかもしれません。 しかし私も普段の開発では VSCode + Vim キー バインディング を使っており、 Emacs は何もかもが素人中の素人です。 そういった方におすすめしたいのが Spacemacs です。 https://www.spacemacs.org/ Emacs ベースのエディタですが、デフォルトで Vim の キーバインド が利用できます。 私も Spacemacs を利用していますが、ほぼほぼ Vim と同じ操作感で扱うことができています。 Orgmode を利用したタスク管理の流れ ここからは私が実際に行っているタスク管理の流れを、Orgmode の機能の紹介とともに説明していきます。 ファイル構成 私はタスク管理のために3種類のファイルを用意しています。 . ├── TODO.org └── weekly    └── YYYYMMDD.org └── daily    └── YYYYMMDD.org TODO.org 自分が担当することになったタスクはひとまず全て TODO.org に追加します。 まさしく TODO リストの役割を果たしており、こちらにタスク全量がリスト化されることになります。 weekly/YYYYMMDD.org 1週間分のタスクリストを表すファイルです。 TODO.org からタスクをピックアップして作成します。 daily/YYYYMMDD.org 1日分のタスクリストを作成する場所です。 weekly/YYYYMMDD.org からさらにタスクをピックアップして作成します。 (随時)TODO リストを作成する 何かタスクに アサイ ンされたらまずは TODO.org に追加していきます。 合わせてここで作業時間の見積もりを行います。 Effort Estimates を利用すると見積もった時間をタスクに対して簡単に設定できます。 https://orgmode.org/manual/Effort-Estimates.html また、TODO リストの未済管理には Multi-state Workflow を利用してステータス管理を行うと良いでしょう。 https://orgmode.org/guide/Multi_002dstate-Workflow.html あらかじめ TODO , DONE ステータスを設定しておき、コマンド一発で遷移できるようにしておきます。 見積もり、ステータス設定を行った後の TODO.org は以下のようになります。 #+SEQ_TODO: TODO(t/!) | DONE(d) // Multi-state Workflow で遷移可能なステータスの定義。タスクに対して C-c C-t でステータスを変更できる。 #+PROPERTY: Effort_ALL 1:00 2:00 3:00 5:00 8:00 13:00 // Effort Estimates で設定可能な見積もり時間。タスクに対して C-c C-x e でここで定義した時間を設定できる。 *** 新規配送システム導入 **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる :PROPERTIES: :Effort: 5:00 :END: **** TODO 出品者は新規配送方法に利用できる配送用ラベルを発行できる :PROPERTIES: :Effort: 8:00 :END: **** TODO 出品者は新規配送方法を利用して配送を実施することができる :PROPERTIES: :Effort: 13:00 :END: (毎週月曜日)1週間のやることリストを作成する 週初めの業務開始のタイミングで1週間のやることリストとして weekly/YYYYMMDD.org を作成します。 まずは自分が1週間で作業に充てられる時間を計算していきます。 ただし1週間のうち全ての時間を作業時間に使えるわけではないのでこれを考慮しましょう。 私の場合、勤務時間のトータルは 40 時間です。(週5日、1日8時間勤務で 5 * 8 = 40 時間)しかし定例や打ち合わせの時間を除くと大体 20~30 時間くらいが作業に充てられる時間となることがほとんどです。 当然休みの日や早退の日があればこれも考慮します。 計算が終わったらファイルにこの時間をメモしましょう。 次に TODO.org から計算した作業時間内でこなせるだけのタスクを選んで weekly/YYYYMMDD.org に追加します。 ここでのポイントですが、 週はじめの段階では作業時間の 80 % 程度でできるタスク量にしておいた方が良いです。 エンジニアは不具合の修正対応など突発的なタスクが発生しがちです。 こういった事態も吸収できるようにバッファを持たせておくことが重要です。 Work time: 20 hrs (80 %: 16 hrs) // 純粋に作業に充てられる時間だけを見積もる。 // また、突発的なタスクに対応できるよう、80%程度の時間でできることに収めた方が良い。 *** 新規配送システム導入 **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる :PROPERTIES: :Effort: 5:00 :END: **** TODO 出品者は新規配送方法に利用できる配送用ラベルを発行できる :PROPERTIES: :Effort: 8:00 :END: **** TODO 出品者は新規配送方法を利用して配送を実施することができる :PROPERTIES: :Effort: 3:00 :END: ここに追加したタスクの見積もりの合計が、作業時間内で収まっているかどうかを確認するには Column View を活用すると良いでしょう。 https://orgmode.org/manual/Column-View.html 親タスクに対して C-c C-x C-c とすると簡単に子タスクの見積もり時間の合計時間を表示することができます。 #+COLUMNS: %ITEM %Effort{:} // ITEM: タスク名, Effort: 見積もり時間 を表示 | *** 新規配送システム導入 | 16:00 | # 子タスクの合計時間が表示されている | **** 購入者は新規配送方法... | 5:00 | | **** 出品者は新規配送方法... | 8:00 | | **** 出品者は新規配送方法... | 3:00 | 1週間分のやることを週初めの段階で決めることで次のようなメリットがあります。 まずキャパオーバーになることを避けられます。 既に予定しているタスクで今週分の作業時間が埋まっているならば、 これ以上の仕事を受けて、早急に対応することは難しいとすぐに判断できます。 逆にこの段階で作業時間が余るようであれば、 新しいタスクを拾ったり、忙しい時にはできないような カイゼン 系のタスクなどに取り組もうと、 週初めの段階で決めてしまえば良いのです。 後者は暇になってから考えても同じじゃないの?とも思いますが、 無計画に暇を迎えるとただただ時間を潰すだけになりがち。人間とは弱い生き物なのです... こうして無理のない範囲に収めながらも、 自分の時間を最大限活用することが可能になっていきます。 (毎日朝)今日やることのリストを作成する 毎日業務を開始するタイミングで今日一日でやることのリストを daily/YYYYMMDD.org に作成します。 週初めの weekly/YYYMMDD.org の作成と同じように一日の作業時間を計算して、 その日に行えるだけのタスクを weekly/YYYYMMDD.org からこちらにコピーして追加します。 1週間分のやることリストの作成と同じように、 ミーティングの時間などは除いて、純粋に作業に充てられる時間に対してタスクを当てこみましょう。 Work time: 5 hrs *** 新規配送システム導入 **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる :PROPERTIES: :Effort: 5:00 :END: (業務時間中)作業時間を記録する 作業した時間は原則タスクに対して記録していきます。 Orgmode には Clocking commands というものが用意されています。 https://orgmode.org/manual/Clocking-commands.html 作業開始時には C-c C-x i 、終了時には C-c C-x o を入力することで、各タスクに対して作業時間の記録が行えます。 *** 新規配送システム導入 **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる :PROPERTIES: :Effort: 5:00 :END: :CLOCKING: CLOCK: [2023-12-04 月 09:30]--[2023-12-04 月 10:00] => 0:30 // 作業時間が記録される :END: また ポモドーロテクニック を使いながら作業時間を記録できる org-pomodoro という サードパーティ のライブラリもあります。 https://github.com/marcinkoziej/org-pomodoro こちらは作業開始時に C-c C-x p を実行すると、 あらかじめ指定した時間が経過したら自動的に作業終了となり時間が記録されます。 作業終了時にはチャイムが鳴ってお知らせもしてくれます。 作業時間中はタスクに全集中で取り組みましょう。X のタイムラインを見ながらダラダラと...なんてのはダメです(笑)。休憩する時は Clocking commands を停止してメリハリをつけましょう! 予定していた1日の作業時間を消化できたら、業務は一旦おしまいにします。おつかれさまでした! (業務終了時)1日の作業を振り返る 予定していた作業時間に対してどのくらい自分がコミットできたかは、前述した Column View で簡単に確認することができます。 #+COLUMNS: %ITEM %Effort{:} %CLOCKSUM // CLOCKSUM: 記録した時間の合計 を表示 | *** 新規配送システム導入 | 5:00 | 5:12 | | **** 購入者は新規配送方法... | 5:00 | 5:12 | 次に daily/YYYYMMDD.org に記録した作業時間を、 weekly/YYYYMNDD.org に転記していきます。 こうすることで1日を経る毎に1週間分の作業時間の記録が溜まっていきます。 *** 新規配送システム導入 **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる :PROPERTIES: :Effort: 5:00 :END: :CLOCKING: CLOCK: [2023-12-04 月 17:00]--[2023-12-04 月 18:12] => 1:12 CLOCK: [2023-12-04 月 13:00]--[2023-12-04 月 15:00] => 2:00 CLOCK: [2023-12-04 月 10:30]--[2023-12-04 月 12:00] => 1:30 CLOCK: [2023-12-04 月 09:30]--[2023-12-04 月 10:00] => 0:30 :END: **** TODO 出品者は新規配送方法に利用できる配送用ラベルを発行できる :PROPERTIES: :Effort: 8:00 :END: :CLOCKING: CLOCK: [2023-12-05 火 15:30]--[2023-12-05 火 18:00] => 2:30 CLOCK: [2023-12-05 火 10:00]--[2023-12-05 火 12:00] => 2:00 :END: このように日々、予定に対してどのくらい実際にコミットできたかを時間で確認できると、自分の仕事にかなりの達成感が出るのではないでしょうか?? モチベーションを維持することは、業務に対するパフォーマンスを最大化するためにはとても重要です。 (毎週金曜日)1週間の作業を振り返る 毎日の振り返りのタイミングで weekly/YYYYMMDD.org に作業時間の記録をしています。 なので週末の業務終了時点では1週間分の作業時間の合計が記録されているはずです。 大元の TODO リストとなっている TODO.org に1週間の作業結果を反映していきましょう。 作業時間の記録を転記して、もし完了になったタスクがあればステータスを変更します。 #+SEQ_TODO: TODO(t/!) | DONE(d) #+PROPERTY: Effort_ALL 1:00 2:00 3:00 5:00 8:00 13:00 *** 新規配送システム導入 **** DONE 購入者は新規配送方法を指定して商品購入を行うことができる :PROPERTIES: :Effort: 5:00 :END: :CLOCKING: CLOCK: [2023-12-04 月 17:00]--[2023-12-04 月 18:12] => 1:12 CLOCK: [2023-12-04 月 13:00]--[2023-12-04 月 15:00] => 2:00 CLOCK: [2023-12-04 月 10:30]--[2023-12-04 月 12:00] => 1:30 CLOCK: [2023-12-04 月 09:30]--[2023-12-04 月 10:00] => 0:30 :END: **** DONE 出品者は新規配送方法に利用できる配送用ラベルを発行できる :PROPERTIES: :Effort: 8:00 :END: :CLOCKING: CLOCK: [2023-12-07 木 09:00]--[2023-12-07 木 10:00] => 0:30 CLOCK: [2023-12-06 水 16:00]--[2023-12-06 水 18:00] => 2:00 CLOCK: [2023-12-06 水 11:00]--[2023-12-06 水 12:00] => 1:00 CLOCK: [2023-12-05 火 15:30]--[2023-12-05 火 18:00] => 2:30 CLOCK: [2023-12-05 火 10:00]--[2023-12-05 火 12:00] => 2:00 :END: **** TODO 出品者は新規配送方法を利用して配送を実施することができる :PROPERTIES: :Effort: 13:00 :END: :CLOCKING: CLOCK: [2023-12-08 金 17:00]--[2023-12-08 金 18:00] => 1:00 CLOCK: [2023-12-08 金 11:00]--[2023-12-08 金 12:00] => 0:30 CLOCK: [2023-12-07 木 13:00]--[2023-12-07 木 14:30] => 1:30 :END: DONE になったタスクをそのまま TODO.org においておくと、 ファイルがどんどん巨大になってしまいます。Orgmode にはこれを Archive として他ファイルに移動してくれる機能がありますのでこちらを利用しましょう。 https://orgmode.org/guide/Archiving.html タスクに対して C-c C-x s を実行することで、あらかじめ指定したファイルにタスクを移動させることができます。 #+ARCHIVE: ~/dev/orgmode/archives/myArchive.org:: # Archive 先の指定。 *** 新規配送システム導入 **** DONE 購入者は新規配送方法を指定して商品購入を行うことができる まとめ 以上が Emacs Orgmode を利用したタスク管理の紹介になります。 冒頭にも話したことになりますがこのタスク管理を行うことで、 仕事の抜け・漏れを起こさなくなる。 自分のキャパを最大限活かせる。(キャパオーバーになるのも避けられる。) 日々の仕事に達成感を得られる。 こういった恩恵を受けることができると私は感じています。 本記事を読んで気になった方は一度試してみてはいかかでしょうか?? 明日の記事の担当は UXD(UI, UX の改善に主に取り組んでいるチーム)の新井さんです。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは!UIUXデザイナーの和田です。 こちらは Enigmo Advent Calendar 2023 の12日目の記事です。 この記事では、 BUYMA のサービスやアプリ・WebサイトのUIUXをより良くすることを目的に進めているUXリサーチ(ユーザ調査)についてご紹介します。 1.なぜUXリサーチ(ユーザ調査)をするのか? より多くの方に BUYMA のファンになっていただけるように【真にユーザの体験価値にマッチしたお買い物体験を実現する】ためには、より深い顧客理解に基づいた PDCA が必要だと考えています。 そのために、現在 BUYMA をご利用いただいているお客さまの利用文脈・背景(ユー ザコン テキスト)を深掘りして、ユーザ インサイト についての理解を深められるような調査を心がけています。 「どのようなお客さまなのか?」 → ユーザ属性に加えて、家族構成や生活スタイルなど 「お客さまがどのような価値観・嗜好をお持ちなのか?」 → ファッションアイテム全般についての価値観・潜在ニーズや行動特性 「どのようなことを BUYMA に期待してくださっているのか?」 →  BUYMA に対するニーズや期待値 「 BUYMA について期待と現状のギャップはどのようなところにあるか?」 →  BUYMA 利用における AS-IS(現状)とTO-BE(理想)のギャップ 上記のような点について、 定量 的・定性的それぞれの側面から現状把握と改善のヒントを得られるように、 ユーザアンケートとユーザインタビューを組み合わせて調査を実施しています。 2. BUYMA のUXリサーチ体制 弊社では、UIUXデザイナーとデータアナリストの調査チームでUXリサーチを企画・進行しています。 またテーマによって、他部署のメンバーと連携・協働することで調査に幅広い視点が盛り込めるように工夫を進めています。 複数の職種からなるチームで調査に取り組むことで、UXリサーチの企画→実施→分析→施策化→デザイン・実装→効果検証・・・のように調査メンバーが主体的に PDCA を回しやすい体制ができていると感じています。(UXリサーチ専任ではないため、各自がその他の業務を調整しながらUXリサーチを進行するため、調査期間中はどうしても忙しくなってしまうこともあります。) 3.調査テーマに沿ったUXリサーチ企画・進行のながれ 弊社では、設定した調査テーマに沿ってユーザ調査を実施しています。 1回のユーザ調査につき、約3〜4ヶ月で企画から分析までが完結するようなスケジュール感で予定を立てて進めています。 以下、調査の流れをご紹介します。 1.企画・準備(1.5ヶ月) 調査テーマは、社内で相談して関心の高いテーマを設定するようにしています。 テーマに沿って対象者の選定や、 ヒアリ ングポイントを整理して、 アンケート設問やインタビューシナリオについて、レビューを繰り返して準備を進めています。 2.アンケート・インタビュー実施(1ヶ月) アンケートはテーマや対象者に合わせて、メールのほか、LINEやアプリからも回答いただけるようにしています。 アンケート設問の中でインタビューにご協力いただける方を募る形で、インタビューにつなげられるようにしています。 アンケート回答期間が約1週間、インタビューセッティング期間が1週間、インタビュー実施期間が約2週間と、とにかく忙しい1ヶ月間です。 3.分析・まとめ(1.5ヶ月) インタビューの実施が完了した後に、分析とまとめ作業をしています。 アンケートとインタビューそれぞれについて、分析結果・ ヒアリ ング結果を記事にまとめて社内で共有するようにしています。 最後にチームで KPT の形式で調査のふりかえり会を実施して、調査の1サイクルが完了となります。 4.UXリサーチに利用しているユーザセグメントの分類軸 UXリサーチでは、ユーザや利用傾向を把握・グルーピングをしやすくするために、独自のユーザセグメント分類軸を定義して利用しています。 アンケート配信対象者を選定する際にも、 調査テーマに沿って「直近利用(R)・利用頻度(F)・価格志向(M)・年代(U)」の分類軸を選定条件に利用しています。 直近利用(R) 最終購入日時(直近購入)または会員登録日時を基準にした直近利用に関する軸 利用頻度(F) 最終購入から遡った1年間における購入頻度の軸 価格志向(M) 生涯購入における最高購入金額の軸 ※どんな価格帯のアイテムに関心の高いユーザなのか分類するために生涯購入金額ではなく最高購入金額で分類しています 年代(U) ライフスタイル変化に合わせた年代グループ ※ご興味のある方は、ぜひ昨年のAdvent Calendar 2022の記事もご覧ください! tech.enigmo.co.jp 5.傾向・改善ヒントを掴むためのユーザアンケート 前述した通り、RFMUのユーザセグメント分類軸を利用して、調査テーマに沿ったアンケート配信対象者を選定してアンケート配信をしています。 アンケートの設問は以下のような形式の設問を利用しながら、 定量 的な傾向、定性的なご意見をキャッチアップできるような設問体系にしています。 アンケートツールは、 Google フォームを利用しています。 単一選択形式/複数選択形式の設問 仮説から選択肢を用意できるような場合は選択形式を利用して 定量 的に傾向を掴めるようにしています。 フリーアンサー(自由記述)設問 選択肢に挙げられていなかったユーザ インサイト を掴むために、積極的にフリーアンサーを用いてユーザのリアルな声をいただけるようにしています。 仮説として全く挙げられていなかったご意見を多数いただくこともあり、フリーアンサー回答の集計も非常に重要だと考えています。 段階評価の設問 尺度を測りたい場合に段階評価を用いています。 アンケート回答は、回答全体の分析に加えて、セグメント別の分析や回答を掛け合わせたクロス集計による傾向分析も実施しています。 6.ユーザ インサイト を深掘るためのユーザインタビュー アンケート集計後は、回答者のユーザセグメント傾向を分析して、インタビューしたいユーザグループをいくつかに分類するようにしています。 それぞれのユーザグループについて数名ずつインタビューを実施できるように、アンケート回答や BUYMA のご利用傾向をもとに候補者の方にお声がけをさせていただくようにしています。 弊社ではZoomを利用した60分のオンラインインタビューを実施しています。 オンラインでご参加いただけるため、地方にお住まいの方やテレワークで休憩中の方、お仕事先からご参加いただける方など・・・、本当にさまざまなご職業・ライフスタイルの方にご参加いただけています! インタビューの内容は主に以下のセクションごとに進行しています。 また、後述するユーザプロフィールシートに基づいて注力したい ヒアリ ングポイントを中心にお話をお聞きするようにしています。 ユーザーインタビューの内容 ユーザ自身について 日中の過ごし方やファッション情報のチェック先などをお聞きしています。 ファッション全般の嗜好やお買い物について 好きなブランドや普段のお買い物の傾向についてお聞きしています。 BUYMA 利用について BUYMA 認知・利用のきっかけやご利用の体験談などをお聞きしています。 調査テーマ ヒアリ ング 調査テーマに沿った内容について、価値観やニーズ、普段の行動、 BUYMA におけるペイン・ゲインなどをお聞きしています。 コンセプト ヒアリ ング Figma のプロトタイプを利用して新機能やUIのコンセプトについての ヒアリ ングを実施しています。 7.ユーザープロフィールシートの作成と活用 インタビュー対象者ごとにユーザの情報がまとまったユーザプロフィールシートを作成するようにしています。(個人が特定されるような情報は含まれていません。) また、ユーザプロフィールシートに対象者のRFMUユーザセグメント分類軸も付与しておくことで、近しい傾向を持つユーザを探しやすくする工夫をしています。 ユーザプロフィールシートの内容 ユーザ基本情報:ユーザ属性やRFMU分類軸 BUYMA 利用情報: BUYMA における利用・購入に関する情報 アンケート回答:アンケートのご回答まとめ インタビューレポート:インタビュー議事録・記録など ユーザインタビューの前に、あらかじめユーザプロフィールシートを作成することで、 ヒアリ ングポイントを精査して対象者の方に合わせたインタビューができるようにしています。 また、ユーザ インサイト について深掘りしたくなった際に、対象となるユーザプロフィールシートを探して活用できるように、 UXリサーチ資産としての整備を進行しています。 8.UXリサーチから得るもの 調査が完了した後、チームメンバーで分析結果・まとめをもとに施策ブレストを実施しています。 そこから、サービス・UIUX改善につながる施策化を進めるかたちで調査を役立てるようにしています。 実際に、UXリサーチで得られた インサイト から生まれた機能やサービスも BUYMA にどんどん盛り込まれています! さらなる BUYMA のUX向上に期待していただけますと幸いです! 明日の記事の担当は・・・ 【 BUYMA のサーバーサイドアプリケーション開発をされている寺田さん】です! お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、今年の4月に新卒で入社したデータエンジニアの中村です。 この記事は Enigmo Advent Calendar 2023 の11日目の記事です。 入社してからは、社内のデータを利用者がより使いやすくなるように、データ基盤の整備・データ連携を進めております。 最近弊社のデータ基盤をTerraformによる管理に移行するタスクに取り組んでいるので、どのような ディレクト リ構成を採用したか説明していこうと思います。 データ基盤をこれからTerraform化しようとしている方に有益な情報となれば幸いです。 目次 背景 ディレクトリ構成 構成の説明 各ディレクトリの役割 stateファイルの分割粒度 工夫したポイント 環境差分の切り出し BigQueryのリソースはyamlで管理 今後の展望 最後に 背景 弊社のデータ基盤は GCP 上にあり、BigQueryをはじめとした様々なサービスを活用してデータを収集しています。 GCP のサービスを利用する際に、これまではコンソール画面からボタンをポチポチしてリソースを作成していたのですが、それだと以下のような課題があります。 作業ミスによって誤ったリソースが作成されてしまう。 GUI での設定だと再現性が低くなってしまう。 リソースの管理が大変。(このリソース消しちゃって大丈夫だっけ?いつ誰が作ったんだ??みたいな状態が起こる。) 同じような設定のリソースを繰り返し作成するのに効率が悪い。 これらの課題を解決するため、IaCサービスであるTerraformを活用し、 GCP 上のサービスをコードベースでの管理に移行することに決めました。 ディレクト リ構成 結論から行くと、色々試行錯誤した末に決定した弊社データ基盤の ディレクト リ構成のベストプ ラク ティスはこちらになります。 terraform ├── root (ルートモジュール) │ ├── bigquery │ │ ├── environments │ │ │ ├── development │ │ │ │ ├── terraform.tfbackend (開発環境のstateファイルのパス) │ │ │ │ ├── terraform.tfvars (開発環境の変数) │ │ │ │ └── dataset │ │ │ │ ├── <dataset>.yaml (yamlでデータセット毎に管理) │ │ │ │ ・・・ │ │ │ └── production │ │ │ ├── terraform.tfbackend (本番環境のstateファイルのパス) │ │ │ ├── terraform.tfvars (本番環境の変数) │ │ │ └── dataset │ │ │ ├── <dataset>.yaml │ │ │   ・・・ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── versions.tf │ ├── datastream │ ├── <service_name> │ ・・・ └── modules(子モジュール) ├── bigquery ├── datastream ├── cloud_monitoring ├── secret_manager ├── <service_name> ・・・ 構成の説明 各 ディレクト リの役割 今回作成した構成は、大別するとroot ディレクト リとmodules ディレクト リに分かれており、各役割は以下の通りです。 root: modules ディレクト リで定義したモジュールを呼び出す modules: 再利用性の高いモジュールを配置 datastreamの例だと、以下のように、modulesで定義した複数のモジュールをrootから呼び出すことでリソースを定義しています。 module "secret_manager" { source = "../../../modules/secret_manager" ・・・ } module "monitoring" { source = "../../../modules/monitoring" ・・・ } module "data_stream" { source = "../../../modules/datastream/postgresql" ・・・ } stateファイルの分割粒度 この構成では、stateファイルはリソース毎に分割しています。 stateファイルの粒度が大きすぎると、その分リリース毎の影響範囲が広くなってしまいます。 現状そこまで管理対象が多くないので、1環境1stateファイルのようなモノリシックな構成でも良かったですが、今後スケールすることを想定して、管理が煩雑にならないようにリソース粒度で分割しました。 工夫したポイント ここまで聞くと、意外とスタンダードな構成だと感じた方も多いのではないでしょうか。 しかし、いくつか工夫した点があるのでそちらも説明していきたいと思います。 1. 環境差分の切り出し リソース毎に分割する構成の場合、よくあるのが以下の構成です。 ├── root │ ├── bigquery │ │ ├── development │ │ │ ├── main.tf │ │ │ ・・・ │ │ └── production │ │ ├── main.tf │ │ ・・・ │ ├── <service> │ │ ├── development │ │ └── production この構成だと、「developmentをコピペしてproductionを作成して、差分だけ修正して・・」と二度手間になってしまいます。 その無駄な作業を省くために、環境毎で中身の変わらないファイルは共有する構成にしました。 ├── root │ ├── bigquery │ │ ├── environments │ │ │ ├── development │ │ │ └── production │ │ ├── main.tf │ │ ├── variables.tf │ │ └── versions.tf この構成のメリットは、環境毎の設定値差分だけenvironments ディレクト リに記載すればその他共通のファイルを繰り返し作成する必要がないので、コーディング量が減らせるところです。 また、開発環境だけあるリソースを作成したいといった場合は、tfvarsとモジュール側でうまく吸収することで環境差分にも対応できます。 Workspaces の利用も考えましたが、公式では開発・本番環境毎に分けるような利用方法は非推奨だったので、今回は利用を見送りました。 2. BigQueryのリソースは yaml で管理 データ基盤の中でも中核をなすBigQueryの管理についてですが、一つのtfvarsで管理したいとなるとファイルが膨大になってしまします。 そこで、デー タセット 毎に yaml ファイルを作成することで見通しをよくしました。 本来tfvarsで全てのデー タセット を管理すると数千行は当たり前に行くところを、以下のようにデー タセット 毎に設定ファイルを分割することで管理が非常に楽になりました。 dataset_id : "hoge" description : "hogehoge" location : "US" access_roles_group_by_email : "hoge@hoge.co.jp" : [ "READER" ] access_roles_user_by_email : "hogehoge@hogehoge.co.jp" : [ "READER" ] Terraformでは、 yamldecode 関数が用意されているので、main.tf側で以下のように記述することでTerraformのmapとして扱うことができます。 locals { yaml_files = fileset("./environments/${var.env}/dataset", "*.yaml") datasets = { for file in local.yaml_files : element(split(".", basename(file)), 0) => yamldecode(file("./environments/${var.env}/dataset/${file}")) } } 今後の展望 まだTerraform化できていないサービスがあるので、引き続き完全Terraform化を目指していきたいと思います。 また、リリース作業は現在手動で行なっているので、CI/CD環境も整えて作業効率を高めていきたいところです。 最後に データ基盤のTerraform ディレクト リ構成に関する記事はネット上を探しても事例が少なく、ゼロから作るのは大変苦労しました。少しでも参考になった部分があれば幸いです。 最後までご覧いただきありがとうございました! 明日の記事の担当は、UIUXデザイナーの和田さんです。お楽しみに! 株式会社 エニグモ すべての求人一覧 hrmos.co
こんにちは、データサイエンティストの髙橋です。業務では企画/分析/ 機械学習 モデル作成/プロダクション向けの実装/効果検証を一貫して行っています。 この記事では 2023/11/01 に公開した AIでさがす 機能の実現にあたり工夫した点について紹介します。本機能では OpenAI の Chat Completions API ( Web 版の ChatGPT の API 版のようなもの)を活用しているため、同 API 関連での工夫が中心となります。 この記事は Enigmo Advent Calendar 2023 の 9 日目の記事です。 AIでさがす機能の紹介 素早い機能公開とコスト削減の工夫にフォーカスした理由 素早い機能公開のため工夫 おすすめ商品以外の質問には回答できなくする RAG は行わずシンプルに検索結果を返す仕組みとする API に渡す過去のメッセージの組み立てをアプリ側でやらないこととする コスト削減のための工夫 一部のメッセージ群を API 呼び出し時は除外する プロンプトに質問と回答の例を含めないこととする まとめ 追記:2024/03/13 AWS Eコマース x 生成AI イベントに登壇しました! 追記:2025/12/18 AI でさがす機能をリニューアルしました! AIでさがす機能の紹介 AIでさがす機能は BUYMA の Web サイト・アプリの検索機能の 1 つとしてボタンを追加しており、Web サイトの場合は以下のボタンを押下すると AIでさがす画面に遷移します。 ユーザーは事例から質問するか、アイテムのカテゴリを選択した上でフリーワードで質問するかのどちらかからチャットを始めることができます。事例は、アイテムのカテゴリとフリーワードの文章をあらかじめ弊社で作成したものです。 例えば、レディースアイテムカテゴリを選択した上で「30代におすすめの ハイブランド レディースバッグ教えてください」と入力してメッセージを送信すると、以下のようにいくつかの商品が表示されます。 上記画面において、商品画像を押下すると該当の商品詳細画面が開き、「もっと見る」を押下すると表示されているおすすめ(シャネル チェーンバッグ など)を検索キーワードとした検索画面が開きます。 さらに、表示された商品群の末尾に以下のように「選んだ理由を聞く」ボタンが表示されており、押下すると選んだ理由が表示されます。 理由も表示できるようにすることでユーザーに納得感を得てもらったり、理由からこういう商品も良いかもと思い検索してもらったりすることを狙っています、また、弊社では FISS Project というファッションのプロが一緒に服を選んでくれるサービスも運営しており、より要望に特化した商品を選んでほしいと思われた方々の同サービスへの 流入 増加も狙いの1つです。そのため、以下のように理由表示後に同サービスの紹介も表示されます(カテゴリとしてメンズアイテムを選択した場合のみ表示されます)。 素早い機能公開とコスト削減の工夫にフォーカスした理由 目次を見ていただくと分かるように素早い機能公開とコスト削減に関する工夫が中心です。この2つにフォーカスした理由は以下のとおりです。 AIでさがす機能は他社の類似事例が少なく、どの程度の効果が見込めるか予測が難しかった このような機能はどういったものであれば BUYMA の会員にとって有益であるのか予測が難しかった そこで、可能な限り低コストで素早く公開し 分析→改善 のサイクルを回すことにより、どのようなものが有益か、どの程度効果が出そうなのかを予測しやすくしようと考えました。 結果として、本格的に開発を開始してから約6ヶ月で公開することが出来ました。なお、この期間にはソフトウェアエンジニアの方によるチャット画面周りのフロント/バッグエンド開発も含んでいます。 また、具体的な数値は出せませんがコストもかなり抑えた状態で公開できました。 素早い機能公開のため工夫 まずは素早い機能公開のための工夫を3つ紹介します。 おすすめ商品以外の質問には回答できなくする 企画の初期段階では商品のおすすめ以外の質問にもある程度対応できるようなチャットボットにすることを考えていました。例えば、 BUYMA 利用時の QA でよくある 「関税がかかるのか?」 という質問に回答できるようにするなどです。しかし、それを実現しようとすると誤った情報を回答させないために BUYMA 公式の QA ガイド を embedding 化し、ユーザーの質問と類似した文書を抽出、その文書をプロンプトに組み込み回答させる、などが必要だろうと考えました。しかし、この作業自体に時間がかかる上に様々な質問に対して誤った情報を回答しないかを検証する時間もかかるため、思い切っておすすめの商品以外の質問には回答できないようにしました。 これを実現するために、ユーザーからのメッセージに対して Chat Completions API により回答を生成させる前に、メッセージがおすすめの商品に関するものかどうかを分類しそれ以外の質問の場合は以下のように固定したメッセージを返すようにしました。 なお、分類自体も Chat Completions API により行うこととしました。これは分類モデルを作成する手間を削減できると考え検証したところ、Chat Completions API による分類でも十分な精度が得られたためです。そのため、ユーザーのメッセージ 1 つに対して最大で 2 回 Chat Completions API を呼び出しています。 RAG は行わずシンプルに検索結果を返す仕組みとする RAG (Retrieval-augmented Generation) とは、外部データを ChatGPT などの大規模 言語モデル に与えることでそのデータに基づいた回答を生成させる手法です。 記事執筆時点(2023/12/09)で採用している方法はシンプルで、 ChatGPT におすすめ商品をいくつか生成してもらい、それぞれを検索キーワードとした検索結果内の複数個の商品 URL と検索結果の URL を表示するというものです。 企画初期段階では RAG のようなやり方(例えば商品情報を embedding 化し、回答の embedding と類似する商品を取得して画面に表示するなど)を検証していたのですが、以下の課題から RAG の方向性を突き詰めると時間がかかりそうなことを実感し、シンプルな方法を採用することとしました。embedding 化には OpenAI の text-embedding-ada-002 を利用しました。 商品の メタデータ (商品名やカテゴリ、ブランド名など)を embedding 化したものと、ChatGPT の回答を embedding 化したものの類似度を取って上位の商品を見ると、回答とずれた商品がそれなりに出現する。 例えば、ChatGPT の回答では「オックスフォードシャツ」と記載があるが、類似度上位にはTシャツが出現することがありました。 上記のような問題があった場合に原因を特定することが難しい。 商品名やカテゴリ、ブランド名などの複数の商品 メタデータ を embedding 化しているため、どの メタデータ が原因なのか、特定の メタデータ の組み合わせが原因なのかなどを特定することが難しく、また特定できたとしても今度は別のパターンで問題が発生することがありました。これを対応していると、この部分の開発のみでそれなりの時間を要してしまうと考えました。 また、仮に embedding 化がうまくいったとしても、それをシステム化することにも時間がかかるだろうと考えました。具体的には、 BUYMA には 600 万品以上の商品があり、この数の embedding と類似度を高速に計算するには Vertex Matching Engine などの利用が必要であり、弊社ではまだ利用実績もなかったため検証・構築に時間を要すると考えました。その上、コストもそれなりにかかりそうであったためシンプルな方法を採用するに至りました。 なお、記事執筆時点(2023/12/09)では RAG を利用した回答生成を高速に実現できそうな機能が OpenAI の Assistants API や Google Cloud の Grounding for PaLM API としてアナウンスされており、今後はユーザーの利用状況を見ながらこれらの活用も検討予定です。 API に渡す過去のメッセージの組み立てをアプリ側でやらないこととする これは弊社のソフトウェアエンジニアとの連携にかかる時間を削減するための取り組みです。Chat Completions API では過去のユーザーと AI のメッセージのやり取りを API の呼び出し側で組み立てる仕様になっています。具体的には、 Open AI のドキュメント に記載のように API 呼び出し時の messages パラメータに過去のユーザーと AI のやり取りを渡す必要があります。 curl https://api.openai.com/v1/chat/completions \ -H " Content-Type: application/json " \ -H " Authorization: Bearer $OPENAI_API_KEY " \ -d ' { "model": "gpt-3.5-turbo", "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Who won the world series in 2020?" }, { "role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020." }, { "role": "user", "content": "Where was it played?" } ] } ' これを実現するための単純な方法は、アプリ側で画面に表示しているユーザーと AI のメッセージを取得し、 API 呼び出し時に渡すようソフトウェアエンジニアの方に依頼することです。しかし、そうすると過去のメッセージ群の一部を意図的に変更したい場合に、こちらの意図しないメッセージ群が渡されるるなどしてソフトウェアエンジニアの方との連携に時間がかかってしまうだろうと想定しました。 そこで、Chat Completions API をラップする独自 API を作成し、その API のレスポンスとして次の呼び出し時に渡すべきメッセージ群を返す仕様としました。具体的には、先程の Open AI のドキュメントの例を用いると Who won the world series in 2020? というメッセージで呼び出したときのレスポンスに、以下のように次の呼び出し時に渡すべきメッセージ群を含め、 { " past_messages ": [ { " role ": " user ", " content ": " Who won the world series in 2020? " } , { " role ": " assistant ", " content ": " The Los Angeles Dodgers won the World Series in 2020. " } ] } 次の呼び出し時は以下のようにユーザーのメッセージと過去のメッセージ群をパラメータとして渡す仕様の独自 API を作成しました。 { " message ": " Where was it played? ", " past_messages ": [ { " role ": " user ", " content ": " Who won the world series in 2020? " } , { " role ": " assistant ", " content ": " The Los Angeles Dodgers won the World Series in 2020. " } ] } この独自 API を作成したことでこちらの意図しないメッセージ群が渡されてしまうことを防ぐことが出来ました。また、後述しますが過去のメッセージ群の一部を意図的に変更することを行っており、それを独自 API のレイヤーで行うことでソフトウェアエンジニアの方に意図的な変更の実装を依頼しこちらで確認するなどの連携が不要となり、開発期間の短縮に繋がりました。 コスト削減のための工夫 次にコスト削減のための工夫を 2 つ紹介します。ここで紹介するものは 1 回の Chat Completions API 呼び出し単位で見れば小さいものですが、 BUYMA の会員数は 1000 万人以上 であり 1 回の API 呼び出しが全体のコストに与える影響は大きいものとなります。どの程度効果が出るのか予測が難しく、また企画当初から全会員への公開を目指していたため少しのコスト削減でも検討することとしました。 一部のメッセージ群を API 呼び出し時は除外する Chat Completions API の料金は input tokens による従量課金性 であり、選んだ理由の回答が長文になりがちであるためそれに関するメッセージ群を API 呼び出し時は除外するようにしました。 例えば、ユーザーが「30代におすすめの ハイブランド レディースバッグ教えてください」というメッセージを送り、「シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ」というメッセージが返された後に、「選んだ理由を教えて」とメッセージを送ると以下のようなメッセージが返されます。 シャネル チェーンバッグ シャネルのチェーンバッグは、エレガントで上品なデザインが特徴です。高級感溢れる素材とクラシックなチェーンストラップが、大人の女性にふさわしい印象を与えます。また、シャネルのバッグはブランドの人気アイテムであり、長く愛用することができるため、コストパフォーマンスも高いです。 グッチ トートバッグ グッチのトートバッグは、洗練されたデザインと高品質な素材が魅力です。ブランドのアイコンであるGG柄やロゴプリントが施されたデザインは、一目でグッチと分かる存在感を持っています。また、トートバッグは容量も大きく使い勝手が良いため、日常使いから旅行まで幅広く活躍します。 プラダ ショルダーバッグ プラダ のショルダーバッグは、モダンで洗練されたデザインが特徴です。シンプルながらも上品さを兼ね備えたデザインは、幅広いスタイルに合わせやすく、長く愛用することができます。また、 プラダ のバッグは高品質な素材と丁寧な作りが評価されており、耐久性も抜群です。 このメッセージの token 数は約 448 であり、以降の Chat Completions API 呼び出し毎に約 $0.00067 のコスト *1 がかかることとなります。先程述べたように BUYMA の会員数は 1000 万人以上のため 1 呼び出しあたり約 $0.00067 のコスト増加でも大きな影響となります。そこで、過去の選んだ理由に関するメッセージ群を API 呼び出し時に除外することとしました。 具体例として、ユーザーが以下の順番でメッセージを送ったとし 30代におすすめの ハイブランド レディースバッグ教えてください 選んだ理由を教えて 小旅行で使えるおすすめレディースバッグは? この時のメッセージ群が以下のようになったとします。 { " messages ": [ { " role ": " user ", " content ": " 30代におすすめのハイブランドレディースバッグ教えてください " } , { " role ": " assistant ", " content ": " シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ " } , { " role ": " user ", " content ": " 選んだ理由を教えて " } , { " role ": " assistant ", " content ": " シャネルのチェーンバッグは、... / グッチのトートバッグは、... / プラダのショルダーバッグは、... " } , { " role ": " user ", " content ": " 小旅行で使えるおすすめレディースバッグは? " } , { " role ": " assistant ", " content ": " トートバッグ / ショルダーバッグ / リュックサック " } ] } ' この時、次の API 呼び出し時に渡す過去のメッセージ群を以下のように理由に関するやり取りを除外したものとしました。 { " messages ": [ { " role ": " user ", " content ": " 30代におすすめのハイブランドレディースバッグ教えてください " } , { " role ": " assistant ", " content ": " シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ " } , { " role ": " user ", " content ": " 小旅行で使えるおすすめレディースバッグは? " } , { " role ": " assistant ", " content ": " トートバッグ / ショルダーバッグ / リュックサック " } ] } なお、上記の除外処理はアプリ側でも可能ですが、「 API に渡す過去のメッセージの組み立てをアプリ側でやらないこととする」にも記載したようにデータサイエンティストとソフトウェアエンジニア間の連携にかかる時間を削減するために、独自 API のレイヤーで行う仕様としました。具体的には、「選んだ理由を教えて」というメッセージで独自 API を呼び出した時のレスポンスは、以下のように理由に関するメッセージ群を除外したものになる仕様としました。 { " past_messages ": [ { " role ": " user ", " content ": " 30代におすすめのハイブランドレディースバッグ教えてください " } , { " role ": " assistant ", " content ": " シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ " } , ] } そして、次に「小旅行で使えるおすすめレディースバッグは?」というメッセージで独自 API を呼び出す時は、上記の理由に関するメッセージ群が除外された過去のメッセージ群をそのまま渡す仕様としました。 { " message ": " 小旅行で使えるおすすめレディースバッグは? ", " past_messages ": [ { " role ": " user ", " content ": " 30代におすすめのハイブランドレディースバッグ教えてください " } , { " role ": " assistant ", " content ": " シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ " } , ] } プロンプトに質問と回答の例を含めないこととする この機能ではユーザーからのメッセージに対して弊社システム側でプロンプトを追加し、Chat Completions API にリクエストを送信しています。そのプロンプトについて、開発初期は以下のようにユーザからのメッセージと AI からのメッセージの例をいくつか含めることでこちらの意図したフォーマットで回答を生成させ、複数のおすすめを分割していました(以下のプロンプトは実際に利用しているものとは異なります)。 「おすすめ1 / おすすめ2 / おすすめ3」というフォーマットで回答してください。 以下が質問と回答の例です。 質問:30代におすすめのハイブランドレディースバッグ教えてください 回答:シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ 質問:小旅行で使えるおすすめレディースバッグは? 回答:トートバッグ / ショルダーバッグ / リュックサック しかし、ある程度開発が進んだ時点でコストを見積もったところ、予想以上にコストがかかる結果となりました。そこで、上記のプロンプトの 以下が質問と回答の例です。 以降を除外することを検討しました。 以下が質問と回答の例です。 以降の token 数は約 140 であり、 Chat Completions API 呼び出し毎に約 $0.00021 のコストがかかることとなります。先程述べた通り BUYMA の会員数は 1000 万人以上のため 1 呼び出しあたり約 $0.00021 のコスト削減でも見積もり上のコスト削減は大きなものでした。 一方で、実際に上記のプロンプトの 以下が質問と回答の例です。 以降を除外し、様々なユーザーのメッセージパターンで検証してみたところ、おすすめされるものにはほとんど変化がないもののフォーマットにブレが生じ、おすすめがうまく分割出来ないことがありました(半角の / が全角の / になるなど)。 そこで、うまく抽出できなかったパターンに対し 正規表現 などの後処理を利用して対応しました。 結果として、公開から1ヶ月程度が経過した時点でフォーマットのブレによりおすすめが抽出出来ない問題は1件も発生しておらず、コストを削減しつつ安定して回答を生成することが出来ています。 まとめ 先日公開した AIでさがす機能実現のための工夫を紹介しました。特に素早い機能公開とコスト削減にフォーカスすることで安価に早く公開することが出来ました。今後は、利用状況を分析し RAG などにより BUYMA データとの連携を強化したり、Fine-tuning などによりさらにユーザーにとって有益な回答を生成させたりなどを検討予定です。 追記:2024/03/13 AWS Eコマース x 生成AI イベントに登壇しました! AWS の招待制のイベントにてこのブログの内容を発表いたしました。 https://x.com/enigmo_tech/status/1776132049579262461 ブログには記載していないプロジェクトの進め方や体制についても記載しております。 speakerdeck.com \イベント登壇しました💁‍♂️/ 弊社データサイエンティストが AWS 社主催のEコマース×生成AIイベントに登壇し、 生成AIの ユースケース として、 BUYMA の商品検索の新機能「AIでさがす」を紹介しました✨ pic.twitter.com/aRGnxI9Psx — EnigmoTech (@enigmo_tech) 2024年4月5日 追記:2025/12/18 AI でさがす機能をリニューアルしました! より良い検索体験提供のために、AIテクノロ ジー グループの吉田さんに機能をリニューアルいただきました。以下記事に内容が記載されていますため、ぜひご覧ください! 株式会社 エニグモ すべての求人一覧 hrmos.co *1 : 当機能の作成期間(2023/05 ~ 2023/11)では gpt-3.5-turbo の 1K tokens あたりの料金は $0.0015 であったためその数値を用いて計算しました。