Slide 1

Slide 1 text

Active Record から考える 次の 10 年を見据えた技術選定 Yuichi Goto (@_yasaichi) September 15,2021 @ iCARE Dev Meetup #25

Slide 2

Slide 2 text

自己紹介 Yuichi Goto(@_yasaichi) ピクスタ株式会社 執行役員 CTO 兼 開発部長 2020 年 7 月発売の「パーフェクト Ruby on Rails 【増補改訂版】」の 共著者(Part 5 担当) 2

Slide 3

Slide 3 text

[PR] 会社の技術系 Podcast 「texta.fm」 を運営しています 本発表の内容は、texta.fm でピクスタの 技術顧問の和田卓人(@t_wada)さんと 議論した内容が下敷きになっています。 画像出典: https://anchor.fm/textafm 3

Slide 4

Slide 4 text

会社紹介 ピクスタは本社と複数のグループ会社(ベトナム開発拠点含む)から構成 されており、「クリエイティブ・プラットフォーム事業」を主要事業としている 2015 年に東証マザーズ市場に上場し、2020 年 からはいわゆる第二創業 期を迎えつつある状態 本社社員 84 名のうち約 2 割がエンジニア職種であり(※)、全ての事業で Ruby on Rails(以下、Rails)を利用している RubyKaigi には 過去 6 回協賛 ※ これらの数値は全て 2021 年 6 月末時点のもの 4

Slide 5

Slide 5 text

本発表の背景と目的 5

Slide 6

Slide 6 text

成長戦略の策定 2020 年に今後 10 年の成長戦略を 策定し、既存事業の周辺市場へ 新規事業を展開することを決定 (右図: 2021 年 12 月期第 2 四半 期決算説明資料 [1] p. 47) CTO としてこの成長戦略の実現に 向けた体制を作ることになった 6

Slide 7

Slide 7 text

どのように体制を考えていったか 前ページの成長戦略を実現することを起点として、次のようにブレイクダウン して考えていった。以降では、各段階の結論を簡単に述べる。 1. 成長戦略の実現にあたって必要な開発体制を考える 2. 1 の体制の実現にあたって必要な構造を考える 3. 2 の構造の実現にあたって必要な要素技術の選定方針を考える 7

Slide 8

Slide 8 text

1. 開発体制のあるべき姿 事業の成長段階によって次のように変化するビジネス上の方針に柔軟に 適応できる開発体制 を作ることで、前述の戦略を実現できると考えた。 PSF(※1)到達前: 試作品の提供を通じて顧客課題の解決策を探したい PMF(※2)到達前: 少人数で高速な仮説検証を回すことでプロダクトの 最適な体験や価格を探したい PMF 到達後: 組織を拡大することで事業の成長を加速させたい ※1、※2 それぞれ「Problem/Solution Fit」「Product/Market Fit」の略。詳しくは [2] などを参照のこと 8

Slide 9

Slide 9 text

2. 理想の開発体制を実現する構造 次の組織構造とシステムアーキテクチャによって、PMF 到達前後の方針の 変化に適応できる開発体制を実現できると考えた。 組織構造(※): 社内インフラ基盤を提供するプラットフォームチームが 存在しており、新規プロダクトの立ち上げが開発チームだけで完結できる システムアーキテクチャ: フロントエンドとバックエンドが API スキーマに よって分離されており、後者を分割することで開発チームを拡大できる ※ 本発表では取り上げないが、詳細に興味のある方は texta.fm 「6. 1on1 in Public [3]」を参照のこと 9

Slide 10

Slide 10 text

事業の各段階におけるシステムアーキテクチャの変化 ※ フロントエンドの分割による開発チームの拡大に関しては、まだ考えがまとまっていないため取り上げていない 10

Slide 11

Slide 11 text

3. 要素技術の選定方針 本発表を通じて説明するいくつかの理由から、前述のシステムの要素技術を 次の方針で選定することにした。 フロントエンドとバックエンドの 開発言語を TypeScript で統一する フロントエンドでは Jamstack アーキテクチャを採用する バックエンドでは 最近話題の Prisma を見送り、アクティブレコードか データマッパーを実装した ORM を採用する ※ 本資料における「Prisma」は、特に断りがない限り ORM としてのそれを指すものとする 11

Slide 12

Slide 12 text

本発表の目的と得られるもの 目的: 前ページの方針に至った理由を ORM を切り口に説明したうえで、 ピクスタでの今後の取り組みを紹介する 得られる(であろう)もの: ORM がバックエンドのアーキテクチャと開発生産性に与える影響 技術と経営を繋ぐ技術選定の一例 12

Slide 13

Slide 13 text

以降の構成 第一部: Active Record から見る Rails 第二部: Prisma から導かれるアーキテクチャ 第三部: ピクスタでの今後の取り組み 13

Slide 14

Slide 14 text

第一部: Active Record から見る Rails 1. Web アプリケーションアーキテクチャ概論 2. Active Record から見る Rails のアーキテクチャ 3. なぜ Rails の利用を取りやめていく選択をしたか 14

Slide 15

Slide 15 text

Web アプリケーションアーキテクチャの主な論点 Web アプリケーションでは、内部を適当数のレイヤーに分割してこれらを 積み重ねる構造(※)によってその複雑さに対処することが多い [4] したがって、Web アプリケーションのアーキテクチャを考えるにあたって 主な論点は次の 2 つとなる 論点 1: どんな役割のレイヤーをどのように積み重ねるか 論点 2: 各レイヤーをどのように設計するか ※ この構造を「レイヤードアーキテクチャ」と呼ぶ。厳密な定義は「POSA [5]」などを参照のこと 15

Slide 16

Slide 16 text

典型的なレイヤリング: 3 レイヤー One of the most common ways to modularize an information-rich program is to separate it into three broad layers: presentation (UI), domain logic (aka business logic), and data access. So you often see web applications divided into . . . On the whole Ive found this to be an effective form of modularization for many applications and one that I regularly use and encourage. ― Martin Fowler (2015) [6] “ “ ※ 太字強調は引用者によるもの 16

Slide 17

Slide 17 text

各レイヤーの名称と役割 Fowler 氏の著書「Patterns of Enterprise Application Architecture [7]」 (※1)では、3 つレイヤーの名称と役割は次のように定義されている。 プレゼンテーション(※2): ユーザーからの HTTP リクエストなどを下位 レイヤーの呼び出しへ変換する、ユーザーへ情報を表示する ドメイン: 入力データの妥当性確認、入力・格納データによる計算を行う データソース: データベースやメッセージングシステムなどと通信する ※1 以下、「PoEAA」と呼ぶ ※2 本レイヤーの設計方法は以降の議論とは関係しないため取り上げない 17

Slide 18

Slide 18 text

これらのレイヤーの積み重ね方のパターン 以降の説明では こちらを扱う ※ 「依存性逆転の原則」の詳細は書籍「Clean Architecture [8]」などを参照のこと 18

Slide 19

Slide 19 text

ドメインレイヤーをどのように設計するか PoEAA で紹介されているパターンのうち、使用する言語・フレームワークに 関係なく利用できるものは次の通り。 トランザクションスクリプト: プレゼンテーションレイヤーからの 1 つの 要求を処理する一連の手続き ドメインモデル: アプリケーションが対象とする問題領域(ドメイン)から 抽出された概念をオブジェクトとして定義したもの 19

Slide 20

Slide 20 text

ドメインレイヤー内のサブレイヤー ドメインモデルを用いる場合、ドメインレイヤーは 2 つに分割されることが 多く、うち上位のレイヤーを PoEAA では 「サービスレイヤー」と呼んでいる サービスレイヤーは複数のドメインモデルやデータソースを協調させて、 プレゼンテーションレイヤーからの要求に応える役割を担う 文脈によって同レイヤーを指す名称が変わるため注意が必要(e.g. 書籍 「Clean Architecture [8]」の同心円図では「ユースケース」と呼ぶ) 20

Slide 21

Slide 21 text

データソースレイヤーをどのように設計するか PoEAA において、データストアが RDB の場合に利用できるパターンとして 紹介されているものは次の通り。 (テーブル|行)データゲートウェイ: RDB 内の(テーブル|レコード)への 操作をカプセル化するオブジェクト アクティブレコード: ドメインロジックを実装した行データゲートウェイ データマッパー: ドメインモデルと RDB 内のレコードを相互変換するもの ※ Rails の Active Record との混同を避けるため、本発表では訳本のパターン名を意図的に用いている 21

Slide 22

Slide 22 text

2 つのレイヤーのアーキテクチャパターンの関係 ドメインとデータソースのアーキテクチャパターンは互いに独立ではなく、 次の組み合わせで利用することが多い(理由は後述)。 A. トランザクションスクリプト + (テーブル|行)データゲートウェイ B. ドメインモデル + アクティブレコード C. ドメインモデル + データマッパー 以降の議論には関係しないため、 こちらの組み合わせは紹介に留める 22

Slide 23

Slide 23 text

各組み合わせの実装例 次の正常系のフローからなる「ニュースレター登録機能」を、前ページの組み 合わせ A と B で実装した例を示す。 1. ユーザーは、フォームにメールアドレスを入力して「登録」ボタンを押す 2. アプリケーションは、メールアドレスの形式が正しいことを確認する 3. アプリケーションは、メールアドレスとその存在確認用のトークンを DB に 記録して、ユーザーに確認メールを送信する 23

Slide 24

Slide 24 text

DB = Sequel.connect("connection_url") class SubscribeToNewsletter Result = Struct.new(:errors, :subscription_id, keyword_init: true) def self.call(email:) unless URI::MailTo::EMAIL_REGEXP.match?(email) return Result.new(errors: ["Email is invalid"]) end values = { email: email, confirmation_token: SecureRandom.uuid } DB.transaction do id = DB[:newsletter_subscriptions].insert(values) EmailConfirmationMailer.deliver(values) Result.new(subscription_id: id) end rescue StandardError Result.new(errors: ["Something went wrong"]) end end A. トランザクションスクリプト + テーブルデータゲートウェイ ドメインロジックは スクリプト内にべた書き 24

Slide 25

Slide 25 text

class SubscribeToNewsletterService Result = Struct.new(:errors, :subscription_id, keyword_init: true) def self.call(email:) unless URI::MailTo::EMAIL_REGEXP.match?(email) return Result.new(errors: ["Email is invalid"]) end subscription = NewsletterSubscription.new(email: email) subscription.set_confrimation_token NewsletterSubscription.transaction do subscription.save! EmailConfirmationMailer .deliver(subscription.slice(:email, :confirmation_token)) Result.new(subscription_id: subscription.id) end rescue StandardError Result.new(errors: ["Something went wrong"]) end end B. ドメインモデル + アクティブレコード with サービスレイヤー 25

Slide 26

Slide 26 text

「ニュースレター登録機能」の 2 つの実装例のアーキテクチャ上の違い 26

Slide 27

Slide 27 text

第一部: Active Record から見る Rails 1. Web アプリケーションアーキテクチャ概論 2. Active Record から見る Rails のアーキテクチャ 3. なぜ Rails の利用を取りやめていく選択をしたか 27

Slide 28

Slide 28 text

Rails の Active Record とは MVC パターンを採用している Rails におけるモデルに相当するもので、 Rails を構成する gem の 1 つである activerecord にその実体がある [9] 各アプリケーションでは、この gem が提供する ActiveRecord::Base という 基底クラスを継承して固有のモデルを定義する その名称からも明らかなように、この基底クラスは PoEAA のアクティブ レコードの実装になっている [10] 28

Slide 29

Slide 29 text

PoEAA のアクティブレコードにはない機能 基底クラス ActiveRecord::Base は PoEAA のアクティブレコードにはない次の 代表的な機能を持っている。 バリデーション: データベース操作の実行前にモデルの状態を検証できる ようにするもの(e.g. ある属性が空でないかの確認) コールバック: データベース操作の実行前後に任意のコードを実行できる ようにするもの 設定したコードはデータベース操作と 同一のトランザクション内で実行される 29

Slide 30

Slide 30 text

class NewsletterSubscription < ActiveRecord::Base validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } before_create :set_confrimation_token after_create :send_email_confirmation_instructions private def set_confrimation_token self[:confirmation_token] = SecureRandom.uuid end def send_email_confirmation_instructions EmailConfirmationMailer.deliver(slice(:email, :confirmation_token)) end end NewsletterSubscription.create(email: "[email protected]") 2 つの機能を利用すると「ニュースレター登録機能」がモデルで完結する 30

Slide 31

Slide 31 text

「ニュースレター登録機能」 における Rails のアーキテクチャ クラスとして実装されていた複数のレイヤーは Rails ではモデルとそのメソッドに対応する 31

Slide 32

Slide 32 text

Rails の Active Record が果たす役割 次の方法で ドメインレイヤー以下をモデルクラスだけで構築できるように してレイヤーの数(≒ コードの記述量)を減らす ことで、高い開発生産性を 実現している。 アクティブレコードを採用して、ドメインレイヤーとデータソースレイヤーを 1 つのクラスで実装できるようにした バリデーションとコールバックを導入して、サービスレイヤーのロジックを モデルクラスの DSL やインスタンスメソッドで実装できるようにした 32

Slide 33

Slide 33 text

Rails のアプローチの構造的な限界: 暗黙の 1:1 対応が崩れるとき プレゼンテーションレイヤーからのある要求に 特化したモデル内のロジックが問題となる 33

Slide 34

Slide 34 text

限界との向き合い方 データベースレベル: イベントエンティティ(※1)を見落としていないか 確認して、もしあれば対応するモデルに問題のロジックを移動する コードレベル(※2): 新たにサービスレイヤーを導入して、こちらに問題の ロジックを移動する アーキテクチャレベル(※2): ドメインを最初から小さくする、後から分割 して小さくすることで問題を発生させない プレゼンテーションレイヤーからの要求と イベントエンティティの生成は 1:1 対応 していることが多く、問題になりにくい ※1、※2 詳しくはイミュータブルデータモデル [11]、Ruby on Rails の正体と向き合い方 [12]をそれぞれ参照のこと 34

Slide 35

Slide 35 text

第一部: Active Record から見る Rails 1. Web アプリケーションアーキテクチャ概論 2. Active Record から見る Rails のアーキテクチャ 3. なぜ Rails の利用を取りやめていく選択をしたか 35

Slide 36

Slide 36 text

要因: 今後予想される新規事業の特性 ピクスタは、「クリエイター基盤づくり」、「洗練された UI・UX」、「オンライン マーケティング(SEO・Web 広告)」の 3 つを強みとして掲げている [1] 前述の成長戦略の実現にあたって、これらの 強みを活かした新規事業を 展開することで、その成功確率を高めようとするのは自然な流れ である 特に 後の 2 つの強みはフロントエンドと関係する ため、今後のシステム アーキテクチャの要素技術の選定にあたって考慮したい 36

Slide 37

Slide 37 text

考慮すべき事項の一例: Core Web Vitals Google が提唱するユーザー体験の質を測定するための指標で、2020 年 時点では LCP、FID、CLS の 3 つ(※)から構成される [13] 2021 年 6 月から Google の検索結果順位を決める要因に加えられており [14]、検索流入に頼ることが多いピクスタでは無視できないものの 1 つ 本指標で自然と高スコアを取れるシステムアーキテクチャにしておけば、 SEO 関連の開発よりもプロダクトの仮説検証に多くの時間を割ける ※ それぞれ「Largest Contentful Paint」、「First Input Delay」、「Cumulative Layout Shift 」の略 37

Slide 38

Slide 38 text

Rails の フロントエンドの今後 Rails 7 に向けて、フロントエンドの開発生産性の向上に関する次の変更が 行われたが(※)、ユーザー体験指標の向上に関する動きは見られない。 デフォルトで Hotwire を導入して、SPA に近い体験の実装を容易にする Import maps を導入して、デフォルトで Webpacker を無効にする デフォルトで Sass のトランスパイルを無効にし、Tailwind CSS のような CSS フレームワークの利用を推奨する ※ 2021 年 9 月中旬時点のコミット内容に基づく記述で、正式リリースまでに変更される可能性がある 38

Slide 39

Slide 39 text

Rails の出自を思い出すと、これは自然な流れである Basecamp と HEY はサービスの特性上 どちらも SEO があまり問題にならない 画像出典: Ruby on Rails の正体と向き合い方 [12] 39

Slide 40

Slide 40 text

2019 年時点での見解 Active Record による Rails のバックエンドでの高い開発生産性は、その 構造的な限界に対処さえできれば依然として魅力的である 一方で Rails の出自を考えると、ピクスタで必要なユーザー体験指標の 向上に注力する流れに自然となる可能性は高くない バックエンドは Rails のまま、ユーザー体験指標の向上に注力している フロントエンドのフレームワークを用いれば全て解決するのではないか 40

Slide 41

Slide 41 text

新規事業 「PIXTA オンデマンド」 で Next.js を試験導入 (2020 年) バックエンドでは引き続き Rails (API モード)を利用している 画像出典: https://od.pixta.jp 41

Slide 42

Slide 42 text

新たな問題と解決策 背景: ピクスタでは、 PMF 到達前のチームを事業責任者、デザイナー、 エンジニアの 3 人で構成することが多く、本新規事業も同様であった 起きたこと: フロントエンドとバックエンドでの開発言語の違いから生じる 認知負荷が想定以上に大きく、 担当エンジニアに負担をかけてしまった 解決策: フロントエンドとバックエンドの 開発言語を統一することで、一人 目のエンジニアにかかる認知負荷をできるだけ下げる 42

Slide 43

Slide 43 text

どのように開発言語を統一するか ピクスタでは、 MVP(※)を Web サービスとして製品化することが多いため、 次の 2 つの方向性を検討したうえでより現実的な B 案を選択した。 A. バックエンドでは引き続き Rails を利用し、ハイパフォーマンスな Web フロントエンドを Ruby で開発する方法を模索する B. フロントエンドでは Next.js のような Jamstack アーキテクチャの実装を 利用し、Rails に近い生産性でバックエンドを JavaScript で開発する 方法を模索する 事業領域がクリエイティブ分野で あることに関連している(と思う) ※ 「Minimum Viable Product」の略。詳しくは「MVP の作り方 [15]」などを参照のこと 43

Slide 44

Slide 44 text

第一部完 44

Slide 45

Slide 45 text

第二部: Prisma から導かれるアーキテクチャ 1. Prisma がアーキテクチャ選定に与える影響 2. なぜ Prisma の採用を見送る選択をしたか 45

Slide 46

Slide 46 text

前提: JavaScript と TypeScript のどちらを選ぶか 次のような良し悪しがあるものの、総合的に見て恩恵の方が大きいと考えて TypeScript を採用した。 以降の議論もこの決定を前提としている。 良い点: システムが複雑化する PMF 到達後では静的型の恩恵が大きい。 特にフロントエンドではテストが書きづらいため、より早くから必要になる 悪い点: PMF 到達前の仮説検証において、静的型が素早い開発の妨げに なることがある(が、最終手段として any がある) 46

Slide 47

Slide 47 text

Prisma とは v1 は任意のデータストアに GraphQL を被せるサーバーの実装だったが、 v2 (2020 年 6 月リリース [16])以降では TypeScript の ORM となった 独自スキーマによるデータモデルの定義、定義に基づいた型安全なデータ ベース操作とマイグレーション機能が主な特徴として挙げられる 調査したところ、 JavaScript の新興フルスタックフレームワーク(※)の 多くで採用されていたため、まず初めに利用を検討した ※ 本発表では、2020 年前後に開発が始まったものを指している。具体的は、Blitz、RedwoodJS など 47

Slide 48

Slide 48 text

const prisma = new PrismaClient(); // Create const createdUser = await prisma.user.create({ data: { email: '[email protected]' }, }); // Read const user = await prisma.user.findUnique({ where: { id: createdUser.id } }); // Update const updatedUser = await prisma.user.update({ where: { id: user!.id }, data: { email: '[email protected]' }, }); // Delete await prisma.user.delete({ where: { id: updatedUser.id } }); Prisma によるデータベース操作の例 各データベース操作はRDB内の テーブルに対応するオブジェクトの メソッド呼び出しによって行う 48

Slide 49

Slide 49 text

Prisma は PoEAA のどのパターンを実装しているか 公式ドキュメントでは、"Prisma is a new kind of Data Mapper ORM" と 述べられている [17] 前ページで示したように、Prisma は POJO(※)と RDB 内のレコードを 相互変換しているので、広義のデータマッパーと言えなくもない しかし、最新の v3.0.2 時点でもこの POJO にドメインロジックを実装する 方法を提供していないため、テーブルデータゲートウェイの実装と言った 方が正しい ※ 「Plain Old JavaScript Object」の略。素の JavaScript オブジェクトのこと 49

Slide 50

Slide 50 text

ドメインレイヤーのアーキテクチャ選定に与える影響 ドメインレイヤー以下をドメインモデルと Prisma で構築しようとすると、 Prisma の返す POJO のデータをドメインモデルに詰め直す必要がある 特に Web アプリケーションの実装初期では、ドメインモデルと RDB 内の テーブルが 1:1 対応することが多いため、この作業は手間なだけである 結果、データソースレイヤーに Prisma を用いると、 ドメインレイヤーでは 自ずとトランザクションスクリプトを用いることになる p. 22 の理由の 1 つがこちら 50

Slide 51

Slide 51 text

import {Ctx} from "blitz" import db from "db" import {hashPassword} from "app/auth/auth-utils" import {SignupInput, SignupInputType} from "app/auth/validations" export default async function signup(input: SignupInputType, {session}: Ctx) { // This throws an error if input is invalid const {email, password} = SignupInput.parse(input) const hashedPassword = await hashPassword(password) const user = await db.user.create({ data: {email: email.toLowerCase(), hashedPassword, role: "user"}, select: {id: true, name: true, email: true, role: true}, }) await session.$create({userId: user.id}) return user } 実際にトランザクションスクリプトに帰着した例 (Blitz) 出典: blitz/examples/custom-server/app/auth/mutations/signup.ts [18] 51

Slide 52

Slide 52 text

第二部: Prisma から導かれるアーキテクチャ 1. Prisma がアーキテクチャ選定に与える影響 2. なぜ Prisma の採用を見送る選択をしたか 52

Slide 53

Slide 53 text

要因: トランザクションスクリプトの開発生産性に関する特性 トランザクションスクリプトは ドメインが 複雑になると開発生産性が急落する 全期間の開発生産性はドメインモデルの 方が高くなりうるため、Rails の生産性を 再現する目的では Prisma は不向き 画像出典: PoEAA(p. 29) 53

Slide 54

Slide 54 text

本件に対する Prisma 側の認識と姿勢 Prisma 側 にも本件は認識されており、「Explore how to extend the Prisma Client [19]」という Issue では次のような趣旨が述べられている。 Prisma で Computed field、Multi-step actions(※)、Custom Validation を実装する場合の指針を示せていない状況を改善したい 上記のロジックをコントローラーに直接実装することは解決策の 1 つだが、 アプリケーションが複雑になるとモデルによる抽象化が必要になる ※ 説明を読む限り、(アプリケーション|ドメイン)サービスに実装するようなロジックのことを指していると思われる 54

Slide 55

Slide 55 text

核心をついた Issue だが、直近 2 か月ほど動きがない状態 2021 年 7 月 14 日に担当者が 変更された後、特に動きなし 画像出典: https://github.com/prisma/prisma/issues/7161 [19] 55

Slide 56

Slide 56 text

Prisma 以外の選択肢 アクティブレコードやデータマッパーの実装(※)を Rails と同じ限界に陥る ことを避けつつ用いる選択もあるが、次のように 一長一短で決め手に欠く。 優れている点: ドメインレイヤーでは自ずとドメインモデルを用いることに なるため、ドメインが複雑になっても開発生産性が落ちにくくなる 劣っている点: データモデルの定義に基づいてコードとその型を生成する Prisma には型安全性の面でどうしても及ばない(e.g. TypeORM [20]) ※ Bookshelf.js や MikroORM、TypeORM、Sequelize などがこれに当たる 56

Slide 57

Slide 57 text

現時点での方針 Rails に近い開発生産性を実現するという観点で見た場合、現時点では最も 有力な TypeScript の ORM が存在しないため、次の方針をとることにした。 アクティブレコードかデータマッパーの実装の中で相対的に型安全性が 高いものを選んで、 その技術検証を進めていく 並行して前述の Issue の行方を観察し、複雑なロジックを実装するための 何らかの方法が考案された場合には Prisma へ移行する 57

Slide 58

Slide 58 text

第二部完 58

Slide 59

Slide 59 text

第三部: ピクスタでの今後の取り組み 59

Slide 60

Slide 60 text

ここまでのまとめ 次の ピクスタのビジネス上の問題を解決するため、言語を TypeScript に 統一し、Jamstack とアクティブレコードの実装を用いる方針を設定した。 A. 成長戦略の実現のため、強みの 1 つであるオンラインマーケティングが 活きる新規事業を展開し、その成功確率を高めたい B. 高速な仮説検証と SEO 関連の開発を両立させるため、開発生産性が 高く、かつ Core Web Vitals で自然と高スコアが取れる構造を作りたい C. B の実現にあたってエンジニアにかかる認知負荷をできるだけ下げたい 近年の流行を取り入れることを 第一とした技術選定ではない 60

Slide 61

Slide 61 text

現在検証中の技術スタック 2021 年 8 月より、前述のシステムアーキテクチャと要素技術の選定方針を もとに、次の技術スタックの実現可能性を専任メンバーが検証している。 フロントエンド: Apollo Client、GraphQL Code Generator、Next.js API スキーマ・ゲートウェイ: GraphQL、Apollo Gateway バックエンド: Apollo Server(with Express)、Nexus、TypeORM 61

Slide 62

Slide 62 text

主な要素技術の選定理由 Next.js: Jamstack アーキテクチャの実装の中でも最も進化が速いため GraphQL: Apollo Federation を利用することで、ほぼコードを書かずに API ゲートウェイ を実装できるため Nexus: NestJS と迷ったが、初期の学習コストの高さと持続可能性にやや 懸念があり、まずは最小限の構成から始めることにしたため TypeORM: 機能、型安全性、持続可能性のバランスが取れていたため 62

Slide 63

Slide 63 text

今後予定している進め方 直近では新規プロダクトの開発は未定なため、まずは既存の PIXTA (※) サービスを前述のアーキテクチャに近づける取り組みを行い、学びを得る この PIXTA のリアーキテクティングは、数名の専任チームが開発チームと 協力しながら 2 ~ 3 年かけて行う リアーキテクティングの成否はもちろんのこと、今後生じる新規プロダクト 開発に得られた学びを還元することも同じくらい重視する ※ ピクスタが運営する写真・イラスト・動画・音楽のデジタル素材オンラインマーケットプレイスのこと 63

Slide 64

Slide 64 text

リアーキテクティングの具体的な進め方 (現在検討中のもの) ※ Rails を GraphQL API のクライアントと見る考え方は github/graphql-client [21] から着想を得ている 64

Slide 65

Slide 65 text

[PR] ピクスタでは、本取り組みに興味のある方を募集しています! 画像出典: https://recruit.pixta.co.jp 65

Slide 66

Slide 66 text

第三部完 66

Slide 67

Slide 67 text

おわりに: 本発表で伝えたかったこと 技術選定の一番の目的は流行を取り入れることではなく、ビジネス上の 問題を解決することである(∴ 本発表でも後者の話題を多く取り上げた) トランザクションスクリプトとドメインモデルは、ドメインの複雑化に伴う 開発生産性の変化の仕方が異なるため、その選定時には注意が必要 ドメインとデータソースのアーキテクチャパターンは互いに関連するため、 ORM の選定はバックエンドのアーキテクチャと開発生産性に強く影響する 67

Slide 68

Slide 68 text

ご清聴ありがとうございました 68

Slide 69

Slide 69 text

参考文献 69

Slide 70

Slide 70 text

参考文献 1 1. ピクスタ株式会社 "2021 年 12 月期第2四半期決算説明資料",URL: https://ssl4.eir- parts.net/doc/3416/ir_material_for_fiscal_ym/104956/00.pdf↩ 2. 馬田隆明 "PMF に到るまでのステージ別指針集 ",URL: https://speakerdeck.com/tumada/pmf-nidao- rumadefalsesutezibie-zhi-zhen-ji↩ 3. "6. 1on1 in Public by texta.fm",URL: https://anchor.fm/textafm/episodes/6--1on1-in-Public-e1078pn↩ 4. "Common web application architectures",URL: https://docs.microsoft.com/en- us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures↩ 5. Frank Buschmann,Regine Meunier,Hans Rohnert,Peter Sommerlad,and Michael Stal (1996) Pattern- Oriented Software Architecture,Volume 1,A System of Patterns: Wiley.↩ 6. Martin Fowler "PresentationDomainDataLayering",URL: https://martinfowler.com/bliki/PresentationDomainDataLayering.html↩ 7. Martin Fowler (2002) Patterns of Enterprise Application Architecture: Addison-Wesley Professional.↩ 70

Slide 71

Slide 71 text

参考文献 2 8. Robert C.Martin (2018) Clean Architecture 達人に学ぶソフトウェアの構造と設計: KADOKAWA.↩ 9. "Welcome to Rails",URL: https://github.com/rails/rails/blob/6-1-stable/README.md↩ 10. "Active Record – Object-relational mapping in Rails",URL: https://github.com/rails/rails/tree/6-1- stable/activerecord#label-Philosophy↩ 11. kawasima "イミュータブルデータモデル",URL: https://scrapbox.io/kawasima/イミュータブルデータモデル↩ 12. Yuichi Goto "Ruby on Rails の正体と向き合い方",URL: https://speakerdeck.com/yasaichi/what-is-ruby- on-rails-and-how-to-deal-with-it↩ 13. "Introducing Web Vitals: essential metrics for a healthy site",URL: https://blog.chromium.org/2020/05/introducing-web-vitals-essential-metrics.html↩ 14. "More time, tools, and details on the page experience update",URL: https://developers.google.com/search/blog/2021/04/more-details-page-experience↩ 71

Slide 72

Slide 72 text

参考文献 3 15. 馬田隆明 "MVP の作り方 とにかく雑に作る「手作業型 MVP」のススメ",URL: https://speakerdeck.com/tumada/mvp-falsezuo-rifang-tonikakuza-nizuo-ru-shou-zuo-ye-xing-mvp- falsesusume↩ 16. Nikolas Burk "Prisma 2.0: Confidence and productivity for your database",URL: https://www.prisma.io/blog/announcing-prisma-2-n0v98rzc8br1↩ 17. "Is Prisma an ORM?",URL: https://www.prisma.io/docs/concepts/overview/prisma-in-your-stack/is- prisma-an-orm↩ 18. "blitz/signup.ts at v0.40.0-canary.7 · blitz-js/blitz",URL: https://github.com/blitz-js/blitz/blob/v0.40.0- canary.7/examples/custom-server/app/auth/mutations/signup.ts↩ 19. "Explore how to extend the Prisma Client · Issue #7161 · prisma/prisma",URL: https://github.com/prisma/prisma/issues/7161↩ 20. "Prisma vs TypeORM",URL: https://www.prisma.io/docs/concepts/more/comparisons/prisma-and- typeorm#type-safety↩ 72

Slide 73

Slide 73 text

参考文献 4 21. "github/graphql-client: A Ruby library for declaring, composing and executing GraphQL queries",URL: https://github.com/github/graphql-client↩ This presentation is created by Marp. Great thanks @yhatt ! 73