一休.com Developers Blog

一休のエンジニア、デザイナー、ディレクターが情報を発信していきます

Design Doc でチームを跨いだ開発を円滑に行う

この記事は 一休.com Advent Calendar 2024 7 日目の記事です。

宿泊事業本部 ユーザー向け開発チームの原です。 一休.com と Yahoo!トラベルの主にフロントエンドの開発を担当しています。

今回は、普段の開発でコードを書き始める前段階で Design Doc を作ることで、円滑な開発を進められるようになったというお話をします。

チーム構成について

まず、前提を共有するために私達が普段どのような体制で開発しているかを説明します。
私が所属している宿泊事業本部 ユーザー向け開発チームは、一休.com と Yahoo!トラベルの主に toC のユーザー向けの機能開発をしています。ユーザー向け開発チームのメインのミッションはユーザー体験を向上させることであり、そういった施策の機能開発を素早くリリースできることを大事にしています。
一方、プロダクト開発においては機能開発だけではなく、プログラミング言語や依存ライブラリのアップデートや、アーキテクチャの見直しといったシステムの健全性を向上させる取り組みも重要です。
機能開発とシステム改善を同じチームが両立して行えることが理想的かもしれません。しかし、Nuxt でできたフロントエンドのアプリケーションに関しては、施策に関する機能開発はユーザー向け開発チームが、システム改善はフロントエンド改善チームという専任のチームが担当しています。
これは、変化の激しいフロントエンド開発でベストプラクティスを追い求めるには施策開発とシステム改善をする責務を分けたほうが進めやすいという判断によるものです。
実際、フロントエンド改善チームの取り組みにより、

  • Nuxt2 から 3 へのアップデート
  • Options API から Composition API への書き換え

といった Vue/Nuxt 界隈の進化に追従したり、

  • GraphQL の client-preset の導入
  • デザインシステムの推進

なども機能開発を止めずに完了しています。こういった取り組みにより、かなり開発者体験がいい環境で日々機能開発ができています。

少し古いエントリーですが、フロントエンド改善チームの取り組みは以下でご確認できます。 user-first.ikyu.co.jp

開発チームと改善チームが分かれている状態においては、うまくコミュニケーションを取らないと問題が生じます。
お互いどんな取り組みをするのか共有しないと、

  • 開発チームの施策で触るコードと、改善チームのリファクタリングしたいコードがコンフリクトする
  • 改善チームが行ったリアーキテクトを開発チームがちゃんと理解しないとベストプラクティスではない実装をしてしまう

といったことが起こり得ます。
特に「ベストプラクティスではない実装をしてしまう」というのは避けたい問題です。
そのため、開発チームが実装した機能は小さな修正を除いては基本的に Pull Request (以下 PR) でレビューしてもらうことになっています。
実際レビューの際に、最適な実装にたどり着くまで時間がかかってしまったということが何度かありました。

前置きが長くなりましたが、こうした別のチームにコードレビューを依頼するとき、円滑な開発を進めるために私が必要だと思っていることを紹介します。

コードレビューについて

私はレビュアーとしてコードをレビューするのは非常に労力のかかる仕事だと思っています。
よく「実装が終わって PR を出したので、もう少しで完了します」みたいなことを言ってしまいがちですが、コードレビューは実装と同等か、場合によってはそれ以上のコストが発生しうる作業だと思っています。
というのも、Approve されるとリリースできるという運用においては、レビュアーの仕事はコード書く人(レビュイー)と同等の責任が発生するためです。
いきなり数百行、数千行規模の差分が発生する修正をレビューするときには

  • その施策や修正の背景
  • 実現するための最適な設計になっているか
  • その diff を取り込むことでどんな影響が起こり得るか

などを考える必要がありますが、それらを一から考えるのは、コードを最初から書くのと同じくらいのコストがかかるものです。
上記のような考慮はコードを書く側(レビュイー)は当然考えたうえで実装しているはずなので、レビュアーにうまく伝えられることができるとレビュアーのコストを軽減できます。
どういった工夫でレビュアーのコストを軽減しようとしているかを紹介します。

いきなりコードを書かない

先程も述べたような差分が数百行、数千行規模の PR をいきなりレビューしてもらうのは、PR の description やコメントをいくら丁寧に書いたとしても、レビュアーの負担は大きいです。
そこで実装に入る前の段階で Design Doc を作成して、大筋の実装内容について合意を取るようにしています。
Design Doc は以下のようなアウトラインで書いています。

## このドキュメントの目的
## やりたいこと
// ここではビジネス的な視点でなぜこの施策をするのかを書きます
## 仕様
// ここでは上記のやりたいことを満たす機能要件を書きます
## 対応内容
// ここではシステム的な視点でどんな対応が必要なのかを書きます

このドキュメントの目的、やりたいことが記載された Design Doc のスクショ

Design Doc の目的は、実装者とレビュアーの間で大まかな実装の合意をとることです。

新規ページ作成を例にすると

  • URL をどう命名するか
  • コンポーネントの階層と、各コンポーネントをどう命名するか
  • サーバー(GraphQL)からデータをどのように取得するか
  • 機能要件を満たすロジックをどう実装するか
    • 既存のロジックで使えるものは何か

などを Design Doc で決定します。
特に命名は先に決めておくと実装、レビューともに楽です。

既存のロジックを使えるというアドバイスがもらえる

(↑ 既存のロジックを使えるというアドバイスがもらえる)

Design Doc で事前に実装方針の合意をとることで、「なぜこのような設計にしたのか」をレビュアーがレビュー時に考える必要がなくなります。 また、レビューする段階で大まかな実装イメージがついているので、レビューの負担が軽減されると考えています。

Pull Request を出す際に気をつけていること

Design Doc との乖離がある場合

Design Doc で実装方針の合意をとれたら、実装をして、完了したらレビューに出します。
当然、実装する中で Design Doc で決めた通りにいかなかったり、もっといい方法が見つかったりすることもあるでしょう。
それを何も共有せずレビューに出してしまうとせっかく実装方針を決めた意義が薄れてしまいます。
Design Doc 時の決定と大きく変わる場合は、レビューを出す前に Design Doc 自体を修正して、もう一度合意を取り直すようにしています。
Pull Request の Description やコメントにその旨を書くだけで伝わるような些細な変更の場合は、レビュー段階でそれを伝えるようにします。

レビュアーの負担を最小限に

当然ですが、レビューを依頼する前に自分で見つけられる粗は見つけておくべきなので、自分がレビュアーのつもりでセルフレビューをします。
施策とは直接は関係ないリファクタリングなど、レビュアーが「これはなぜいま修正が必要なのか?」と疑問を持ちそうな箇所はコメントを残しておきます。

  • 動作確認方法
  • 影響する既存機能が元通り動いていることをどうテストしたのか

といった情報も記載します。
また、実装していてもっと良い書き方があるはずだが思いつかなかったような場合、どんなことを試してうまく行かなったということを残しておくとよいでしょう。

最後に

今回はチーム間を跨いだレビューで私が気をつけていることを紹介しました。
常にペアプロ・モブプロを行っていたり、チームの成熟度が高い場合は Design Doc を作成することの必要性は薄いかもしれません。
ただ、実装タイミングでどんな意思決定がなされたのかという情報は、時間が経った後から見返す際、有用になります。
また、

  • レビューのコストは実装と同じくらいのコストになり得る
  • レビュアーの負担はレビュイーの工夫次第で軽減できる

というのはどこでも共通する話だと思います。

永続データプログラミングと永続データ構造

この記事は 一休.com Advent Calendar 2024 の3日目の記事です。

昨今は我々一休のような予約システム開発においても、関数型プログラミング由来のプラクティスを取り入れる機会が増えています。

例えば、値はイミュータブルである方が扱いやすい、関数は副作用のない純粋関数にする方がテスタビリティなども含め何かと都合がよい、そういう場面では積極的に不変な値を使い、関数が冪等になるよう意識的に実装します。ドメインロジックを純粋関数として記述できると、堅牢で責務分離もしやすく、テストやデバッグもしやすいシステムになっていきます。

ところで「関数型プログラミングとはなんぞや」というのに明確な定義はないそうです。ですが突き詰めていくと、計算をなるべく「文」ではなく「式」で宣言することが一つの目標だということに気がつきます。

文と式の違いは何でしょうか?

for 文、代入文、if 文などの文は、基本的には値を返しません。値を返さないということは、文は直接結果を受け取るものではなく、命令になっていると言えます。文は計算機への命令です。

一方の式は、必ず返値を伴いますから、その主な目的は返値を得る、つまり式を評価して計算の結果を得ることだと考えることができます。

customer.archive()

と、文によって暗黙的に customer オブジェクトの内部状態を変更するのではなく

const archivedCustomer = archiveCustomer(customer)

と、引数で与えられた customer オブジェクトを直接変更することなしに、アーカイブ状態に変更されたコピーとしての archivedCustomer オブジェクトを返値として返す、これが式です。この関数は純粋関数として実装し、customer オブジェクトは不変、つまりイミュータブルなものとして扱うと良いでしょう。

式によるイミュータブルなオブジェクトの更新は TypeScript なら

export const archiveCustomer = (customer: Customer): Customer => ({
  ...customer,
  archived: true
})

と、スプレッド構文を使うことで customer オブジェクトのコピーを作りつつ、変更したいプロパティを新たな値に設定したものを返すように実装します。

このように、引数で与えたオブジェクトは直接変更せず、状態を変更した別のオブジェクトを返すような関数の連なりによって計算を定義していくのが関数型プログラミングです。

このあたりの考え方については、過去の発表スライドがありますので参考にしてください。

実際、我々の一部プロダクトのバックエンドでは TypeScript による関数型スタイルでの開発を実践しています。以下はプロダクトのコードの一例で、Customer オブジェクトに新しいメールアドレスの値を追加するための addEmail 関数です。先の実装に同じく、スプレッド構文を使って元のオブジェクトを破壊せずに、メールアドレスが追加されたオブジェクトを返します。

const addEmail =
  (address: EmailAddress) =>
  (customer: Customer): Customer => {
    const newAddress: CustomerEmail = {
      id: generateCustomerEmailId(),
      address,
    }
    return {
      ...customer,
      emails: [...customer.emails, newAddress],
    }
  }

ドメインオブジェクトの状態遷移はすべて、この式による状態遷移のモデルで実装しています。

永続データプログラミング

さて、本記事のテーマは「永続データ」です。永続データとは何でしょうか?

式を意識的に使い、かつ値をイミュータブルに扱うことを基本としてやっていくと、何気なく書いたプログラムの中に特徴的な様子が現れることになります。

以下、リスト操作のプログラムを見てみましょう。リストの先頭や末尾に値を追加したり、適当な値を削除する TypeScript のプログラムです。リストをイミュータブルに扱うべく、値の追加や削除などデータ構造の変更にはスプレッド構文を使い、非破壊的にそれを行うようにします。

// as1: 元のリスト
const as1 = [1, 2, 3, 4, 5];

// as2: 新しいリスト (先頭に 100 を追加)
const as2 = [100, ...as1];

// as3: 新しいリスト (末尾に 500 を追加)
const as3 = [...as2, 500];

// as4: 新しいリスト (値 3 を削除)
const as4 = as3.filter(x => x !== 3);

console.log("as1:", as1); // [1, 2, 3, 4, 5]
console.log("as2:", as2); // [100, 1, 2, 3, 4, 5]
console.log("as3:", as3); // [100, 1, 2, 3, 4, 5, 500]
console.log("as4:", as4); // [100, 1, 2, 4, 5, 500]

更新をしても元のリストは不変なので、as1 を参照しても更新済みの結果は得られません。リスト操作の返り値を as2 as3 as4 とその都度変数にキャプチャし、そのキャプチャした変数に対して次のリスト操作を行います。こうしてデータ構造は不変でありつつも一連の、連続したリスト操作を表現します。

データ構造を不変にした結果、リストが更新される過程の状態すべてが残りました。リストを何度か更新したにも関わらず、変更前の状態を参照することができています。as1 を参照すれば初期状態を、as2 や as3 で途中の状態を参照することができます。このように値の変更後もそれ以前の状態が残るさまを「永続データ」と呼びます。そして永続データを用いたプログラミングを「永続データプログラミング」と呼びます。

値をイミュータブルに扱うと必然的にそれは永続データになるので、永続データプログラミングはそれ自体、何か特別なテクニックというわけではありません。一方で、値が永続データであることをはっきりさせたい文脈上では「永続データプログラミング」という言葉でプログラミングスタイルを表現すると、その意図が明確になることも多いでしょう。

以下の山本和彦さんの記事では、関数型プログラミングすなわち「永続データプログラミング」であり、永続データを駆使して問題を解くことこそが関数型プログラミングだ、と述べられています。

筆者の関数プログラミングの定義、すなわちこの特集での定義は、「⁠永続データプログラミング」です。永続データとは、破壊できないデータ、つまり再代入できないデータのことです。そして、永続データを駆使して問題を解くのが永続データプログラミングです。

また関数型言語とは、永続データプログラミングを奨励し支援している言語のことです。関数型言語では、再代入の機能がないか、再代入の使用は限定されています。筆者の定義はかなり厳しいほうだと言えます。

第1章 関数プログラミングは難しくない!―初めて学ぶ人にも、挫折した人にもきちんとわかる | gihyo.jp

命令型プログラミングにおいては変更にあたり値を直接破壊的に変更します。変更前のデータ構造の状態を参照することはできません。リストの破壊的変更は、基本的に (式ではなく) 文によって行われるでしょう。文を主体としたプログラミング··· 命令型プログラミングでは、永続ではないデータ、つまり短命データを基本にしていると言えます。一方、式によってプログラムを構成する関数型プログラミングでは、関数の冪等性を確保すべくイミュータブルに値を扱うことになるので、永続データが基本になります。

イミュータブルな値によるプログラミングをする際、そこにある値は不変であるだけでなく、同時に永続データなのだということを認識できると、プログラミングスタイルに対するよりよいメンタルモデルが構築できると思います。

Haskell と永続データプログラミング

やや唐突ですが、イミュータブルといえば純粋関数型言語の Haskell です。先の TypeScript によるリスト操作のプログラムを、Haskell で実装してみます。

main :: IO ()
main = do
  let as1 = [1, 2, 3, 4, 5]
      as2 = 100 : as1
      as3 = as2 ++ [500]
      as4 = delete 3 as3

  print as1 -- [1,2,3,4,5] 
  print as2 -- [100,1,2,3,4,5]
  print as3 -- [100,1,2,3,4,5,500]
  print as4 -- [100,1,2,4,5,500]

Haskell はリストはもちろん、基本的に値がそもそもがイミュータブルです。リスト操作の API はすべて非破壊的になるよう実装されているので、変更にあたり TypeScript のようにスプレッド構文でデータを明示的にコピーしたりする必要はありません。裏を返せば、変更は永続データ的に表現せざるを得ず、式によってプログラムを構成することが必須となります。結果、Haskell による実装は自然と永続データプログラミングになります。

関数型プログラミングすなわち永続データプログラミングだ、というのは、この必然性から来ています。

永続データの特性を利用した問題解決

永続データプログラミングは不変な値を使うことですから、それを実践することで記事冒頭で挙げたようなプログラムの堅牢性など様々なメリットを享受できるわけですが、「変更前の過去の状態を参照できる」という、値が不変であるというよりは、まさに「永続」データの特性が部分が活きるケースがあります。

わかりやすい題材として、競技プログラミングの問題を例に挙げます。

atcoder.jp

問題文を読むのが面倒な方のために、これがどんな問題か簡単に解説します。入力の指示に従ってリストを更新しつつ、任意のタイミングでそのリストの現在の状態を保存する。また任意のタイミングで復元できるようするという、データ構造の保存と復元を題材にした問題です。

ADD 3
SAVE 1
ADD 4
SAVE 2
LOAD 1
DELETE
DELETE
LOAD 2
SAVE 1
LOAD 3
LOAD 1

こういうクエリが入力として与えられる。

  • 空のリストが最初にある
  • クエリを上から順番に解釈して、ADD 3 のときはリスト末尾に  3 を追加する
  • DELETE なら末尾の値を削除
  • SAVE 1 のときは、今使っているリストを ID 番号  1 の領域に保存、LOAD 1 なら ID 番号  1 の領域からリストを復元する
  • クエリのたび、その時点でのリストの末尾の要素を出力する

という問題になっています。

この問題を永続データなしで解こうとすると、リストを更新しても以前の状況に戻れるような木のデータ構造を自分で構築する必要がありなかなか面倒です。一方、永続データを前提にすると、何の苦労もなく解けてしまいます。

以下は Haskell で実装した例です。やっていることは、クエリの内容に合わせてリストに値を追加・削除、保存と復元のときは辞書 (IntMap) に、その時点のリストを格納しているだけです。問題文の通りにシミュレーションしているだけ、とも言えます。

main :: IO ()
main = do
  q <- readLn @Int
  qs <- map words <$> replicateM q getLine

  let qs' = [if null args then (command, -1) else (command, stringToInt (head args)) | command : args <- qs]

  let res = scanl' f ([], IM.empty) qs'
        where
          f (xs, s) query = case query of
            ("ADD", x) -> (x : xs, s) -- リストに値を追加
            ("DELETE", _) -> (drop1 xs, s) -- リストから値を削除
            ("SAVE", y) -> (xs, IM.insert y xs s) -- 辞書にこの時点のリストを保存
            ("LOAD", z) -> (IM.findWithDefault [] z s, s) -- 辞書から保存したリストを復元
            _ -> error "!?"

  printList [headDef (-1) xs | (xs, _) <- tail res] -- 各クエリのタイミングでのリストの先頭要素を得て、出力

Haskell のリストは永続データですから、値を変更しても変更以前の値が残ります。その値が暗黙的に他で書き換えられる事はありません。よって素直にリストを辞書に保存しておけばよいのです。一方、命令型プログラミングにおいてリストがミュータブルな場合は、ある時点の参照を辞書に保存したとしても、どこかで書き換えが発生すると、辞書に保存された参照の先のデータが書き換わるためうまくいきません。

永続データ構造

さて、ここからが本題です。TypeScript でリストを永続データとして扱うにあたり、スプレッド構文によるコピーを使いました。

// as2: 新しいリスト (先頭に 100 を追加)
const as2 = [100, ...as1];

// as3: 新しいリスト (末尾に 500 を追加)
const as3 = [...as2, 500];

すでにお気づきの方も多いと思いますが、値の更新にあたり、リスト全体のコピーが走ってしまっています。一つ値を追加、削除、更新するだけでもリストの要素  n 件に対し  n 件のコピーが走る。つまり  O(n) の計算量が必要になってしまいます。永続データプログラミングは良いものですが、ナイーブに実装するとデータコピーによる計算量の増大を招きがちです。

Haskell など、イミュータブルが前提のプログラミング言語はこの問題をどうしているのでしょうか?

結論、データ構造全体をコピーするのではなく「変更されるノードとそのノードへ直接的・間接的に参照を持つノードだけをコピーする」ことによって計算量を抑え、不変でありながらも効率的なデータ更新が可能になるようにリストその他のデータ構造が実装されています。つまり同じ「リスト」でも、命令型プログラミングのそれと、不変なデータ構造のそれは実装自体が異なるのです。抽象は同じ「リスト」でも具体が違うと言えるでしょう。

変更あったところだけをコピーし、それ以外は元の値と共有を行うこのデータ構造の実装手法は Structural Sharing と呼ばれることもあります。Structural Sharing により不変でありながら効率的に更新が可能な永続データのデータ構造を「永続データ構造」と呼びます。

永続データ構造については、以下の書籍にその実装方法含め詳しく記載されています。

純粋関数型データ構造 - アスキードワンゴ

もとい、例えば Haskell のリストは先頭の値を操作する場合は  O(1) です。先頭要素だけがコピーされていて、それ以降の要素が更新前後の二つのリストで共有されるからです。

同じく、先の実装でも利用した Data.IntMap という辞書、こちらも永続データ構造ですが、内部的にはパトリシア木で実装されていて、値の挿入やキーの探索は、整数のビット長程度の計算量 ···  n をデータサイズ、 W をビット長としたとき  O(min(n, W)) に収まります。

Haskell で利用する標準的なデータ構造 ··· List、Map、Set、Sequence、Heap は、すべてイミュータブルでありながら、値の探索や変更が  O(1) や  O(\log n) 程度の計算量で行える永続データ構造になっています。(なお、誤解の無いよう補足すると、ミュータブルなデータ構造もあります。ミュータブルなデータ構造は手続き的プログラミングで変更することになります)

永続データ構造を利用することによって、永続データプログラミング時にもパフォーマンスをそれほど犠牲にせず、大量のデータを扱うことが可能になります。裏を返せば、永続データプログラミングをより広範囲に実践していくには、永続データ構造が必要不可欠であるとも言えます。関数型プログラミングは値が不変であることをよしとしますが、そのためには永続データ構造が必要かつ重要なパーツなのです。

TypeScript その他のプログラミング言語で永続データプログラミングを実践するとき、純粋関数型言語とは異なり、素の状態では永続データ構造の支援がないということは念頭に置いておくべきでしょう。

TypeScript や Python で永続データ構造を利用するには?

TypeScript の Array、Map、Set などの標準的なデータ構造はすべて命令型データ構造、つまりミュータブルです。命令型のプログラミング言語においては、どの言語も同様でしょう。一方、プログラミング言語によっては List、Map、Set などの永続データ構造バージョンを提供するサードパーティライブラリがあります。

これらのライブラリを導入することで、TypeScript や Python で永続データ構造を利用することができます。しかし、実際のところこれらの永続データ構造の実装が、広く普及しているようには思えません。

永続データ構造は業務システム開発にも必須か?

結論からいうと、命令型のプログラミング言語で業務システム開発をする場合には、必須ではないでしょう。

永続データプログラミング自体は良い作法ですが、業務システムにおいては、大量データのナイーブなコピーが走るような実装をする場面が少ないから、というのが理由だと思います。

Haskell のような関数型言語を使っているのであれば、永続データ構造は標準的に提供されていて、そもそも必須かどうかすら気にする必要がありません。永続データ構造のメカニズムを全く知らなくても、自然にそれを使ったプログラムを書くように導かれます。

命令型言語を使いつつも、永続データプログラミングを実践するケースではどうでしょうか? 速度が必要な多くの場面では、いったん永続データを諦め、単に命令型データ構造を利用すれば事足りるので、わざわざ永続データ構造を持ち出す必要はないでしょう。ドメインオブジェクトの変更をイミュータブルに表現するためコピーする場合も、せいぜい 10 か 20 程度のプロパティをコピーする程度で、コピー 1回にあたり数万件といったオーダーのコピーが発生するようなことは希でしょう。

よって業務システム開発において Immutable.js や pyrsistent のようなサードパーティライブラリを積極的に使いたい場面は、先に解いた競技プログラミング問題のように、永続データ構造の永続である特性そのものが機能要件として必要になるケースに限られるのではないか? と思います。

Immutable.js の開発が停滞しているのは、フロントエンドで永続データ構造の需要が乏しいからでしょう。このようなデータ構造自体は非常に重要な概念で、多くのプログラミング言語に存在します。我々フロントエンドエンジニアが依存するブラウザの内部でも、効率的なデータ処理のために多用されているはずです。しかし、フロントエンドエンジニアがイミュータブルに求めているのは処理速度ではなく設計の改善です。だからこそ、Immutable.js に代わって Immer が隆盛したのでしょう。

Immutable.jsとImmer、ちゃんと使い分けていますか?

一方、純粋関数型言語で競技プログラミングのような大きなデータを扱うプログラミングを行う場合、永続データ構造は必須ですし、また永続データ構造を利用していることを意識することでよりよい実装が可能になると思っています。個人的にはこの「永続データ構造によって、より良い実装が可能になる」点こそが本質的だと思っています。

先の競技プログラミングの実装を改めてみてみます。

main :: IO ()
main = do
  q <- readLn @Int
  qs <- map words <$> replicateM q getLine

  let qs' = [if null args then (command, -1) else (command, stringToInt (head args)) | command : args <- qs]

  let res = scanl' f ([], IM.empty) qs'
        where
          f (xs, s) query = case query of
            ("ADD", x) -> (x : xs, s) -- リストに値を追加
            ("DELETE", _) -> (drop1 xs, s) -- リストから値を削除
            ("SAVE", y) -> (xs, IM.insert y xs s) -- 辞書にこの時点のリストを保存
            ("LOAD", z) -> (IM.findWithDefault [] z s, s) -- 辞書から保存したリストを復元
            _ -> error "!?"

  printList [headDef (-1) xs | (xs, _) <- tail res] -- 各クエリのタイミングでのリストの先頭要素を得て、出力 (※)

このプログラムでは、クエリのたびに、その時点でのリストの値を出力する必要があります。が、上記のプログラムでは (クエリのたびに都度出力を得ているのではなく) クエリを全部処理し終えてから、最終的な出力、つまりプレゼンテーションを組み立てています。(※) の実装です。

データ構造が命令型データ構造の場合、こうはいきません。ある時点のデータ構造の状態はその時点にしか参照できないため、プレゼンテーションをそのタイミングで得る必要があります。

一方、永続データ構造の場合、各々時点のデータ構造の状態を後からでも参照できますし、メモリ上にデータ構造を保持しておいても Structural Sharing によりそれが肥大化することもありません。このプログラムのように、中核になる計算 ··· つまりドメインロジックをすべて処理し終えてから、改めてプレゼンテーションに変換することが可能です。プレゼンテーション・ドメイン分離の観点において、永続データ構造が重要な役割を果たしています。この考え方は、実装スタイルに大きな影響を与えます。

この点に関する詳細は、競技プログラミング文脈を絡めて話す必要もあり長くなりそうなので改めて別の記事にしようと思います。

さて、業務システム開発には必須とは言えないと私見は述べましたが、命令型プログラミング言語でも値を不変に扱うとき、このナイーブなコピーが走る問題を意識できているかどうかは重要でしょう。多くの関数型言語においてはこの課題を永続データ構造によって解消しているということは、知っておいて損はありません。

永続データ構造の実装例

「永続データ構造」というと字面から何かすごそうなものを思い浮かべるかもしれませんが、その実装方法を知っておくともう少し身近なものに感じられると思います。永続データ構造の中でも比較的実装が簡単な、永続スタックと永続配列の実装を紹介して終わりにしたいと思います。実装の詳細については解説しませんが、雰囲気だけみてもらって「何か特別なことをしなくても普通に実装できるんだな」という雰囲気を掴んでもらえたらと思います。

永続スタック

Haskell で実装した永続スタックの一例です。再帰データ型でリストのようなデータ構造を宣言し、API として head tail (++) など基本的な関数を実装します。

代数的データ型でリンクリスト構造を宣言し、先頭要素への参照を返すように実装します。先頭要素を参照したいとき (head) は、先頭要素への参照からそれを取り出し値を得るだけ。先頭以外の要素を得る、つまり分解したいとき (tail) は次の要素への参照を返す。これだけで永続スタックが実装できます。

二つのスタックを結合する ((++)) ときはどうしても  O(n) かかってしまいますが、その際も双方のリストをコピーするのではなく古いリストの一方だけをコピーし、のこりの一つは新しいリストで共有されるように実装しています。

import Prelude hiding ((++))

data Stack a = Nil | Cons a (Stack a) deriving (Show, Eq)

empty :: Stack a
empty = Nil

isEmpty :: Stack a -> Bool
isEmpty Nil = True
isEmpty _ = False

cons :: a -> Stack a -> Stack a
cons = Cons

head :: Stack a -> a
head Nil = error "EMPTY"
head (Cons x _) = x

tail :: Stack a -> Stack a
tail Nil = error "EMPTY"
tail (Cons _ xs) = xs

(++) :: Stack a -> Stack a -> Stack a
Nil ++ ys = ys
Cons x xs ++ ys = Cons x (xs ++ ys)

main :: IO ()
main = do
  let s0 :: Stack Int
      s0 = empty
      s1 = cons (1 :: Int) s0
      s2 = cons (2 :: Int) s1
      s3 = cons (3 :: Int) s1

      s4 = s1 ++ s3

  print s0
  print s1
  print s2
  print s3
  print s4

出力結果は以下です。

Nil
Cons_1_Nil
Cons_2_(Cons_1_Nil)
Cons_3_(Cons_1_Nil)
Cons_1_(Cons_3_(Cons_1_Nil))

永続配列

永続配列は、配列といっても命令型の配列のように連続した領域を索引で参照できるようにするモデルではなく、完全二分木で表現します。

値は葉に持たせて、インデックスによる参照時には根から二分木を辿って目的の葉を特定します。そのため、参照時の計算量は  O(1) ではなく  O(\log n) となります。

二分木による配列の表現

更新時には「変更されるノードとそのノードへ直接的・間接的に参照を持つノードだけをコピーする」という考えに従い、根から更新対象の葉までを辿る経路上のノードをコピーする経路コピーという手法を使います。経路をコピーするといっても、木の高さ程度ですから更新も結局  O(\log n) になります。

経路コピーについては Path Copying による永続データ構造 - Speaker Deck のスライドがわかりやすいと思います。

{-# LANGUAGE DeriveFunctor #-}

import Prelude hiding (read)

data Tree a = Leaf a | Node (Tree a) (Tree a) deriving (Show, Functor)

fromList :: [a] -> Tree a
fromList [] = error "Cannot build tree from empty list"
fromList [x] = Leaf x
fromList xs =
  let mid = length xs `div` 2
   in Node (fromList (take mid xs)) (fromList (drop mid xs))

read :: Int -> Tree a -> a
read _ (Leaf x) = x
read i (Node left right)
  | i < size left = read i left
  | otherwise = read (i - size left) right

write :: Int -> a -> Tree a -> Tree a
write _ v (Leaf _) = Leaf v
write i v (Node left right)
  | i < size left = Node (write i v left) right
  | otherwise = Node left (write (i - size left) v right)

size :: Tree a -> Int
size (Leaf _) = 1
size (Node left right) = size left + size right

main :: IO ()
main = do
  let arr = fromList [1 .. 8 :: Int]
  print arr

  print $ read 3 arr

  let arr' = write 3 42 arr

  print $ read 3 arr'
  print $ read 3 arr

永続スタック、永続配列の実装を簡単ですが紹介しました。

何か特殊な技法を使うというものではなくスタック、配列などの抽象が要求する操作を考え、その抽象に適した具体的で効率的なデータ構造を用意する、というのが永続データ構造の実装です。

まとめ

永続データプログラミングと永続データ構造について解説しました。

  • 不変な値を使い、式でプログラムを宣言すると永続データプログラミングになる
  • 永続データプログラミングでは、変更前の値を破壊しない。変更後も変更前の値を参照できるという特徴を持つ
  • 関数型プログラミングすなわち永続データプログラミングである、とも考えられる
  • 永続データプログラミングにおけるデータコピーを最小限に留め効率的な変更を可能にする不変データ構造が「永続データ構造」
  • 業務システム開発において、永続データ構造は必須とは言えない。パフォーマンスが必要な場面で、永続データ構造を持ち出す以外の解決方法がある
  • 大量データを扱うことが基本で、かつ値を不変に扱いたいなら永続データ構造は必須
  • 一般のシステム開発においても機能要件として「永続」データが必要になるなら、Immutable.js とかを利用しても良いかも
  • 関数型プログラミングが、不変でありながらも値の変更をどのように実現しているかは永続データ構造に着目するとよく理解できる

というお話でした。

途中少し触れた、永続データ構造を前提にした計算の分離については別途あらためて記事にしたいと思います。

追記

以下に記事にしました。

zenn.dev

一休.com Developers Blogの執筆環境2024

この記事は一休.com Advent Calendar 2024の1日目の記事です。

kymmtです。

当ブログ「一休.com Developers Blog」は、以前からはてなブログで運用しています。そして、今年からは執筆環境を少し改善しました。具体的には、GitHubを用いて記事の作成や公開ができるようにしました。

この記事では、当ブログの執筆環境をどのように改善し、ふだん運用しているかについて紹介します。

HatenaBlog Workflows Boilerplateの導入

従来は、執筆者が記事をローカルやブログ管理画面のエディタ上で書き、なんらかの方法でレビューを受け、公開するというフローでした。このフローで一番ネックになりやすいのはレビューで、Slack上でレビューが展開されがちになり、議論を追いづらいという問題がありました。

そこで、執筆環境の改善のために、はてなさんがβ版として公開しているHatenaBlog Workflows Boilerplateを利用して、記事執筆用のリポジトリを整備しました。

github.com

これは、GitHub Actionsのはてなブログ用reusable workflow集であるhatenablog-workflowsを用いた、はてなブログ記事執筆用のリポジトリテンプレートです。

リポジトリを整備した結果、GitHub上ではてなブログの記事をMarkdownファイルとして管理したり、GitHub Actionsで原稿の同期や公開などの操作を実行できるようになりました。

現在は、記事執筆のフローは次のようになっています。

  1. 下書き用pull request (PR)作成actionを実行
  2. 手元にブランチを持ってきて執筆
  3. ときどきコミットをpushしてはてなブログに同期し、プレビュー画面で確認
  4. PR上でレビュー
  5. 公開できる状態にしてPRをmainにマージし、自動で記事公開

普段開発に携わっているメンバーはGitHubに慣れています。そのようなメンバーがPR上でブログ記事執筆やレビューができるようにすることでの執筆体験向上を図りました。とくに、レビューをPRで実施することで、あるコメントがどの文章に対するものなのか分かりやすくなる点は便利だと感じています。社内のメンバーからも、

ブログが GitHub で管理できるようになったのは、レビューの観点でホントありがたい

という感想をもらっています。

記事のネタ管理

記事のネタはGitHubのissueとして管理しています。どの執筆PRでネタが記事になったのかや、ネタに関する細かいメモも記録しています。情報の一元化の観点では、スプレッドシートなどで別管理するよりは好ましいと思います。

æ ¡æ­£

校正はtextlintを利用しています。pull_requestトリガーでtextlintによる校正を実行するGitHub Actionsのワークフローを設定しています。ワークフローは.github/workflows/textlint.yamlに次のようなものを置いています。

name: textlint

on:
  pull_request:
    paths:
      - "draft_entries/*.md"
      - "entries/*.md"

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
      - run: npm ci
      - name: Add textlint problem matcher
        run: echo "::add-matcher::.github/textlint-unix.json"
      - name: Lint files added/modified in the PR
        run: |
          git diff --diff-filter=AM origin/main...HEAD --name-only -- '*.md' | xargs npx textlint --format unix

.github/textlint-unix.jsonは次のとおりです。

{
  "problemMatcher": [
    {
      "owner": "textlint-unix",
      "pattern": [
        {
          "regexp": "^(.+):(\\d+):(\\d+):\\s(.*)$",
          "file": 1,
          "line": 2,
          "column": 3,
          "message": 4
        }
      ]
    }
  ]
}

これでPR画面上にtextlintによる校正結果が表示されるようになります。

problem matcherによるtextlintの校正結果の表示

textlintで現在使っているルールは次のとおり最低限のものです。技術記事もテクニカルライティングの一種であるとは思いますが、Web上で気軽に読んでもらう類のものでもあるため、文体にある程度個性が出るのは問題ないと考え、そこまで厳しいルールにはしていません。

レビュアー

記事ファイルが配置されるディレクトリのコードオーナーに技術広報担当者を設定して、担当者のレビューが通れば記事を公開してOK、というフローにしました。現在は2名の担当者で回しています。

HatenaBlog Workflows Boilerplateを利用しているという前提で、.github/CODEOWNERSを次のように設定しています。ここで@orgにはGitHub orgの名前が、teamには技術広報担当者からなるteamの名前が入ります。

/draft_entries/ @org/team
/entries/ @org/team

この設定に加えて、リポジトリのbranch protection rulesとしてmainブランチでコードオーナーのレビューを必須にすることで、必ず技術広報担当者が目を通せる仕組みとしています。

とはいえ、各記事の内容については執筆者のチームメンバーのほうが深く理解していることも多いです。ですので、内容の正確性という観点ではチームメンバーどうしでレビューしてもらい、技術広報担当者は会社の名前で社外に公開する記事としてふさわしいかという観点でのチェックをすることが多いです。

余談: HatenaBlog Workflows Boilerplateへのフィーチャーリクエスト

上記のBoilerplateを用いてリポジトリを運用しているうちに、ほしい機能が1つ生まれたので、該当リポジトリのissueを通じて機能をリクエストしました。

github.com

具体的には、下書きPRを作成したとき、そのPRはdraft状態になっていてほしいというものです。GitHubの仕様上、PRがdraftからready for reviewになるとき、コードオーナーへレビュー依頼の通知が送られるようになっています。ですので、記事を書き始めるときのPRとしてはdraftとするのが自然と考えた、というものです。

結果、機能として無事取り込んでいただけました。ありがとうございました1。

staff.hatenablog.com

おわりに

この記事では2024年の当ブログの執筆環境について紹介しました。GitHubを起点とする執筆環境や自動化を取り入れることで、執筆体験を向上できました。この記事も紹介した仕組みで書きました。引き続き、このブログで一休での開発における興味深いトピックをお届けしていければと思います!


  1. 機能リリース後に、draft機能がGitHubの有料プランの機能であることに伴うフォローアップもしていただきました。ありがとうございました

一休はRust.Tokyo 2024にゴールドスポンサーとして協賛します

kymmtです。

11/30に開催されるRust.Tokyo 2024に一休はゴールドスポンサーとして協賛します。

rust.tokyo

一休でのRustの活用

一休では一休.comレストランにおいてRustの活用を進めています。昨年に当ブログで活用の様子を紹介した際は、当時開発が進んでいたレストラン予約サービスWeb UIのバックエンドにおけるユースケースだけに触れていました。

user-first.ikyu.co.jp

それから1年弱経過した現在では、Rust活用の場はさらに広がっており、Rustを書いているメンバーも増えてきています。

Rust.Tokyoのゴールドスポンサー

2024年はRustでWebサービスを開発するのがますます現実的な選択肢として挙がるようになった年だったのではないかと思います。たとえば、RustでWebアプリケーションを開発するための本が複数発売されるなど1、学習リソースは着実に揃ってきています。このような環境のなかで、一休もRust採用企業として活用事例の共有などを通じてコミュニティを活性化させることが重要だと考えています。

以上のような状況に基づいて、一休はこのたびRust.Tokyo 2024にゴールドスポンサーとして協賛させていただくことになりました。当日は会場で

  • スポンサーセッション登壇
  • ブースの出展

をやります。

スポンサーセッションでは、トラックAで15:25から「総会員数1,500万人のレストランWeb予約サービスにおけるRustの活用」と題してkymmtが発表します。2024年現在の一休.comレストランにおけるRust活用の様子のスナップショットとして、システムの設計や技術的なトピックについて紹介する予定です。

また、会場ではブースを出展する予定です。技術広報チームを中心に、一休.comレストランのエンジニアとしてkymmtが、ほかにも宿泊予約サービスの一休.comのエンジニアとEMがブースに参加する予定です。一休のサービス開発の様子について興味があるかたはぜひお越しください。

おわりに

11/30に一休はRust.Tokyo 2024にゴールドスポンサーとして協賛します。参加者のかたは当日会場でお会いしましょう!

TSKaigi Kansai 2024とJSConf JP 2024に一休のエンジニアが登壇します

kymmtです。

今月は

  • 11月16日に京都で開催されるTSKaigi Kansai 2024
  • 11月23日に東京で開催されるJSConf JP 2024

と、JavaScript/TypeScriptに関するカンファレンスが2つ開催されます。今年は、一休.comレストランのフロントエンドアーキテクトを務めるエンジニア恩田 (@takashi_onda)がこれらのカンファレンス両方に登壇します。

1週違いで開催されるそれぞれのカンファレンスでは、一休.comレストランのフロントエンド開発をきっかけとする内容のトークテーマを携えて登壇します。この記事では、現在絶賛発表準備中の本人からのコメントも交えつつ、発表内容について紹介します!

TSKaigi Kansai 2024: 構造的型付けとserialize境界

kansai.tskaigi.org

TSKaigi Kansai 2024は、TypeScriptをテーマとして2024年5月に東京で開催されたTSKaigi 2024から派生した地域型カンファレンスです。

本カンファレンスでは、16:00-16:30に「カミナシ堂」会場で「構造的型付けとserialize境界」というタイトルで恩田が発表します。

以下、恩田からのコメントです。

京都在住なので、地元でお話しできるのが楽しみです。一休では僕のように関西やその他の地域に在住しているエンジニアも複数います。発表を聞いていただき興味を持たれた方は、お気軽にお声がけください。

JSConf JP 2024: Reactへの依存を最小にするフロントエンドの設計

jsconf.jp

JSConf JP 2024は、Japan Node.js Association様により運営されている、JavaScriptをテーマとする大規模カンファレンスです。

本カンファレンスでは、11:10-11:40にトラックBで「Reactへの依存を最小にするフロントエンドの設計」というタイトルで発表します。この発表は、本ブログで以前公開した記事「React / Remixへの依存を最小にするフロントエンド設計」をもとに、今回あらためてJSConfでお話しするものです。

user-first.ikyu.co.jp

以下、恩田からのコメントです。

一休レストランのフロントエンド開発では、疎結合な設計を心がけていて、ほとんどのロジックは Vanilla JS だけで完結しています。そのような書き方を可能とする、バックエンド開発で培われた知見をフロントエンドに適用する手法についてお話ししたいと思います。

おわりに

この記事では、TSKaigi Kansai 2024とJSConf JP 2024でのエンジニア恩田 (@takashi_onda)の発表内容について紹介しました。

参加者の方は、それぞれの会場でぜひ発表を聞きに来ていただければと思います!

Vue Fes Japan 2024に登壇 & ランチスポンサーをしました

CTO室プラットフォーム開発チームのいがにんこと山口(@igayamaguchi)です。

先日、Vue Fes Japan 2024が開催され、一休は登壇とスポンサーをしました。その紹介をします。

Vue Fes Japan 2024が開催

10月19日(土)に日本最大級の Vue.js カンファレンス、Vue Fes Japan 2024 が開催されました。一休は当カンファレンスでプロポーザル採択による発表と、ランチスポンサーとしてランチセッションでの発表を行いました。この記事ではその発表の概要を紹介します。

一休で行った発表は2つです。

  • Vue.js、Nuxtの機能を使い、 大量のコピペコードをリファクタリングする
  • Nuxtベースの「WXT」でChrome拡張を作成する

Vue.js、Nuxtの機能を使い、 大量のコピペコードをリファクタリングする

1つ目の発表は自分がプロポーザルを送り採択された発表です。一休.com、Yahoo!トラベルで発生したコピペ問題の原因と解決策の話です。

speakerdeck.com

一休.com、Yahoo!トラベルでは大量のコピペコードが発生していました。それにより何かを変更するときに作業がN倍になってしまい、開発スピードが落ちていました。 今回の発表ではこの問題の原因を紐解き解決していった話をしました。 コピペ問題といっても原因は様々です。ワークフローに問題があったり、コピペが発生せざるを得ない技術的背景があったり。それぞれに対応しています。 内容をざっくり抜き出すと、以下のようになります。

  • 開発ワークフローに問題がありUIのコピペが横行していたので、ワークフローの見直し、UIコンポーネントの作成
  • デザイン差分を小さくしてコードの統一
  • Option APIでロジックが共通化できていなかったので、Composition APIを導入して共通化、さらには責務わけのためのコンポーネント分割

詳細は発表資料をご覧ください。

Nuxtベースの「WXT」でChrome拡張を作成する

2つ目は新規プロダクト開発部の星野によるランチセッションでの発表です。「WXT」というOSSを使用し、Chrome拡張を作成してみた話です。

speakerdeck.com

何かを便利にしようとChrome拡張を使おうと思った時、自分の求めるChrome拡張が存在しなかったり、信用できない開発者のChrome拡張を使用したくないということがあります。この問題を解決するために「WXT」というOSSを使用してChrome拡張を開発した話をしました。

WXTはNuxtベースで開発できる拡張機能開発フレームワークです。Chrome拡張を開発するにはいくつか設定ファイルを作成したりChrome拡張特有の作業が必要でとても面倒なのですが、WXTを使用することでこの作業がとても簡単になります。しかもNuxtを使用している人には分かりやすいインターフェースになっており、Vue.js/Nuxtを触っている開発者にはとても開発しやすいものになっています。さらにはVue.jsだけでなくReactなど他のUIフレームワークを使用して開発することもできます。

自分でChrome拡張を作りたいという方はこの発表を参考にWXTを試してみてください。

おわりに

Vue Fes Japan 2024での一休の発表を紹介させていただきました。

Vue Fesですが、発表者、かつ一視聴者として参加してみて、すごく熱があるカンファレンスでした。参加者も多く、Vue.jsだけでなくViteの話も聞けて、さらにRolldownやoxcの話を開発者から聞けるという場は国内ではなかなかないと思います。そのような場で発表できたこと、スポンサーをできたことはとてもありがたかったです。

2025年も開催されるとのことなのでまた楽しみです。


一休では、ともに良いサービスをつくっていく仲間を募集中です。

hrmos.co

カジュアル面談も実施しているので、お気軽にご応募ください。

www.ikyu.co.jp

「一休×AEON 事業会社のサービスを支える基盤開発トーク」を開催しました

一休×AEONイベントの様子

はじめに

kymmtです。

先日2024年9月18日に、「事業会社のサービスを支える基盤開発トーク」と題してイオンスマートテクノロジー(以下AST)さんと合同で技術イベントを実施しました。

ikyu.connpass.com

イベントでは、各会社の事業を支える基盤プロダクトの開発や運用における苦労や工夫について登壇者の方々にお話しいただきました。

この記事では、このイベントの様子や発表の内容を紹介します。なお、X(旧Twitter)でも#ikyu_aeonというハッシュタグで当日の様子がご覧になれます。

会場

会場は、一休のオフィスも入っている東京ガーデンテラス紀尾井町内のLODGEでした。

当日は、一休/ASTのメンバーも合わせて計30人程度の方にご来場いただきました。また、イベント開始前に、ASTのもりはやさんからトップバリュ製品やイオンで販売中のお菓子を来場者向けに提供していただきました。

発表

『歴史あるプロダクトにマイクロサービスを導入するプロセス』

1つ目の発表は、一休の菊地さんによる『歴史あるプロダクトにマイクロサービスを導入するプロセス』でした。

一休のプロダクトを横断して利用されるマイクロサービス群(社内では一休プラットフォームと呼んでいます)を開発するチームでは、個別のプロダクトへのマイクロサービス導入も進めています。長い歴史を持ちコードベースが複雑なプロダクトにマイクロサービスを導入するには、技術課題の解決にとどまらず、積み重なった仕様を解きほぐして関係者と調整を進める必要もあります。

この発表では、そのような課題に直面した一休.comレストランへのポイント管理マイクロサービス導入の事例を紹介いただきました。発表のなかで、デッドコード削除や仕様の調整のような地道な取り組みから、プロダクトとマイクロサービスを疎結合に保ちつつ連携する仕組みを解説していただきました。

『1000万DLを超えたiAEONアプリ:完全停止を防ぐ耐障害性と信頼性の設計』

2つ目の発表は、ASTの范さんによる『1000万DLを超えたiAEONアプリ:完全停止を防ぐ耐障害性と信頼性の設計』でした。

iAEONアプリは実店舗のレジ前で会員バーコードを表示するようなユースケースを想定しており、障害が発生して利用不可になることを可能な限り避けたいという要件があります。一方で、複雑なバックエンド側の一部で発生した障害がアプリに伝播することで、アプリが利用不可になることも過去あったとのことでした。

この発表では、上述したような特性を持つiAEONアプリの耐障害性を高めるための取り組みについてお話しいただきました。とくに、デバイス上のキャッシュなどを活用してフロントエンドであるiAEONアプリ上のエラーをできるだけ局所化する手法について詳しく解説いただきました。App StoreやGoogle Play StoreにおけるiAEONアプリの評価が結果的に向上したとのことで素晴らしいと感じました。

『宿泊予約サイトにおける検索と料金計算の両立』

3つ目の発表は、一休の鍛治さんから『宿泊予約サイトにおける検索と料金計算の両立』でした。

宿泊予約サイトである一休.comやYahoo!トラベルでの料金に関する業務ルールは複雑です。また、ユーザーが宿泊したい部屋をさまざまな条件に基づいて検索するときに、その複雑な業務ルールで算出される料金の検索サーバであるSolrで計算する必要があります。

この発表では、そのような一休.comの検索要件の複雑さの解説や、要件を満たすためにSolrのプラグイン機構を活用していることについて解説いただきました。SolrのプラグインはJavaで開発することができ、Javaの機能を活用できるので、Solrクエリをメンテナブルに保ちつつ、複雑な料金計算を検索時に実行できているとのことでした。

『SRE改善サイクルはチームを超えて - ダッシュボードを眺める会の取り組み』

4つ目の発表は、ASTのもりはやさんから『SRE改善サイクルはチームを超えて - ダッシュボードを眺める会の取り組み』でした。

New Relicなどを活用したシステムメトリクスを概観できるダッシュボードは、非常時に障害の原因を探るためのものとして非常に便利です。一方で、定点観測することで、日々のシステムの状態や、障害になり得る変化を見つけることができます。

この発表では、ASTのSREチームを中心にダッシュボードを定期的に眺める会を設けることで、メトリクスの変化や改善ポイントを発見して深掘りする機会を意図的に作り出す取り組みについてお話しいただきました。システムの信頼性を高める機会を作り出すのはもちろん、会を通じて「ザイオンス効果(単純接触効果)」でチームビルディングにも寄与している点が素晴らしいと感じました。

おわりに

「事業会社のサービスを支える基盤開発トーク」の様子について紹介しました。

各社のサービスの基盤プロダクト開発について社内外のエンジニアの工夫や知見が聞ける、とてもおもしろいイベントになりました。ご来場いただいたみなさま、ありがとうございました!


一休では、ともに良いサービスをつくっていく仲間を募集中です。

hrmos.co

カジュアル面談も実施しているので、お気軽にご応募ください。

www.ikyu.co.jp

情シスカンファレンス「Business Technology Conference Japan 2024」にブロンズスポンサーとして協賛します

コーポレート本部 社内情報システム部 兼 CISO室 id:rotom です。

10/12(土) にハイブリッド形式で情シス向けのテックカンファレンス「Business Technology Conference Japan 2024(BTCONJP 2024)」が開催されます。

btcon.jp

btajp.connpass.com

昨年オンライン開催された BTCONJP 2023 では私が登壇者として 本社を東京ガーデンテラス紀尾井町へ移転し、オフィスファシリティ・コーポレートIT を刷新した話 というテーマで発表させていただきましたが、今年は core staff として運営に参画しており、所属企業である一休はブロンズスポンサーとして協賛しております。

Identity, DX & AI, Device Management, Zero Trust, Cyber Security, Business Technology の6つのテーマに沿った12のセッションや、イオン / イオンスマートテクノロジー CTO の 山﨑 賢氏による基調講演を用意しています。 詳細は以下のプレスリリースをご覧ください。

prtimes.jp

オフライン会場は一休 本社も入居する、東京ガーデンテラス紀尾井町 紀尾井タワーのLINEヤフー株式会社 本社です。

map.yahoo.co.jp

LODGE での懇親会も予定しておりますので、ぜひ現地でご参加ください!