はじめに 事前の検証作業 MySQL 5.7 ⇒ 8.0の変更点の影響確認 1. 照合順序 2. 暗黙のソート 3. 予約語 アップグレード方法の検討 パラメータグループの作成 パフォーマンス確認 アップグレード済みのステージング環境の運用 k6とDatadogを用いた主要画面のパフォーマンス確認 実行計画の変化の確認 SQLの改善 本番アップグレード作業 振り返りと今後について まとめ はじめに プラットフォーム部 SREチームのショウゴ( @shogo_452 )です。 TUNAGのメインデータベースをAmazon Aurora MySQL v2(MySQL5.7互換)からv3(MySQL8.0互換)にアップグレードしたので、その検証内容などアップグレード完了に至るまでの過程を共有します。 事前の検証作業 MySQL 5.7 ⇒ 8.0の変更点の影響確認 まずは、MySQL 5.7(以下、5.7)からMySQL 8.0(以下、8.0)へのバージョンアップにおける変更点とその影響有無の調査を行いました。 1. 照合順序 照合順序は、文字の並び替えや比較のルールです。 文字コードutf8mb4の照合順序のデフォルトが、8.0でutf8mb4_general_ciからutf8mb4_0900_as_ciに変更になりました。 TUNAGでは、明示的にutf8mb4_general_ciを指定していたため、影響はありませんでした。 各文字セットにはデフォルト照合があります。 たとえば、utf8mb4 および latin1 のデフォルトの照合は、それぞれ utf8mb4_0900_ai_ci および latin1_swedish_ci です。 dev.mysql.com 2. 暗黙のソート 5.7では、GROUP BY句で暗黙的にソートが行われていましたが、8.0からはこの暗黙のソートがなくなったため、ORDER BY句を使う必要があります。 TUNAGでは暗黙のソートに依存している箇所はなかったため、影響はありませんでした。 以前は (MySQL 5.7 以下)、GROUP BY は特定の条件下で暗黙的にソートされていました。 MySQL 8.0 では発生しなくなったため、暗黙的ソートを抑制するために最後に ORDER BY NULL を指定する必要はなくなりました (前述のとおり)。 ただし、クエリー結果は以前の MySQL バージョンとは異なる場合があります。 特定のソート順序を生成するには、ORDER BY 句を指定します。 dev.mysql.com 3. 予約語 8.0では、 新たな予約語 が追加されましたが、 TUNAGで使用しているRailsでは、テーブル名とカラム名をバッククォートでエスケープしてくれるので影響はありませんでした。 dev.mysql.com アップグレード方法の検討 最終的に 「インプレースアップグレード」 を選択しました。 Blue/Greenデプロイ(以下、B/Gデプロイ)も1つの選択肢としてあったのですが、既存のクラスターパラメータグループで binlog_format=MIXED に事前に変更する必要があり、8.0用の新規作成するカスタムパラメータグループで binlog_format=MIXED と設定することとし、今回はB/Gデプロイの選択を見送りました。 パラメータグループの作成 次に8.0用のカスタムパラメータグループの作成です。 まずは、既存の5.7のカスタムパラメータグループのうち、デフォルトの値からカスタムされているパラメータの確認を行いました。 確認の際は、AWS CLIとjqコマンドを用いてデフォルトパラメータグループとカスタムパラメータグループの内容をjsonファイル化し、両ファイルの差分をdiffで確認しました。 この方法を取ることでカスタマイズをしている内容を漏れなく確認することができました。 $ aws rds describe-db-cluster-parameters --db-cluster-parameter-group-name default.aurora-mysql5.7 | jq . > default-cluster-mysql5.7.json $ aws rds describe-db-cluster-parameters --db-cluster-parameter-group-name aurora-cluster-mysql5.7 | jq . > production-cluster-mysql5.7.json $ git diff default-cluster-mysql5.7.json production-cluster-mysql5.7.json $ aws rds describe-db-parameters --db-parameter-group-name default.aurora-mysql5.7 | jq . > default-instance-mysql5.7.json $ aws rds describe-db-parameters --db-parameter-group-name stats-aurora-mysql | jq . > production-instance-mysql5.7.json $ git diff default.aurora-mysql5.7.json production-instance-mysql5.7.json パフォーマンス確認 アップグレード済みのステージング環境の運用 まずは、何台かのステージング環境を8.0にアップグレードした状態で、他のエンジニアメンバーに普段の開発で使ってもらう運用を2週間程度行いました。 これは、普段の開発の中で著しくパフォーマンスが遅くなる画面やAPIを見つけることを狙いました。 k6とDatadogを用いた主要画面のパフォーマンス確認 次に、コアな機能のAPIに対して、関連するレコード数が最多のケースでパフォーマンス確認を行いました。 負荷試験ツールのk6を用いて、検証条件のケースをk6のシナリオに記述、リクエストを実行し、SQLの実行時間や画面の描画時間などの確認をDatadogのAPMを用いて行いました。 この確認の結果、主要なAPIの一部で大幅なパフォーマンスの劣化が起きることが判明しました。 k6.io 実行計画の変化の確認 8.0へのバージョンアップでパフォーマンスが大幅に劣化したSQL(一部省略)が下記となります。 SELECT `table_a`.`id`, -- 省略 FROM `table_a` LEFT OUTER JOIN -- 省略 WHERE -- 省略 AND `table_a`.`id` IN ( SELECT `table_a`.`id` FROM `table_a` LEFT OUTER JOIN -- 省略 WHERE -- 省略 AND ( `table_a`.`id` IN ( SELECT -- 👈 サブクエリ1 `table_b`.`table_a_id` FROM `table_b` WHERE -- 条件1(省略) ) OR `table_a`.`id` IN ( SELECT -- 👈 サブクエリ2 `table_c`.`table_a_id` FROM `table_c` WHERE -- 条件2( 省略) ) OR `table_a`.`id` NOT IN ( SELECT -- 👈 サブクエリ3 `table_c`.`table_a_id` FROM `table_c` WHERE -- 条件3(省略) ) ) ) ORDER BY `table_a`.`id` DESC ; 5.7では、サブクエリ1, 2, 3の部分に対してEXISTS戦略による最適化が適用されていたのですが、 8.0にバージョンアップすると実体化を使用した最適化(materialization)が適用され、実行計画が変わることが分かりました。 サブクエリの最適化状況を確認する場合は、EXPLAIN後にSHOW WARNINGSステートメントで発行できる追加情報もしくはオプティマイザトレースを参照すると確認することができます。 EXPLAIN SELECT * FROM test_table; SHOW WARNINGS; dev.mysql.com SET optimizer_trace= ' enabled=on ' ; EXPLAIN SELECT * FROM test_table; SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; dev.mysql.com SQLの改善 パフォーマンス劣化の原因となったSQLについては、様々な対策方法を検討した結果、 最終的にEXISTS句とNOT EXISTS句を明示的に使用するように変更することで、5.7の実行計画を維持できるようになりました。 SELECT `table_a`.`id`, -- 省略 FROM `table_a` LEFT OUTER JOIN -- 省略 WHERE -- 省略 AND `table_a`.`id` IN ( SELECT `table_a`.`id` FROM `table_a` LEFT OUTER JOIN -- 省略 WHERE -- 省略 AND ( EXISTS ( SELECT 1 FROM `table_b` WHERE (`table_a`.`id` = `table_b`.`table_a_id`) AND -- 条件1(省略) ) OR EXISTS ( SELECT 1 FROM `table_c` WHERE (`table_a`.`id` = `table_c`.`table_a_id`) AND -- 条件2(省略) ) OR NOT EXISTS ( SELECT 1 FROM `table_c` WHERE (`table_a`.`id` = `table_c`.`table_a_id`) AND -- 条件3(省略) ) ) ) ORDER BY `table_a`.`id` DESC ; 本番アップグレード作業 本番のアップグレードは、上記のSQL改善を行った上で、ステージング環境で確認した手順で作業を行いました。 アップグレード作業と合わせてCA証明書の更新も行いました。CA証明書は、負荷影響も考慮して既存のrds-ca-2019と同じアルゴリズムが使用されているrds-ca-rsa2048-g1を選択しました。 振り返りと今後について 事前の検証を入念に行ったこともあり、無事に何事もなくバージョンアップ作業を終えることができました。 今回作成した8.0用のパラメーターグループで binlog_format=MIXED を設定したことにより、 今後はB/Gデプロイを利用することができるため、次回はB/Gデプロイを試そうと思っています。 また、メジャーバージョンアップの検証手順が今回確立でき、別のデータベースのAurora PostgreSQL 11.17 から Aurora PostgreSQL 15.4 へのアップグレード作業を控えていたため、 早速手順を流用し先日アップグレードを無事終えました。 まとめ 本記事では、Amazon Aurora MySQL v2(MySQL5.7互換)からv3(MySQL8.0互換)へのアップグレードについて、 検証内容などアップグレード完了に至るまでの過程を紹介しました。 最後に、スタメンではエンジニアを募集しています。興味をもっていただけましたら、ぜひ下記からご応募ください。 herp.careers
こんにちは、エンジニアの @natsuokawai です。 先日開催された Kaigi on Rails 2023 にて、スタメンとしてスポンサーブースを出展しておりました。 遊びに来てくれた皆さんありがとうございました! スタメンのブース その際 Ruby on Rails に関するクイズを出題していたので、本記事ではそれらの解説を簡単にしたいと思います。 問題1 以下のコードを Rails 6.1 以前で実行した時の結果は?(Task は ActiveRecord オブジェクト、id は integer 型のカラムとする) task1 = Task .create( title : " Task 1 " ) task2 = Task .create( title : " Task 2 " ) tasks = Task .where( id : task1.id).merge( Task .where( id : task1.id..task2.id)).pluck(& :title ) 選択肢: ["Task 1"] ["Task 2"] ["Task 1", "Task 2"] [] 解説 正解は 1. ["Task 1"] です! 弊社のサービス TUNAG(ツナグ)の Rails バージョンを 6.1 → 7.0 に上げる際に引っかかったので、今回問題にしてみました。 Rails 6.1 以前では、 relation1.merge(relation2) とした際、relation1 と 2 の条件が AND で結合されたり、relation2 の条件で上書きされたりと、一貫性のない仕様になっていました。 上記コードでは AND で結合されるため 、 tasks を取得する際に発行される SQL は SELECT "tasks".* FROM "tasks" WHERE "tasks"."id" = 1 AND "tasks"."id" BETWEEN 1 AND 2 となり、 Task 1 のみが取得されます。 ちなみに Rails 7.0 以降では常に relation2 の条件で上書きされるように変更されたので、 ["Task 1", "Task 2"] になります。 詳細: Deprecate inconsistent behavior that merging conditions on the same column by kamipo · Pull Request #39328 · rails/rails · GitHub バージョンを上げたときのブログ記事: TUNAG の Rails バージョンが 7.0 になりました - stmn tech blog 問題2 以下のアソシエーションを持つモデルにおいて、正しく動作しないものはどれか? class User < ApplicationRecord belongs_to :company end class Company < ApplicationRecord has_many :users validates :name , presence : true end 選択肢: 1. User.joins(:company).where(company: {name: 'stmn'}).count 2. User.preload(:company).where(company: {name: 'stmn'}).count 3. User.eager_load(:company).where(company: {name: 'stmn'}).count 4. User.includes(:company).where(company: {name: 'stmn'}).count 解説 正解は(正しく動作しないのは) 2. User.preload(:company).where(company: {name: 'stmn'}).count です! N+1 問題の解消でお世話になるメソッドたちについてのクイズでした。 こちらは基礎的な問題だったので、正解者も多かったですね。 実際にコードを実行してクエリを見てみるとわかりやすいですが、1、3、4 は companies を JOIN をしているため、 where メソッドにて companies の絞り込みを行うことができます。 preload は JOIN しないので、companies を参照しようとしてエラーになります。 includes は内部で preload と eager_load を呼び分けるメソッドです。上記のような companies の絞り込みクエリなどがない場合は preload と同じ挙動になります。 参考: activerecord/lib/active_record/relation.rb#L915 activerecord/lib/active_record/relation.rb#L735 問題3 下記のようなコントローラーが定義されているとき、コールバックに定義されたメソッドが実行される順番として正しいものはどれか? class ParentController < ApplicationController before_action :parent_before prepend_before_action :parent_prepend_before end class ChildController < ParentController before_action :child_before prepend_before_action :child_prepend_before end 選択肢: 1. parent_prepend_before → child_prepend_before → child_before → parent_before 2. parent_prepend_before → child_prepend_before → parent_before → child_before 3. child_prepend_before → parent_prepend_before → child_before → parent_before 4. child_prepend_before → parent_prepend_before → parent_before → child_before 解説 正解は 4. child_prepend_before → parent_prepend_before → parent_before → child_before です! コールバックの実行順序に関する問題です。レビューでこんなコードが来たら通さないよねと言われた回数第一位です (それはそう) 継承関係にあるとき、親→子の順でコールバックが実行されます。 また、 prepend_before_action は before_action の配列の先頭にメソッドを追加するので( Array#prepend と同じ)、今回のような場合は親が先に before_action の前に追加し、子がさらにその前に追加します。 参考: actionpack/lib/abstract_controller/callbacks.rb#L171-L175 activesupport/lib/active_support/callbacks.rb#L744 問題4 update! を実行したときのクエリの発行回数は Rails 7.0 以前と 7.1 でそれぞれ何回か?( Post.last で発行されるクエリは考慮しない) class User < ApplicationRecord has_many :posts end class Post < ApplicationRecord belongs_to :user end post = Post .last post.update!( title : " Rails is the best " ) 解説 正解は 「7.0 以前は 2 回、7.1 以降は 1 回」です! こちらもまた Rails バージョンによる差異の問題になります。 optional: true ではない belongs_to 関連が定義されているとき、Rails 7.0 以前は update! のタイミングで関連先のレコードをロードしていました。 7.1 以降では関連先に変更がなければロードしないようになり、クエリの発行回数が減っています。 詳細: Avoid validating `belongs_to` association if it has not changed by fatkodima · Pull Request #46522 · rails/rails · GitHub まとめ Kaigi on Railsのコアコンセプトは 「初学者から上級者までが楽しめるWeb系の技術カンファレンス」ということで、馴染みのあるメソッドを中心に選んでみました。 ブースではこのクイズを起点にして、Rails を始めたばかりの方やコミッターなど幅広い方と盛り上がることができ、とても楽しかったです。 最後に、スタメンではエンジニアを募集しています。Rails 好きな方はもちろん、フロントエンドやモバイルアプリなど各技術領域で募集中なので、ご応募お待ちしております! herp.careers
株式会社スタメンは、2023年10月27日、28日の2日間、浅草橋ヒューリックホールにて開催される「Kaigi on Rails 2023」に、Rubyスポンサーとして協賛し、ブース出展などを通してイベントを盛り上げます。 kaigionrails.org TUNAGの開発では 2017年のサービス提供開始当初から Ruby on Rails を採択してきました。 直近では、 10月6日にリリースされた Rails 7.1 への移行を当日中に完了 するなど、スピード感を持って Rails での開発に取り組んでいます。 本エントリーでは、そんな我々がこの度の Kaigi on Railsに初協賛するにあたって準備を進めている、スポンサーLTや、スポンサーブース、ノベルティ、フライヤーについてをご紹介いたします! スポンサーLTについて スポンサーLTには、Rails 7.1 アップデートの際には主力を担った、プラットフォーム部の河井が登壇いたします。 登壇者:河井 夏生 日時:2023年10月28日 16:40〜(5分) 場所:Hall A 詳細: https://kaigionrails.org/2023/schedule/#day2 スポンサーブースやノベルティ、配布物のご紹介 オフライン会場のスポンサーブースは、 カンファレンスでのブース出展の際 にはお馴染みのスタメンカラーのテーブルクロス、バナースクリーン、のぼり旗。 スタメンメンバーは、黒地に白いスタメンロゴのTシャツが目印です! Rails にまつわるアンケートやクイズを企画しており、ご参加された皆様に楽しんでいただけるようにエンジニアメンバー一丸となって内容を考えています! アンケートかクイズかのいずれかに回答いただけると、もれなくもらえるノベルティ、抽選で当たる豪華ノベルティなども用意する予定です。 6枠のみのRubyスポンサーの一角ということで、気合を入れて準備しているので、当日は是非お立ち寄りください! またスポンサーブースや配布物の共有スペースでは、開発組織についての簡単な紹介しているフライヤー配布する予定です。 おわりに Kaigi on Rails の2日間を通して、Railsエンジニアの皆様だけでなく、様々な技術領域のWebエンジニアの皆様とお会いできることを心待ちにしております! カンファレンス情報 名称 : Kaigi on Rails 2023 開催日時 : 2023年10月27日(金)〜10月28日(土) 場所 : 浅草橋ヒューリックホール&カンファレンス + オンライン 公式サイト: https://kaigionrails.org/2023/ 参加方法や詳細については、 公式サイト をご覧ください。 採用情報 スタメンでは、Railsエンジニアに限らず、全ての領域でエンジニアを募集しています。もし興味を持っていただけたら、下記からご応募ください! herp.careers
こんにちは、スタメンの @natsuokawai です。 今日は Rails World 2023 @アムステルダム の Day 2 ですね。 X(Twitter)のポストを見ていても現地の熱が伝わってきて、私も来年の Rails World にはぜひ参加したいと感じています。 さて、そんな Rails World の開催中、10月5日の日本時間の17時過ぎに、Rails の最新バージョン 7.1 が公開されたことを知ります。 その時点ですでに弊社のサービスTUNAG(ツナグ)も Pre-release バージョンでリリース準備を進めていたところでしたので、発表のタイミングに戸惑いながらも、無事 7.1 に上げることができました。 バージョンを 7.1 に上げたときの Slack 投稿 前回のブログ でもお伝えした 6.1 から 7.0 まで上げる作業には、リリースから500日以上経過した状況で着手し、そこから3週間ほどの時間を要しました。 一方で今回は、正式版リリース後1.5時間で追従することができ、作業にかかった時間は3日ほどと、大幅な時間短縮を実現できています。 これでタイムリーに最新版に追従できている状態になったので、今後は計画的にバージョンアップを行っていきたいです。 常に最新版を使っていれば更新時の差分も少なく、大規模なプロジェクトの必要もなく日々の作業の中で追従しやすくなります。 その分不具合に遭遇するリスクも増えるかもしれませんが、問題の改善や報告を通じて Rails コミュニティにフィードバックすることで、コミュニティへの貢献もしていきたいと思っています。 最後に宣伝をさせてください。 スタメンは今月末の Kaigi on Rails 2023 に Ruby スポンサーとして協賛 しています。 私も現地にいますので、会場でお会いできた際はぜひお話ししましょう! また、スタメンではエンジニアを募集しています。興味を持っていただけましたらぜひ下記からご応募お待ちしております。 herp.careers
こんにちは、CTO室 エンジニアHRチームのがせ( @gasekao96 )です。 スタメンでは、 "Expand your Horizon." をテーマに8月と9月にそれぞれ2週間のエンジニアサマーインターンを実施しました。 インターンには合計8人のメンバーが実際の開発チームに参加。 MacBook Air M2 24GB、32インチ 4Kモニター、各自が希望するマウス/トラックパッド、ハーマンチェアーなどの開発デバイスが貸与され、社員と同じ環境で、そしてプロダクションコード上で開発タスクに取り組みました。 本エントリーでは、プログラムの内容と参加した学生の感想をご紹介します。 インターン初日 初日の出社後は全員集まって自己紹介や、CTO松谷からの事業説明をうけるオリエンテーションを行いました。 事業説明では、 TUNAG ユーザーの解像度を上げてもらうために導入事例動画を交えて紹介がありました。 オリエンテーション後は、”ニューメンバーランチ制度”を利用して、各開発チームメンバーとランチに出かけました。 この制度は1人のニューメンバーに対して2回利用することができます。2回目のニューメンバーランチでは、他の開発チームやビジネスサイドのメンバーとも出かけて、技術や趣味の話で盛り上がっていたようです。 チーム横断で行われたランチの様子 午後からは、いよいよ開発チームとしてのキックオフ。インターン期間の目標設定、チームビルディングや開発環境の構築を行いました。 2日目以降 開発期間 2日目以降はいよいよ、 フロントエンドリプレイス やパフォーマンス改善といったそれぞれの開発タスクに着手し始めます。 開発期間に入ってからは、続々とインターンメンバーによるPRが作成されていました。 メンターからのコードレビューは妥協無しで厳しく行われ、時にはチーム外のメンバーからもレビューをもらう機会があったようです。 インターン期間中、日々の気づきや共有したいトピックがあればTUNAGの日報に投稿してもらっていました。 PR粒度やPR前の自己レビューといったチーム開発をする上での心掛けや、技術課題に詰まった際に前提を見直す/立ち返る、また質問の仕方を工夫するなど、日々の学びがたくさん共有されていました。インターン生だけでなく、スタメンのエンジニアメンバーも改めて自身の振る舞いを見直すいいきっかけになったのではないかと思います。 さらに、日報の中で近隣のおすすめ飲食店をスタメン社員に質問したりと積極的にコミュニケーションを取る姿も見られ、日々投稿へのコメントも盛り上がっていました。 また開発以外では、CEOとの対面1on1や懇親会、部活に所属するメンバーなどの有志によって企画されたゲーム大会などが行われました。 ランチタイムを利用して行われたゲーム大会では、参加せずにご飯を食べながら見守るメンバーも多数。 開発する中では話す機会の少ないビジネスサイドのメンバーや役員メンバーとのコミュニケーションを通じて、スタメンの雰囲気やカルチャーも存分に感じてもらえたようです。 部署を超えてゲームに興じるみなさん インターン最終日 最終日には、オンラインでエンジニアメンバー全員の前での成果発表会が行われ、実装した機能・ページやパフォーマンス改善の成果、得た知見や開発の苦労などを各々のスタイルで発表が行われました。 発表の様子 プロダクトへの理解度やリリースまで完遂する実装速度の速さには、スタメンのエンジニアメンバーから驚きのコメントがたくさん寄せられました。 成果発表後はメンターとフィードバック面談を行い、インターン完走を賞賛するスタカネを鳴らし、全ての工程が終了です。 初日は緊張気味の堅い表情の皆さんでしたが、最終日にはリラックスした表情で過ごしている姿がとても印象的でした。 インターンメンバーからの感想 最後に、インターンメンバーからもらった感想をご紹介します。 フロントエンドリプレイスプログラム 土佐さん: フルタイムのインターンに参加させていただくのは今回が初めてで、時間内に与えられたタスクができるのかとても不安でしたが無事インターンを終えることができました。 メンターの方をはじめ、エンジニアの皆様からは技術的に学ぶことも多く、今後の私の糧にさせていただきたいと思います。 また、ビジネスサイドの社員さんやインターン生と関わることも多く、普段しないような話もたくさんさせていただきました。 2週間という短い期間ではありましたが、社内雰囲気や、社員さんの人となり、また技術的なことなど本当に多くのことを学べ、実りあるインターンとなりました。 この結果にはスタメンの社員さん皆様の協力無くしてはできなかったことだと思います。 改めまして2週間という短い期間でしたがお世話になりました。 (土佐さんは、自身のブログでもインターンについての記事を投稿してくれました。取り組みや、感じた社内の雰囲気についてもかなり深く書いていただいたので、ぜひご一読ください!) ブログ: 株式会社スタメンのインターンシップに参加してきました! フロントエンドリプレイスプログラム 大橋さん: あっという間の2週間でしたが、インターンに参加出来てとてもよかったと感じています。出社する形でのインターン自体あまり経験がなかったのですが、おでんチームを含めた皆さんがあたたかく迎え入れてくれたおかげで、すぐに打ち解けることが出来ました。 今回のインターンのテーマとして、技術面の成長とコミュニケーションということを軸にしていましたが、どちらも学ぶことができました。技術面に関して細かい部分は省きますが、アプリを開発する際の生産効率の上げ方、拡張性、保守性など、実際に運用されているTUNAGの開発に携われたからこその学びが多くありました。コミュニケーションに関しても、懇親会など会社側で用意してくれた企画などもあり、エンジニアサイドに限らず多くの人とコミュニケーションをとることが出来てよかったです。また、CEO、CTOと直接話せる機会もあり、2週間でやれることは全てやれたというのが率直な感想です。最後に、今回のインターンは遠方から参加しましたが、名古屋のグルメなどを堪能できたのもよかったです。 フロントエンドリプレイスプログラム 中村さん: 技術的にも、人としても成長できたインターンになりました。 技術的には、特にフロントエンドのテストに関して大きく成長できました。メンターの方にはかなり丁寧にコードレビューをいただき、それを通してテストケースの網羅性や特定のケースの見落としについての意識が向上しました。インターン参加前はフロントエンドのテストを書いたことがありませんでしたが、インターンを通じて全てのテストケースを網羅する重要性を認識し、その意識が高まりました。 人としての成長として、チームで開発する際の意識が改善されたと思います。初見の人にとって、このPRではどこまでを実装したのか、このコンポーネントは何に使われるかなどを分かりやすく書くことを意識できるといいねといったレビューをいただきました。チームでの時間を増やすためにも、初見の人にとって分かりやすい情報共有を心がけるようになりました。 スタメンさんのインターンに参加して、多くのものを吸収できたと思います。今回身につけたことを今後の開発にどんどん活かしていきたいです。 フロントエンドリプレイスプログラム Kagariさん: 開発しやすいと思ったのと社員の人柄がとても好きだなと感じました。というのも開発の中で疑問に思ったことや改善点を本音でぶつけ合うことができ、制作向上に繋がっていると感じたのと開発以外でも雑談を行ったり冗談を言い合える関係で楽しめたためです。 また私が携わったのはフロントエンドのリプレイスでアーキテクチャやコードの品質周りで深く学べたので参加してよかったと思いました。 パフォーマンス改善プログラム 大石さん: 今回のサマーインターンでは、実際に運用されていて、利益を上げているプロダクトのパフォーマンス・チューニングを体験させていただき、非常に貴重な経験をさせていただきました! 実際にユーザーがいるサービスであるからこそ、表示速度を削った際のインパクトを想像しやすく、「ユーザーのために」とモチベーションが向上していくのを感じました。 これまではサービスやプロダクトを「作っていく」過程を多く経験してきましたが、サービスのフェーズが違えばやることが違うことや、運用フェーズで組織内で行っていることの解像度が高まりました。 また、パフォーマンス・チューニングを行うことで可読性が下がるため、トレードオフの関係になっていることや、その中でもパフォーマンス・チューニングをやってよかったと思えるように、アクセスログなどを用いてデータをもとに意思決定することの大切さに気づくことができました。 ランチや懇親会などで、ビジネス職の方と関わることもでき、スタメンの文化や他業種のことも知ることができました! パフォーマンス改善プログラム 近藤さん: 今までバックエンドを触った経験はなかったのですが、メンターの方に沢山サポートいただき、パフォーマンス改善を行うことができました。 定量的に成果が見えるので、とても楽しい2週間でした。 2週間のプログラムを終えてスタカネを鳴らす様子 採用情報 株式会社スタメンでは、全てのポジションでエンジニアを募集しています。 興味を持っていただけたら、下記のリンクからご応募ください! herp.careers
こんにちは、リファクタリング大好きな ミノ駆動 です。 株式会社スタメン では、企業エンゲージメント構築サービス TUNAG (ツナグ)の技術的負債解消と今後の持続的成長のため、ドメイン駆動設計(DDD)の導入を検討しています。 ところでDDDはとかく理解しづらく、何のためのDDDなんだという議論になりがちです。この記事では、DDDの 真の主人公コアドメイン を中心に、DDDが何を解決するものなのか、 全体像 を改めて整理します。 この記事で扱う内容 DDDが解決したい課題と解決方法の全体像。 この記事では扱わない内容 設計パターンの実例などの実装詳細。 大事な前提 〜利益を得るためのサービス開発 会社でのサービス開発は、趣味や道楽でやるものでしょうか。違いますね。ビジネスとして、企業活動としてサービス開発しています。当たり前の話ですが、利益を得られるように開発しなければなりません。 ドメイン駆動設計は、 継続的に利益向上するための設計手法であり思想 だと考えます。理由を以下に説明します。 DDDが解決したい課題 DDDが解決したい課題は、大きくは以下2つです。 顧客満足度低下による利益減 :「今作ってるもの、本当に顧客に喜ぶものなの…?」と疑問に感じながらの開発が常態化。そしてやっぱり顧客にウケない。顧客離れが起き、稼げなくなる。 開発コスト増大による利益減 :「この機能を実装したら顧客にウケることは間違いないんだ、でも技術的負債があまりにも酷すぎて機能追加が難しすぎる!」……開発生産性が悪化し、リリースまでの時間やコストが増大。折角の利益が膨大な開発コストによって相殺されてしまう。 DDDが目指す世界 上記課題が解決された世界は次のようになるでしょう。 機能性向上 :サービスの 競争優位性 がチームに理解されており、競争優位性を高め続けられている状態。継続的に利益が増大し続けている状態。 変更容易性向上 :技術的負債に苦しめられることがなく、いつでも 迅速に 機能を実装しリリースし続けている状態。 「ドメイン」とは まずこの記事を理解する上でドメインという概念の理解が必要です。 世の中には解決が必要な問題がいろいろあります。「楽しい時間を過ごしたい」「美味しいものを食べたい」「新しい服がほしい」など、いわゆる人の欲求ですね。そうしたいろんな問題の中で、特定の範囲の問題を 問題領域 と呼びます。 次の図における楕円が問題領域の一例です。ここでの問題領域は「楽しい時間を過ごしたい」とします。こうした問題領域のことを ドメイン と呼びます。 問題領域(ドメイン)と解決領域 さて問題に対し、世の中にはさまざまな解決方法が用意されています。この「楽しい時間を過ごしたい」という問題領域には、例えば世の中にはスポーツ観戦や旅行、ゲームなどがあります。こうしたものを、問題領域に対して 解決領域 と呼びます。 問題領域の中にさらに個々の問題領域がある 次の図はX(旧Twitter)の例です。 X(旧Twitter)の問題領域と解決領域(筆者の想像) 「コンテンツを楽しみたい」という全体の大きな問題に対応する解決領域がXだと仮定します。Xには、さらに個々の問題領域があると考えられます。「話題性のあるコンテンツを見たい」「情報収集したい」「TLに流れてくるコンテンツを健全に保ちたい」…。こうした個々の問題領域に対して、Xでは上図の赤四角で示すような機能が用意されています。 どこに開発リソースを投じるか? お金は無限にありません。有限です。 会社のお金も有限です。当然開発予算も有限です。限りある予算の中で、顧客が価値を感じるサービスを作っていかなければなりません。 上で説明したように、サービスには対応する問題領域がいろいろあります。その中でどの領域に投資しますか? 選択と集中 費用対効果を高める 特定の事業分野に経営資源を集中することを 選択と集中 といいます。経営戦略上、 自社が得意とする、差別化可能な、競争優位性のある分野 に集中投資することで費用対効果を高めます。 サービス開発も同様に選択と集中が必要です。不得意な、競争優位性のない分野に開発コストをかけていいものでしょうか…? サービスの魅力が大して高まらないでしょうし、その間にも競合他社に出し抜かれるでしょう。 皆さんも思い浮かべてください。自社サービスにおける、差別化可能な競争優位性のある分野は何でしょうか…?? それが コアドメイン です。 中心的事業領域 コアドメイン コアドメイン はDDDでいの一番で登場する、超重要概念です。DDD序文からの引用です。 DDDを駆動している原則は次の3つだけです。 ・ コアドメイン に集中すること。 ・ドメインの実践者とソフトウェアの実践者による創造的な共同作業を通じて、モデルを探究すること。 ・明示的な境界づけられたコンテキストの内部で、ユビキタス言語を語ること。 コアドメインとは中心的価値を発揮する事業領域のことです。競争優位性があり、他社との差別化が図られます。「その部分がなくなると商売が成り立たなくなってしまう」ぐらい重要な領域です。 コアドメインとは、いわゆる商品の「ウリ」の部分です。昔から営業さんは「うちの商品は◯◯がウリなんですよー」と営業をかけていましたよね。 コアの例 他社さんのコアと思わしき分かりやすい例です( ※あくまで私個人の見解です )。 ハッピーターン:亀田製菓さんのお菓子。粉(ハッピーパウダー)がコアでしょう。粉250%増量タイプも販売されています。 スプラトゥーン:任天堂さんのインクバシャバシャゲーム。インクがコアでしょう。陣地確保、移動経路開拓、高速移動、攻撃、補給、隠匿などあらゆる役割をインクが担っています。インクを中心にゲームのあらゆる要素がデザインされています。 コンビニやECサイトは、皆さん普段から利用されていると思います。商品を見たとき、「この商品の競争優位性は何だろう、何を差別化してるんだろう、何をウリにしているんだろう」と考えてみるのが良いでしょう。 コアドメインの価値を高められない! さて、ソフトウェアではない話が多少混じりましたが、ソフトウェアの話に戻ります。サービス開発では、コアドメインの価値を高めようにも高められない、価値向上を妨害するさまざまな要因があります。 ◆妨害要因1 : そもそもコアドメインが何か分からない そもそも自分たちのサービスのコアドメインが分からない、何が競争優位性なのか分からないといった状況です。 スタートアップ直後は何をやりたいのか明確ですが、サービスが大きくなってくると何が自分たちの強みなのか分からなくなってくる場合がよくあります。こうなると、その場の思いつきで五月雨式に機能が実装されがちになります。顧客のニーズをなかなか満たせず、競争優位性が育ちにくくなり、サービスの価値は低減する一方になります。 ◆妨害要因2 : サブドメインのロジックとの混在、密結合 コアドメイン以外の問題領域をサブドメインと呼びます。サブドメインの分かりやすい例は決済。多くのECサイトには決済機能があります。決済の不具合は重大なインシデントとなるので、決済は慎重に作り込む必要があります。では決済はコアドメインか? といえばそうではなく、どのECサイトにも実装されているもので、決済自体はサービスの競争優位性とはなりません。 ところで現実のサービスでは、サブドメインのロジックとコアドメインのロジック同士が混在したり、密結合になっている、ということが珍しくありません。このような構造では、コアとサブの見分けが難しくなったり、サブのロジックが邪魔でコアのロジックを上手く変更できない、といったことが発生します。 ◆妨害要因3 : コアドメインロジックの散在、低凝集 コアドメインのロジックがソースコード全体のあちこちに散在し、低凝集に陥っているケースです。 コアのロジックを変更しようにもあちこちにバラバラに実装されているために、コアのロジックを探し回り影響範囲を調査するだけで莫大な時間がかかります。 ◆妨害要因4 : DBロジックやViewロジックとの密結合 ドメインのロジックはアプリケーションロジックです。問題領域の問題を解決するルールや概念を実装したものです。以下がその例です。 X(旧Twitter) : リツイートすると、フォロワーのタイムラインにリツイートしたツイートが反映される。 スーパーマリオ:ダッシュしながらジャンプすると、高くジャンプできる。 エクスプレス予約:新幹線の指定席は、乗車日の1ヶ月前から予約可能。 一方で、DBの責務は永続化です。Viewの責務は表示です。ドメインのルールとは関心事も責務も別です。ところがDBロジックやViewのロジックがドメインロジックと密結合になっているケースがあります。こうした密結合はコアドメインのロジック変更を困難にしてしまいます。 列挙したこれらの妨害要因があると、コアドメインの競争優位性を高めようにもなかなか高めることができません。あまりに酷いと商売が成り立たなくなります。 コアドメインの価値を高めるさまざまな手法 上記の妨害要因があるとコアドメインは「汚れている、汚染されている」状態になります。汚れているとは、 コアドメインがサブドメインのロジックで汚れている コアドメインがDBロジックで汚れている といった事象です。 こうした汚れを取り除き、綺麗に清潔に保つことで初めてコアドメインは価値向上の速度を上げることができます。汚れを取り除いたり、綺麗にしたり、コアドメインの価値を高めるさまざまな手法がドメイン駆動設計では解説されています。ドメイン駆動設計は、 コアドメインの価値を高めるために考えられた設計思想であり設計手法 と言えるでしょう。 ◆手法1 : 境界付けられたコンテキスト 問題領域ごとにサブシステムに分割する考え方です(※より正確には、一枚岩モデルだとシステム構造が混乱してしまうため、文脈ごとに特化したモデルを設計し、各特化型モデルの適用可能範囲を定めたものが境界付けられたコンテキストです)。 手前味噌ですが、境界付けられたコンテキストについては下記動画とスライドが分かりやすいでしょう。 www.youtube.com speakerdeck.com (※悲鳴が大音量で流れるのでご視聴には十分注意) クソコード動画「一枚岩モデル」 #AWSDevDay pic.twitter.com/NwfOmXWy6F — ミノ駆動 (@MinoDriven) 2022年11月9日 境界付けられたコンテキストの設計には何がコアで何がサブなのか事業分析が必要です。そしてコアドメインとサブドメインをシステムレベルで分離隔離します。そしてコアドメインの価値を簡潔に説明する ドメインビジョン声明文 を作成し、何がコアであるかを明示します。 こうすることで、以下の利点が生じます。 何がコアで何がサブか、システム構造レベルで見分けがつくようになる。どのロジックが競争優位性を発揮するのか理解できるようになる。 コアがコアロジックだけの純粋な高凝集になり、サブと疎結合になる。 これらのメリットにより、上で挙げた 妨害要因1〜3が解消されるでしょう 。 ◆手法2 : 階層型アーキテクチャ ドメイン関心事や技術関心事など、関心事単位で階層ごとに分離します。次の図はクリーンアーキテクチャの思想でドメイン層を他の関心事から分離したアーキテクチャ図です。 %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#000000', 'primaryTextColor': '#6360DC' } } }%% graph TD subgraph A["外界"] View層 DB end subgraph B["入出力変換"] Controller層 Infrastructure層 end subgraph C["ユースケース解決"] UseCase層 end subgraph D["ドメインルール解決"] Domain層 end View層 --- Controller層 Controller層 --- UseCase層 DB --- Infrastructure層 Infrastructure層 --- UseCase層 UseCase層 --- Domain層 Infrastructure層 --- Domain層 例えばDBやファイルなどの永続化に関するロジックは、Infrastructure層にてRepository interfaceの実装クラスにカプセル化します。Repositoryは永続化知識のカプセル化に用いられるパターンです(※正確にはオブジェクトのコレクションをエミュレートする仕組みであり、DBやファイルは仕組みの一形態ににすぎない)。 このように隔離することで、コアロジック(上図のDomain層)が他のロジックに汚されずに済みます。これで 妨害要因4が解消されます。 ◆手法3 : Entity, ValueObject, Aggregateなどの設計パターン 上記手法2まで説明した考え方により、コアドメインのロジックを隔離できました。 さて、隔離できたはいいものの、コアドメインのロジックが複雑化し技術的負債になってしまっては開発生産性を出せません。負債化しないよう、適切な関心事単位で疎結合高凝集にします。そこで登場するのがドメイン駆動設計に登場するValueObject、Entity、Aggregateなどの設計パターンです。ここまで来てようやっと見覚えのあるものが登場するんですね。 %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#000000', 'primaryTextColor': '#6360DC' } } }%% classDiagram namespace Core { class Repository { <<interface>> } class Aggregate class Entity class ValueObject } Repository ..> Aggregate Aggregate --> Entity Aggregate --> ValueObject Entity --> ValueObject これらのパターンを駆使し、変更容易性が向上するよう内部構造を整理していきます。また、ここに挙げた以外にも役立つ設計があるならどんどん使って良い、というのがドメイン駆動設計の見解です。 ここまで紹介した手法1〜3、 コアの邪魔になるものをひたすら分離隔離していくための手法 であることが理解できると思います。 ◆手法4 : 蒸留、深いモデル ここまでの手法は主にコアドメインの技術的負債を解消し、 変更容易性 (ソフトウェア品質特性の一種、なるべくバグを埋め込まずどれだけ素早く正確に変更できるかを示す度合い)を向上させる観点の設計技法です。しかしDDDはこれだけではありません。 DDDではリファクタリングを繰り返し、ドメインモデルを改良することの重要性を説いています。なぜでしょうか。改良を繰り返していくと「 このドメインの問題を解決するには、実はこういう構造のモデルが相応しいんじゃないか!? 」と気付く瞬間が訪れます。それが深いモデルです。 深いモデル とは、深い理解に基づいた本質を表現するモデルとDDDでは説明されます。深いモデルにより 機能性 (ソフトウェア品質特性の一種、顧客ニーズをどれだけ満たすかを示す度合い)の向上が期待できます。深いモデルは一朝一夕に発見または設計できるものではありません。重要なところに焦点を当てて改良を繰り返す、蒸留というプロセスを経る必要があります。 蒸留には、ドメインの深い理解の獲得が必要なので、ドメインすなわち事業課題やその領域のオキテに詳しい人( ドメインエキスパート )との対話が必要です。そして事業内容を理解するためにもユビキタス言語の策定が必要です。 ユビキタス言語 とは、ステークホルダー全員が理解可能な言葉であり、境界付けられたコンテキストごとに意味が一意に定まる言葉です(文脈によって言葉の意味が違うので)。ユビキタス言語は単に「全員が理解できる言葉」ではなく、 目的を表現する言葉 です。DDDの「意図の明白なインターフェース」では次のように説明しています。 クラスと操作には、その効果と 目的 を記述する名前を付け、約束したことを実行する手段には言及しないこと。(中略)こうした名前はユビキタス言語に従っていなければならない。(中略)そうしたインターフェースには、手段ではなく 目的 の観点から語らせなければならない。 (『エリック・エヴァンスのドメイン駆動設計』p.252より引用) この説明から、ユビキタス言語が目的を表現する言葉であることが分かります。そもそもシステムは何らかの目的を達成するために開発される手段であることから考えても不自然ではありません(※ちなみに拙著『良いコード/悪いコードで学ぶ設計入門』では「目的駆動名前設計」と題してユビキタス言語の操り方を解説しています)。 gihyo.jp 目的ベースでユビキタス言語を策定し、目的に着目しやすくなることで良い効果が生まれます。「 この目的を達成するためならば、今の実装や構造にこだわる必要はないよね。もっと良い手段があるのでは!? 」という着想が得られやすくなります。そしてそれが深いモデルの発見や設計に繋がる、と考えることができます。 これは馬車しかなかった時代の自動車のイノベーション話に似ています。「もっと早い馬がほしい」と顧客に言われて馬の肉体構造や馬車の構造を一生懸命分析しても、自動車は生み出されません。現行手段に囚われず、顧客課題と真の目的に向き合い、目的をより満たすものを考え出す姿勢が大事です。 DDDで解決したいこと まとめ 以上、DDDで解決したい課題と、解決手法の全体像を紹介しました。要約すると以下となります。 DDDは、中心的事業領域であるコアドメインの価値を高め、継続的に利益向上していくための設計手法。 DDDは、コアドメインの機能性と変更容易性の向上に貢献する。 境界付けられたコンテキストにより、コアドメインとサブドメインを疎結合にする。 階層型アーキテクチャにより、Domain層とそれ以外の技術層を疎結合にする。 Entity、ValueObject、AggregateなどのパターンによりDomain層内をドメイン概念レベルで高凝集疎結合にし、変更容易性を向上させる。 蒸留プロセスにより繰り返しモデルを改良し、機能性向上に貢献する深いモデルを発見・設計する。 DDDの向き不向き 解説したDDDの特徴を踏まえると、DDDには向き不向きがあることが分かります。 DDDは、以下を満たすものに向いていると言えるでしょう。 事業の主力、競争優位性の発揮対象 長期開発対象 複雑な事業課題を取り扱うもの DDDは、以下には不向きでしょう。設計コストが高くつきます。 サブやオプション的な位置付けのサービスや機能 プロトタイプなど短期開発で終わるもの 取り扱う事業課題が単純で、CRUD程度のシンプルな作りになるもの おまけ 実はあなたもDDDを実践している? 「そうは言ってもDDDの実践は結構大変そうだなぁ」と感じた方がおられるかもしれません。しかし一方で、私たちは日々の生活でDDDを実践していると私は考えます。 人には皆それぞれ 人生のコアドメイン があります。人はそれを 生きがい と呼びます。大好きな趣味や、大切な人との時間、推しのゲーム……それぞれあることでしょう。生きがいは神聖不可侵であり、なんぴとたりとも邪魔は許されません。例えば自分の趣味の大規模イベントや大事な旅行の予定があるとき、アクシデントが発生したり別の用事が突然割り込まれたりすると台無しになってしまいます。台無しにならぬよう健康に気をつけたり、仕事をしっかり片付けて割り込みが入らないようにした経験、皆さんもあると思います。 ちなみに私はフロムソフトウェアさんのゲーム『アーマード・コア6』の発売に備えるために万全の体制で臨みました(下記ツイートは引用)。 今日も1日 #アーマードコア6 #AC6 pic.twitter.com/7069RC9Ulx — 魚遣(うおつか) (@nou_ni_hitomi) 2023年8月22日 生きがいの大切な時間を守るために、邪魔するものを徹底的に除去する。ソフトウェア開発も同様に、競争優位性を発揮するコアドメインを守るために、コア以外の要素を徹底的に分離隔離する、これがドメイン駆動設計なのです。 おわりに 以上の解説のもと、DDDを活用してプロダクトの価値をより高めるべく邁進していきます。 弊社スタメンはTUNAGを中心に右肩上がりの成長を続けています。この成長をより高めるため、私に課せられた責務は非常に重要なものになっております。 2023年12月期 第1四半期決算説明資料 より この勢いで弊社スタメンはさらなる事業拡大を目指し、採用活動をより活性化していきます。現在従業員数は約80名。今の規模の2倍、3倍、5倍とスケールさせていきます。 エンジニア絶賛募集中です。私と一緒に働いてみたい、興味がある方は、ぜひ下記にアクセスしてみてください。 herp.careers
プラットフォーム部 DevEx チームの河井です。 8月に弊社サービス TUNAG(ツナグ)で使っている Ruby on Rails のバージョンを 6.1 から 7.0 に上げたので共有します。 やったこと 一般的なバージョンアップのフローについては多くの記事がありますので、ここでは影響が大きかった仕様変更の対応方法について紹介します。 フォーマット指定なしの to_s to_s にフォーマットを渡すことが非推奨化されました。 この変更により、引数なしの to_s の挙動が変わってしまう問題がありました。 そこで、影響を受けるクラスについて to_s をオーバーライドし、フォーマットがこれまでと変わらないようにしつつ、警告を出すことで後から直すべき箇所を見つけやすくしました。 class ActiveSupport :: TimeWithZone alias original_to_s to_s if Rails :: VERSION :: MAJOR == 6 # Rails6ではNOT_SETが定義されていないのでここで定義する NOT_SET = :default end def to_s (format = NOT_SET ) if format == NOT_SET # 引数なしのto_sはActiveSupport側でwarningされないので、ここでwarningする ActiveSupport :: Deprecation .warn( ' Since Rails7, the format of ActiveSupport::TimeWithZone#to_s with no arguments has changed. To output the same format as before, please use #to_formatted_s instead. ' ) to_formatted_s else original_to_s(format) end end end という対応をして無事 Rails のバージョンを 7.0.6 に上げた直後、7.0.7 がリリースされ、上記と同じ対応が入りました。 github.com そのため今から Rails 7.0 に上げる場合は上記対応は不要なのですが、記録として残しておきます。 relation.merge relation1.merge(relation2) という書き方をしたときに、同じカラムに関する WHERE 条件が AND で結合されたり、relation2 の条件で上書きされたりするという仕様になっていました。 Rails 7.0 からは Hash#merge と同じように常に上書きされる仕様に変更されました。 # Rails 6.1 (IN句はマージする側の等値条件によって置き換えられる) Author .where( id : [david.id, mary.id]).merge( Author .where( id : bob)) # => [bob] # Rails 6.1 (競合する条件がどちらも存在する: 非推奨) Author .where( id : david.id..mary.id).merge( Author .where( id : bob)) # => [] # Rails 7.0 (IN句の振る舞いは同じで、マージされる側の条件が常に置き換えられる) Author .where( id : [david.id, mary.id]).merge( Author .where( id : bob)) # => [bob] Author .where( id : david.id..mary.id).merge( Author .where( id : bob)) # => [bob] Ruby on Rails 7.0 リリースノート - Railsガイド AND として merge を使っていた場合(上記コード例「競合する条件がどちらも存在する: 非推奨」のパターン)の対応として、 relation.and を使う方法があります。 github.com relation.and の注意点としては、 relation.or と同じように、クエリの構造が同じでないとエラーになるということがあります。 今回 TUNAG で対応が必要だったケースは、AND として merge を使っているケースで、かつ2つのリレーションの構造を同じにすることが難しい状況でした。 最終的に、クエリを修正して WHERE 条件が被らないようにすることで、 merge を使っても挙動が変わらないようにしました。 timestamp 型の precision Rails 7 から datetime 型の precision のデフォルト値に変更がありました。 以前は無指定だと DB デフォルトだったのが、7 からは無指定だと precision: 6 、nil を指定すると DB デフォルトになるという仕様になりました。 schema.rb を使っている場合は互換性維持の手段があるようですが、弊社では ridgepole を使っており、以下の例の after のようにスキーマの修正が必要でした。 # before t.datetime :column_a # 無指定: DB のデフォルト precision t.datetime :column_b , precision : 6 # after t.datetime :column_a , precision : nil # nil 指定: DB のデフォルト precision t.datetime :column_b # 無指定: precision == 6 参考: https://github.com/ridgepole/ridgepole/blob/8b7ea6844a118deb6c4cf9bedcab00a37a56a1f9/README.md?plain=1#L16-L18 安全にバージョンアップを行うために 前回 Rails を 5.0 から 6.1 にしたときは、大量の修正を含んだ Pull Request をマージする運用になっていたため、1週間以上社内限定で動かすなど慎重なリリース作業が求められていました。 一方で今回は、事前にバージョンアップできる gem を更新したり、Rails のバージョン分岐を用いて Rails 6 のうちから 7 でも動くような修正をしたりと、細かく修正を積み重ねていきました。(Pull Request の数は gem のアップデートで 20 件、コードの修正で 30 件ほど。) CI 環境で検知できるものは先に対応した上で、さらにテストでカバーできていない箇所を拾うため、 ActiveSupport::Deprecations を活用して、production 環境で非推奨なコードが実行されたことを検知できるようにしました。 # config/environments/production.rb ActiveSupport :: Deprecation .disallowed_behavior = [ :log ] ActiveSupport :: Deprecation .disallowed_warnings = [ / some_method / , ] 結果として、前回行ったような重厚なリリース作業というものはなく、通常の機能追加と同じような感覚でバージョンアップを行うことができました。 まとめ 本記事では、弊社サービス TUNAG を Rails 7.0 にバージョンアップする際に直面した主要な仕様変更と、それに対する具体的な対応策を紹介しました。 記事を公開するタイミングで既に Rails 7.1 Beta 1 がリリースされていましたが、次回からは更にタイムリーに情報を提供できるよう、新しいバージョンに向けて継続的に取り組んでいきます。 最後に、スタメンではエンジニアを募集しています。興味をもっていただけましたら、ぜひ下記からご応募ください。 herp.careers
今回スタメンは9月14日からベルサール渋谷ガーデンで3日間開催されたDroidKaigiに初めてスポンサーとして参加をしました。本ブログではイベントの様子やスポンサーブースの様子をレポートしていきます。 開発部モバイルアプリGでAndroidアプリ開発をしているカーキ( @khaki_ngy )です。 スタメンとしてのスポンサーだけでなく、自分個人としてもオフラインでのDroidKaigiに参加するのは初めてだったので、その点での感想も含めてレポートしていきたいと思います! スポンサーブースについて ブースの様子 今回、スポンサーブースではAndroidのロゴを当てるクイズを実施しました。 トランプを引いてもらい出た数字のAndroidのバージョンのロゴを当ててもらうというものです。正解した方には抽選を引いてもらい当選した方にはAnkerの充電ポートをプレゼントしてました。 今回実施したロゴクイズ画像 ロゴクイズの回答 このクイズ、古いバージョンであればあるほどマイナーバージョンが多く存在するので、難易度の高いクイズになっていました笑。自分もやってみてAndroid1,2,3あたりのロゴはあやふやだなぁというのを実感しました。ここ数年のバージョンではロゴがそもそも用意されていないことがあり、少し寂しいですね。 皆さんはこのロゴの一覧を見てAndroidのバージョン数が想像できましたか? またスタメンの提供している『TUNAG』のデモをブースで展開していました。 スタメンの存在はまだまだよく知られておらず、事業内容を口頭で説明しても中々イメージをわいてもらえないのではと感じていました。 そこで今回はブースでのTUNAGのデモを展示しました。 デモの様子 期間中は多くの人がデモを触ってくださり、多くの方にTUNAGのことを知ってもらう機会となりました! ブースにお越しくださりありがとうございました! 株式会社スタメンさん @stmn_inc のブース紹介です! 会社紹介のほかAndroidロゴクイズで参加できる抽選や、アプリのデモやCTO/EMとの面談予約をされています! 是非、足を運んでみてください!! #DroidKaigi pic.twitter.com/cUAtFmRTXO — DroidKaigi (@DroidKaigi) 2023年9月15日 ここがすごかったDroidKaigi ここからはDroidKaigiに初めて参加をした自分が感じた、ここがすこかったDroidKaigiについて紹介していこうと思います。 スポンサーブースのスタンプラリー DroidKaigiでは各スポンサーブースにそれぞれスタンプが配置されており、それを使ったスタンプラリーを実施していました。 Day2終了時点でのスタンプカード スタンプラリーを通じて参加者の方が気軽にスポンサーブースを訪れることができるような仕組みが用意されていたのは、参加者目線でもスポンサー目線でも嬉しかったです!自分も参加者としては、スポンサーブースに興味はあるけど、話すきっかけがなくて怖いなと感じることが時々あったので、楽しみながら回ることができたのはとても良かったです! スポンサーであるスタメンも例に漏れずスタンプを押していました。スタメンは『 POWER 』でした笑 自分がブースにいる時は、「パワーーーーー!」と叫びながら押させていただきました💪 スタンプラリーで様々な景品をいただけるのも嬉しかったです!得しかしてない企画✨ スタンプラリーでゲットした品々 セッションルームの名前が可愛い セッションルーム名は Android Studio のバージョン名から取られていました。 Arctic Fox Bumble bee Chipmunk Dolphin Electric Eel 会場であるベルサール渋谷ガーデンのホールのアルファベットA~Eをベースにバージョン名が当てられているのもうまいなーと思いました😊 キャラクターがプリントされたうちわ 各バージョンのイラストもめっかわでした🥰 無料のネイル体験 DroidKaigiでは無料のネイル体験を実施していました! DroidKaigiのロゴやドロイドくんのロゴを描いてもらうことができ、自分も早朝に並んで描いてもらいました。 可愛い!!😻 DroidKaigiロゴとドロイドくんのネイル 自分自身、ちょうどネイルに興味があったので最初のきっかけとして最高でした! ネイルをしてると不思議とテンション上がりますね❤️🔥 参加メンバーの感想 自分以外に参加したAndridエンジニアからの感想も紹介します! カーキ ここからは個人的な感想になります。 DroidKaigiめちゃめちゃ楽しかったです!普段名古屋にいるとこれだけたくさんのAndroidエンジニアの方に会う機会がないので、それだけで感動しました😂 まだスタメンの認知は高いわけではなく、また多くの人にスタメンやTUNAGを知ってもらう機会になり、とても嬉しかったです! 今年は初参加・初スポンサーでしたが、来年は初スピーカーの実績を解禁できるようにプロポーザルを出すぞ!と気持ちが入りました🔥 鈴木 カーキさんと同じく名古屋でAndroidアプリ開発をしている鈴木です。 今回初めてのオフライン参加で行きの新幹線ではとてもわくわくしていました。 セッションメインでの参加をしていましたが、各ブースで他の企業の話を聞くことができたのはオフラインならではのとても貴重な機会だったと思っています。 弊社ブースにもたくさんの方に来ていただけて色々な方とお話しができてとても楽しかったです。 最後に DroidKaigiはその紹介文に『 エンジニアが主役 のAndroidカンファレンス』とあるように、参加されるエンジニアに向けて様々な取り組みがされていて、スポンサーとしても参加者としてもとても楽しかったです! また懇親会などで多くの方とお話をして、たくさんの勇気をもらいました。 また1年間たくさん吸収をして来年あの場所で続きを話せるように頑張りたいと思いました! 採用情報 スタメンではAndroidエンジニアを含めたモバイルアプリ開発のエンジニアを募集しています! 興味のあるからはカジュアル面接からどうぞ! herp.careers
全員での集合写真 こんにちは!普段はTUANG Androidアプリを開発をしているカーキ( @khaki_ngy )です。 先日、早稲田大学西早稲田キャンパスで開催されたiOSDCにスポンサーとしてスタメンが参加しました。 そんなiOSDCの様子をレポートしていきたいと思います🔍 ididblog !! iOSDCとは、年に一度開催されるiOS開発最大のカンファレンス(お祭り)です。 今年はオフラインの参加人数制限も解除され、非常にワイワイした雰囲気で開催されていました。 会場の雰囲気 会場ではセッションブースのほか、ポスターセッションやアンカンファレンス、スポンサーブースなどがあり、参加者同士の交流が盛んに行われていました。 今回、スタメンのスポンサーブースのエリアはポスターセッションなどが行われるスペースでした。このスペースでは朝はドーナツ、夕方は寿司やお菓子など様々なフードの提供があり終始ワイワイとした雰囲気でした! 朝のドーナツ お昼過ぎの軽食 沢山のうまい棒 スタメンは今回が初めてのスポンサーだったので、初めは参加者の方に集まってもらえるのか不安を感じていましたが、自然と人が集まる空間が作られており、運営の方々の工夫を感じました。ありがとうございます! スポンサーセッションについて 今回スタメンはスポンサーセッションも実施しました! スポンサーセッションの様子 『VIPERアプリにSwiftUIを導入したら、View層の責務がより分離できた話』 と題して、スタメンのiOSエンジニアのおしん( @38Punkd )が、長年VIPERアプリとして運用してきたTUNAGアプリにSwifUIを導入した背景や、その中で乗り越えてきた問題などを紹介しました。 資料も公開されているので、こちらもご覧ください! またスタメンテックブログでも登壇の内容を軽く紹介していますので、 こちら もご覧ください。 スポンサーブースについて 今回スタメンは初めてiOSDCにスポンサーとして出展しました。過去に RSGT などでブース出展の経験はありましたが、今回のようにしっかりと準備した形でのスポンサーブースを出展するのはスタメンとして初めての試みでした。 スタメンブースの様子 今回のブース出展では『推しのアーキテクチャアンケート』を実施しました。 推しのアーキテクチャアンケートでは、現在使用しているかどうかに関わらず、自分の 推し のアーキテクチャを皆さんに伺いました。 アーキテクチャアンケートの結果 このアンケートは多くの参加者の方から、投票をいただき非常に盛り上がりました。 結果としては、合計276名の方に投票していただきました。 また各アーキテクチャ毎の投票数は以下のようになりました。 🥇1位 MVVM: 95票 🥈2位 VIPER: 38票 🥉3位 MVC: 37票 4位 TCA: 27票 5位 MVP: 19票 6位 Redux: 12票 など… 1位のMVVMに関しては、投票する場所がなくなるほどの人気でした。元々RxSwiftを利用したリアクティブプログラミングとしてMVVMは利用されている印象はありましたが、近年のSwiftUIの登場でもその人気を後押ししているように感じました。 2位はVIPERということで、今回VIPERに関するスポンサーセッションを行うスタメンとしては、美味しい結果になりました。複雑なアプリになるとVIPERの利用を考えるという声を多くいただきました。スタメンでのVIPER導入の背景は こちらのブログ にまとめられているのでご確認ください。 今回はスポンサーセッションでVIPERアーキテクチャの話をすることもあって、「推しのアーキテクチャアンケート」を行いました。アンケートに答えていただいた参加者の方とお話しする中で、アーキテクチャの選定や導入にはプロダクトごとの背景があり、どこでも悩んだり苦悩しながらアーキテクチャを入れているんだなぁというのを感じました。改めてアンケートにご回答していただいた参加者の皆さんありがとうございました! 最後に iOSDCにスポンサー側として参加したことで、運営スタッフ方の工夫をより知ることができましたし、参加者の方との交流も沢山することができました。 3日間スポンサーブースでの対応は少し疲れましたが、それ以上にスポンサーとしてiOSDCをサポートすることができてよかったと感じています! 参加メンバーの感想 今回参加したメンバーからの感想もご覧ください 朝倉(iOS) コロナ以前には何度か参加していたiOSDCへ、今回初めてスポンサー参加しました。 iOSアプリ開発を生業としている1エンジニアとして、日本最大のiOS開発者コミュニティへ少しでも貢献できたのであれば大変嬉しく思います。 過去参加していた時にはエンジニアとしてでしたが、今はエンジニアリングマネージャとなり、以前とはまた異なる目線を持って参加することになりました。 久々に会った方達は私と同じようにポジションが変わっていたり、学生をはじめとした、新世代のiOSエンジニア出てきたりと、さまざま変化が肌で感じられ、感慨深くもあり、大変嬉しく思いました。 おしん(iOS) スポンサーセッションで発表を担当しました、おしんこと青木です。 実は私は、こういったカンファレンスへの参加は、今回が初めてでした。 今回のiOSDCが、コロナ後初のオフライン開催ということもあり、会場はどのような雰囲気なのか、あまり想像がついていませんでしたが、非常に多くの方が連日カンファレンスに参加され、夜は盛大に交流会が開かれる等、国内最大規模のカンファレンスの雰囲気を、肌で感じられました。 スポンサーセッションについては、iOSDC初参加の私がセッションをすることに対して、私自身不安でしたが、弊社のメンバーが何度も練習に付き合ってくれ、当日は必要以上に緊張することなく、セッションを終えることができました(メンバーに感謝!🙌)。 スポンサーセッションやブース出店の合間に、適宜他の方のセッションを聴きに行っていました。 私はその中でも、 giginet さんの「 Swift Packageを使った巨大な依存グラフのキャッシュ戦略 」の内容が、特に勉強になりました。 Swift Package Manager(以降、SPM)はXcodeで簡単にパッケージ管理できる素晴らしい機能ですが、SPMの機能を損なうことなく、新たにキャッシュ機構を持たせ、ビルド速度を速くする試みは、開発効率を上げる画期的な方法だと感じました。 他にもたくさんの学びがあるセッションを聴くことができ、エンジニアとして大いに成長する機会になりました。 今回、iOSDC初参加ではありましたが、セッションや、ブース、交流会を通して多くの方と交流できました。 カンファレンスを通してネットワークを増やせたことが、一番の良さだと感じています。 カーキ(Android) レポートでイベントやスポンサーブースでの紹介は上でしたので、個人的な感想をまとめていこうと思います!自分としては対面でのカンファレンスは2019年の try! Swift 以来だったので、とても楽しみにしていました。 当時学生の頃に参加した時とは異なり、エンジニア同士の繋がりも増えて、SNSで交流のあるエンジニアの方と直接話すことができる機会となりとても楽しかったです。特に自分は名古屋本社に勤めていることもあり、普段東京にいないこともあって、出合いに飢えていたのかもしれないと感じました😅 自分がiOSDCで特に驚いたのがアンカンファレンスです。アンカンファレンスとは参加者主導の会議で、みんなで話したいことや行いたいワークショップなどを事前にホワイトボードに記入して、集まった人同士でワイワイしていました。 自分はSwift6の機能である「 所有権 についてみんなで話そう」の回に参加をしました。自分は普段はAndroid開発をしているので、iOSの最新技術には疎い状態でした。ただ最初から答えがあるわけではなく、参加者全員が議論をして、理解を深めていく場となっており、あの場所で共に学ぶことができて良かったと感じています。これもやはり対面ならではの空間だなと感じました。 また今回の参加を通して、iOSDCというコミュニティに参加できてよかったと改めて感じました。本文中で述べたように、ポスターセッションやスポンサーブースが盛り上がるような仕組み作りだったり、LTでの会場全体での盛り上げの仕組みなど、様々なところに運営スタッフの方の工夫を感じました。自分が運営しているコミュニティに対しても「もっと良いコミュニティを作るぞ」とモチベーションが上がりました。運営スタッフの方々の準備には頭が下がるばかりです、本当に最高のカンファレンスをありがとうございました!
株式会社スタメンは、2023年9月14日から16日までの3日間、ベルサール渋谷ガーデンとオンライン配信にて開催される「DroidKaigi 2023」に、ゴールドスポンサーとして協賛し、イベントを盛り上げます。 2023.droidkaigi.jp 本エントリーでは、スポンサーブース、ノベルティ、フライヤーについてご紹介いたします。 スポンサーブースのご紹介 オフライン会場のスポンサーブースは、スタメンカラーのテーブルクロス、バナースクリーン、のぼり旗が目印です。 会場内で会話のネタになるようなコンテンツをご用意しています。エンジニアメンバーもブースに立ちますので、会場に来られる方はぜひお立ち寄りください。 ノベルティグッズ・フライヤーのご紹介 スポンサーブースでは複数種類のノベルティ配布予定。DroidKaigi にちなんだデザインのノベルティも用意しています。 実際にブースで見ていただきたいので、本エントリーではぼんやりと公開させていただきます。 このほかにも、抽選で当たる豪華なノベルティもありますので、是非ゲットしにきてください。 参加者の方が受け取る配布物には、こちらのフライヤーが同封されています。 エンジニア組織についての簡単な紹介があるので、是非ご一読ください! おわりに DroidKaigi 2023 オフライン会場に参加するモバイルGの直近の取り組みについては、こちらの記事でご紹介しています。 tech.stmn.co.jp Androidエンジニアの皆様と交流できるのを楽しみにしています!3日間楽しみましょう! カンファレンス情報 名称 : DroidKaigi 2023 主催 : 一般社団法人DroidKaigi 開催日時 : 2023年9月14日 (木)〜9月16日(土) 場所 : ベルサール渋谷ガーデン + YouTube 参加方法や詳細については、 公式サイト をご覧ください。 採用情報 またスタメンでは、Androidエンジニアに限らず全技術領域で、プロダクトを成長させていくエキスパートを募集しています。もし興味を持っていただけたら、下記からご応募ください! herp.careers
アイコンの出典: https://icons8.com こんにちは、株式会社スタメンでiOSエンジニアをしている青木 ( @38Punkd )です。 先日の投稿 にあった通り、スタメンは iOSDC Japan 2023 にゴールドスポンサーとして協賛します。私はそのスポンサーセッション枠として登壇します。この記事では、当日発表する内容を少し先出ししてご紹介できればと思います。 fortee.jp 私たちは TUNAG というプロダクトを、Web・iOS・Androidの3プラットフォーム上で提供しています。iOSアプリでは VIPER アーキテクチャを導入しています。具体的な導入方法については、弊社で以前に試行錯誤した様子が以下の記事で解説されていますので、気になる方は読んでみてください。 tech.stmn.co.jp TUNAG iOSアプリのサポート対象バージョンの下限を、今年からiOS 13に変更したので、13から使用可能になる SwiftUI の導入を本格的に検討しました。 実際にSwiftUIを導入するに至ったのですが、これまでVIPERで構築してきたアプリに対して、SwfiftUIを導入する上で何を具体的に検討したかをご紹介します。 SwiftUIとは 2019年にApple社が提供開始した宣言的UIフレームワークのことです。XcodeのGUIで SwiftUI を選択してファイルを新規作成すると、以下のようなViewが初期生成されます。 struct SwiftUIView : View { var body : some View { Text( "Hello World!!" ) } } SwiftUIには2つの大きな特徴があります。 データバインドの仕組みを持つ UIKitと互換性がある それぞれについて簡単に補足します。 データバインドの仕組みを持つ SwiftUIは先のコードで示したように、画面に表示するViewを構築する部分を View プロトコルに準拠した構造体で表現します。 SwiftUI Viewの画面更新をするには、 ObservableObject プロトコルに準拠した、状態管理用クラスを用意します。 これがいわゆる ViewModel に相当し、このViewModelの値が更新されるとSwiftUI Viewの該当のViewが自動更新されるため、SwiftUIはそれ自体がMVVMの特性を持ちます。 UIKitと互換性がある SwiftUIのViewをUIKit上で使うためのラッパークラス UIHostingController UIKitのViewをSwiftUI上で使うためのラッパークラス UIViewRepresentable が用意されています。 どちらのオブジェクトも、UIKit・SwiftUIをインポートすれば、iOS 13以上ならどこからでも呼び出せるため、SwiftUIはUIKitと互換性が非常に高いと言えます。 実際にVIPERにSwiftUIを導入する上で検討したこと これまでUIKitを用いて UIViewController 上でViewを構築してきたので、SwiftUIを導入することでアンコントローラブルな点が出てこないか、また、データバインドの仕組みを持つSwiftUIがVIPERに馴染めるか等の相性を考慮する必要があります。 この相性について大きく分けると、以下2点を考慮する必要があります。 UIViewControllerがSwiftUIの ライフサイクル をハンドリングできるか UIViewControllerがSwiftUIの イベント をハンドリングできるか 1点目について、詳しく説明します。 1. UIViewControllerがSwiftUIの ライフサイクル をハンドリングできるか “SwiftUIのライフサイクル” は、さらに3つの具象に分けて考えられます。 1-1. SwiftUI View自身 のライフサイクル 1-2. SwiftUI Viewのデータバインドをする ObservableObject プロトコルのライフサイクル 1-3. SwiftUI ViewをUIKitで使うためのラッパー UIHostingController のライフサイクル 1つ目について、深掘ります。 1-1. SwiftUIの View自身 のライフサイクルをハンドリングできるか Viewのライフサイクルとは、Viewがインスタンス生成された後に画面として使われ、その後インスタンス破棄されるまでの一連の流れのことを指します。 UIViewControllerのライフサイクルに対して、UIKitでは多くのメソッドが提供されています。 出典: https://developer.apple.com/documentation/uikit/uiviewcontroller 対してSwiftUIでは、 viewDidAppear と ViewDidDisappear に相当する、 onAppear と onDisappear メソッドの2つだけが提供されています。 しかし、このonAppearとonDisappearメソッドは、いくつかのケースにおいては呼ばれないことがあります。 また、ある画面のライフサイクルメソッドを呼ぶには、その画面への遷移ロジックが必要です。iOS 15までは、UIKitの UINavigationController に相当するSwiftUIの NavigationView が、SwiftUI View間の画面遷移ロジックを担います。(iOS 16からは、 NavigationStack も使えるようになります)。 このNavigationViewは、挙動が比較的不安定であると言われています。 これらを踏まえて、ライフサイクルメソッドを呼ぶための画面遷移ロジックは、現時点ではSwiftUIの機構に任せるのではなく、UIViewControllerに任せる判断をしました。 続いて、SwiftUI Viewはインスタンス生成のタイミングについてはどうでしょうか。SwiftUI Viewが準拠するViewプロトコルには、 id メソッドがデフォルト実装されています。 出典: https://developer.apple.com/documentation/swiftui/view/id(_:) SwiftUI Viewがインスタンス生成されるタイミングは、自身の保持するIDが生成・更新された時だと分かります。 以上から、SwiftUIのView自身のライフサイクルをハンドリングをするには、3点を考慮すれば良いと言えます。 UIViewControllerに比べて、SwiftUI Viewのライフサイクルメソッドの種類は少ない、かつ完全にイコールではない 画面遷移ロジックは、SwiftUIの機構は使わず、UIViewControllerに任せる SwiftUI Viewのインスタンス生成のタイミングはidの変化をトリガーにすると良い つまり、画面遷移ロジックはUIViewControllerに任せて、SwiftUI Viewの責務は、見た目の部分のみを担わせた方が良さそうと言えます。 続いて、先ほど列挙した3つの具象のうち、2つ目についてです。 1-2. ObservableObject プロトコルのライフサイクルをハンドリングできるか SwiftUI でViewの状態管理をするには、ObservableObjectプロトコルに加えて、 @StateObject や @ObservedObject を用います。 どちらも、ObservableObjectプロトコルに準拠しているクラス内の値変化を、監視できるようにするためのプロパティラッパーです。 両者の違いは、 @StateObjectは、保持するViewに対して、インスタンスが 1つだけ 生成されるのに対して、 @ObservedObjectは、保持するViewのライフサイクルに依存し、 親Viewの再描画でインスタンスは再生成される という点です。 私たちのプロダクト TUNAG は、iOS 13からサポート対象ですが、@StateObjectはiOS 14からしか使えません。なので、@ObservedObjectを使うことが必須になります。 結論としては、@ObservedObjectは親Viewのライフサイクルに依存するので、 画面単位のライフサイクルと一致させる と、ハンドリングしやすいという考えに至りました。 すなわち、 @ObservedObjectを保持するクラス = UIViewController とすれば、ObservableObjectプロトコルのライフサイクルをハンドリングしやすいと言えます。 最後に、3つ目についてです。 1-3. SwiftUI ViewをUIKitで使うためのラッパーUIHostingControllerのライフサイクルをハンドリングできるか これについては、UIHostingControllerはUIViewControllerを継承しているので、ライフサイクルを親ViewControllerに委譲( didMove メソッドで可能)できます。 出典: https://developer.apple.com/documentation/swiftui/uihostingcontroller つまり、親ViewControllerのライフサイクルによって、SwiftUI Viewのライフサイクルをコントロールできます。 ここまで、UIViewControllerがSwiftUIのライフサイクルをハンドリングできるかについて、説明してきました。 では、SwiftUIのイベントハンドリングについてはどうでしょうか。 UIViewControllerがSwiftUIの イベント をハンドリングできるか SwiftUI View内で発生するイベント、例えばボタンタップは、タップ時の処理を記述できます(当たり前と言えば当たり前ですが)。 この処理を別のクラスに伝播させれば、SwiftUI Viewのイベントをハンドリングできます。具体的には、以下のように完了ハンドラや、デリゲート等でハンドリングできます。 struct ViewScreen : View { var tapHandler : (() -> Void ) ? var body : some View { Button { tapHandler?() } label : { Text( "Tap Me !!" ) } } } まとめ SwiftUIの性質を分けて、VIPERにSwiftUIを導入する上で検討した事をご紹介しました。 UIViewControllerがSwiftUIのライフサイクルをハンドリングできるか 1-1. SwiftUI View自身のライフサイクル 1-2. SwiftUI ViewのデータバインドをするObservableObjectプロトコルのライフサイクル 1-3. SwiftUI ViewをUIKitで使うためのラッパーUIHostingControllerのライフサイクル UIViewControllerがSwiftUIのイベントをハンドリングできるか 以上を踏まえて、最終的にSwiftUIを導入する上での要件を定めました。 画面遷移をUIViewControllerに担わせるため、UIViewController.view = SwiftUI View の構造を作る ObservableObjectに準拠したクラス(ViewModel)のインスタンス(@ObservedObject)をUIViewControllerに保持させる この2つの要件を元に、実際にVIPERにSwiftUIを導入する方法をコードで示せればと思いますが、 ここからは iOSDC Japan 2023 当日、以下スケジュールでご紹介できればと思います!! 登壇・出展情報 日時:2023年9月3日 15:05〜(20分) 場所 : 早稲田大学 理工学部西早稲田キャンパス Track:C 当日はスポンサーセッションだけでなく、ブースも出展しますので、気軽に遊びに来てください! 当日お会いできる事を楽しみにしています!! tech.stmn.co.jp 採用情報 スタメンでは、iOSエンジニアに限らず全技術領域で、プロダクトを成長させていくエキスパートを募集しています。もし興味を持っていただけたら、下記からご応募ください! herp.careers
株式会社スタメン でフロントエンドエンジニアをしている 神尾 です。普段は、エンゲージメントプラットフォーム「 TUNAG 」の開発をしています。 TUNAGでは、2023年1月からWebフロントエンドのリプレイスプロジェクトが始まり、今もプロジェクトが進行中です。現在のWebフロントエンドでは技術選定の選択肢が多く、選定にあたっての検討事項がとても多いと思います。 この記事では、リプレイスの際に採用した技術の選定理由や設計についてまとめたので、この記事を通して、読んでくださった方のお役に立てれば嬉しいです。 また、リプレイスの背景やインフラについては別記事になっているので、そちらもご覧ください。 tech.stmn.co.jp 技術構成 / 選定理由 React / Next.js / TypeScript React Next.js TypeScript SWR aspida msw storybook / chromatic styled-components hygen eslint / prettier / stylelint ディレクトリ構成 apis features pages / screens ui 運用してみて分かった嬉しさ・つらさ 嬉しさ つらさ ドキュメントの運用 今後のこと おわりに 技術構成 / 選定理由 React / Next.js / TypeScript ベースとなる技術として、React / Next.js / TypeScriptを採用しました。 React 社内のプロダクトはReactを多く使用しており、社内に知見を持つエンジニアが多かったため、ここは特に議論はなく決定しました。 Next.js Next.jsを採用した理由はいくつかあります。以下が主な選定理由です。 ルーティングやCode Splittingなどデフォルトで備わっている機能の恩恵を受けたかったこと 一部の機能でSSRを使いたかったことや、プロダクトの性質上、様々な機能があるため、それに合わせてレンダリング方法を選択できる余地があることにメリットを感じたため TypeScript デファクトスタンダードになっているため、採用しない理由がなく採用をしました。 SWR データ取得ライブラリ&状態管理ライブラリとして、SWRを採用しました。 リプレイス前の環境では、Reduxを使用していましたが、コード量の多さや学習コストの高さからReduxの採用はしませんでした。 また、Reduxで管理していた状態のほとんどがAPIから取得したデータであった為、SWRで問題がなかったのも採用理由です。 SWR以外にも同じようなライブラリとして TanStack Query や RTK Query があります。 その上でSWRを採用した理由は、開発元がVercelのためNext.jsの新機能が出た時に一番早く対応されるだろうということやRTK QueryがSuspenseに対応していなかった等が挙げられます。 aspida バックエンドとしてRailsを使ってスキーマ駆動で開発しているため、OpenAPIからHTTPクライアントとリクエスト / レスポンスの型を自動生成して開発効率の向上を図っています。 そのためのライブラリとして、aspidaを採用しました。 同じようなライブラリとして、 Orval や openapi-typescript も候補に上がりました。どのライブラリでも「OpenAPIからHTTPクライアントとリクエスト / レスポンスの型を自動生成」という要件を満たせていましたが、aspidaが社内で一番知見があったため採用しました。 msw モックサーバーとしてmswを採用しました。 aspidaで自動生成したレスポンスの型と組み合わせることで、型安全なモックサーバーを立てています。 そのおかげで、OpenAPIがあれば、APIの実装を待たずに、ほとんどのフロントエンドの開発を完了することが出来るので開発効率が爆上がりしました。 開発だけでなくテストにも活用しています。 storybook / chromatic 現在のフロントエンド開発において、必須とも言えるstorybookを採用し、ホスティング先とVisual Regression Testing(VRT)のためにchromaticを採用しました。 storybookは、カタログになるというメリットだけでなく、モックサーバーすら立てることなくプレゼンテーショナルなコンポーネントの開発が始められる利点もあります。 また、storybookを導入したけど、そのままメンテナンスが行われないという話は、よく耳にするので、そうならないために、storiesはhygenで自動生成するようにしています。 そうすることで、propsを追加するだけでstorybookに載せることができ、実装コストがほとんどないため、継続的に運用できています。 styled-components CSSライブラリとしてcss-in-jsのstyled-componentsを採用しました。 選定理由としては、旧環境で使用していたので知見があり、使い慣れていたというのが大きいです。 ですが、これに関してはNext.jsのApp Routerがstableになった現在においては、適切な技術選定ではなかったと思っています。(当時はギリギリexperimentalでした) 現在は Pages Routerを使っているため、特に問題になっていませんが、将来的にApp Routerに移行するタイミングで、改めてCSSライブラリ選定し直したいと思っています。 hygen コンポーネントの雛形作成のためにhygenを採用しました。 後述でディレクトリ構成について書きますが、現在はそのうちfeatures, ui, pages, screens, usecasesはhygenで雛形を自動生成できるようにしています。 初めは、開発効率の向上のための雛形作成だと考えていましたが、現在は雛形によってディレクトリ構成が崩れにくいというメリットの方が大きいと感じています。 今後も雛形化できるものは積極的にしていき、方針が変わった場合は、hygenの雛形をメンテナンスするということを継続的にしていきたいと思います。 eslint / prettier / stylelint 静的解析として eslint、コードフォーマッターとしてprettier / stylelint を導入しています。 husky でコミット前に実行することで、ルールに違反したコードが含まれるのを防いでいます。 これによる恩恵はコードレビュー時においても大きいです。 「コメントする程ではないけど気になる」といったものを、これらのツールで弾くことでレビュワーもレビュイーもハッピーになれます。 現在は、レビュー内容でルールを決められるものは、規約ではなく、これらで自動修正するようにしています。 具体的には、以下のようなプラグインやルールを設定しています(全てではありません) プラグイン eslint-plugin-import import文の並び替え eslint-plugin-unused-imports importしているが使用されていないものを削除 eslintのルール react/jsx-boolean-value 例: propsが isOpen={true} の場合 isOpen に省略する react/jsx-curly-brace-presence 例: propsのstringが title={'text'} の場合、波括弧は不要なので title='text' に変更する react/self-closing-comp コンポーネントにchildrenがない場合、タグを省略記法に変更する 以上が技術構成と選定理由でした。 ディレクトリ構成 ├ src │ ├ apis // API層 │ ├ constants // 共通の定数 │ ├ features // 特定の機能を実現するコンポーネント │ │ ├ Todo │ │ │ ├ hooks // 機能を実現するカスタムフック │ │ │ ├ logics // 機能を実現するためのロジック(データ整形など) │ │ │ ├ presentations // プレゼンテーショナルなコンポーネント │ │ │ │ ├ index.tsx. // 正常系 │ │ │ │ ├ loading.tsx // ローディング │ │ │ │ ├ error.tsx // エラー │ │ │ ├ tests // 統合テスト │ │ │ ├ index.stories.tsx // storybook │ │ ├ index.tsx // hooksとpresentationsの繋ぎ込み │ ├ pages // Next.js のルーティング │ ├ screens // featuresコンポーネントをページ全体に配置する。URLのクエリパラメータ取得 │ ├ ui // 共通のコンポーネント │ ├ usecases // 共通のhooks │ ├ utils // 共通のロジック 現在のディレクトリ構成はこのようになっています。 (他にもtestConfigs(テストの設定)など細かいものはありますが割愛しています) この中からいくつかピックアップして紹介させていただきます。 apis 基本的にaspida等を使って自動生成したものを置いており、ほとんどは yarn prepare にscriptを入れているので、そこで自動生成するようにしています。 具体的には以下を置いています。 HTTP クライアント レスポンスの型 OpenAPIのexampleから抜き出したモックデータ mswのモック features featuresでは、以下の2種類のコンポーネントに分けることで責務を明確にしています。 副作用を持つコンポーネント(Container Component) 副作用を持たない純粋関数のコンポーネント(Presentational Component) 副作用を持つコンポーネント(Container Component) このコンポーネントは、同じfeature内のhooksやlogics、src/utlilsやsrc/usecasesなどを使って機能を実現し、Presentational Componentに流し込む役割があります。 そのため、どのように表示されるか(見た目)は知りません。 ここに副作用を集中させることで、多くのコンポーネントを副作用を持たない純粋関数のコンポーネント(Presentational Component)にすることが目的です。 副作用を持たない純粋関数のコンポーネント(Presentational Component) 副作用を持たないコンポーネントで、見た目に責務を持ちます。 このコンポーネントに渡ってきたpropsがどのようなロジックで作られているのかについては知りません。 pages / screens pages pagesはルーティングのみに責務を持ち、ページに表示する内容は知らず、対応するscreensを呼び出すことのみを行なっています。 screens 実際にページに表示する内容です。 基本的にはfeaturesを並べることを行いますが、ページに依存しているURLのクエリパラメータの取得もここで行います。 ui 共通で使えるUIコンポーネントを置きます。 実際には、TUNAGにはデザインシステムがあり、基本的には、そこにUIコンポーネントがあるので、このディレクトリに置くものは多くありません。 ただ、デザインシステムに載せるか判断できていないもの(デザインが未確定など)は一時的に、このディレクトリに置き、随時、デザインシステムへの移行を検討しています。 運用してみて分かった嬉しさ・つらさ 嬉しさ 責務が明確で可読性が高い 各コンポーネントの責務が明確になっているのと依存関係が一方向でシンプルなので可読性が非常に高いです。 現在はやっていませんが、今後開発する上で必要になれば、コンポーネント間の依存関係もeslintで制限することも考えています。 自動生成でコード記述量が少ない & 構成が崩れにくい hygenとaspidaを使って自動生成できる箇所が多いので、実装量が少なくなりました。 また、それによってフロントエンドの開発になれていないメンバーであってもスムーズに実装することが出来ています。 現在はリプレイス中のため、開発するチームは限られていますが、今後もある特定のチームだけがフロントエンドを開発するわけではないので、この段階から自動生成をここまで充実させられているのはとても良いことだと思います。 SWRが複数リクエストを1つにまとめてくれるので、サーバーの負荷を上げずに、1つのfeatureで機能が完結できる SWRのようなデータ取得ライブラリを使わずに2つの異なるfeatureから同じAPIにリクエストをした場合、2回リクエストが行われます。 仮に、そのAPIの処理が重くサーバーの負荷を軽減するために1度のリクエストに制限したいとなった場合の解決策として、グローバルに状態管理するという方法があると思います。 ただ、そうなるとfeatureの外との依存関係が生まれてしまい、複雑になる恐れあるので、できれば避けたい実装です。 SWRはデフォルト2秒間は同じkeyに対する複数リクエストを1つにまとめてくれる機能を持っています。 それを活用することで複数の異なるfeatureからの同じAPIへのリクエストを1本にまとめ、グローバルに状態管理することもなく、単独のfeatureのみで機能が完結させることができました。 その結果、実装がシンプルになりとても助かっています。 つらさ 副作用をまとめているためpropsのバケツリレーが多い featuresでは、副作用を集中させることで、ほとんどをプレゼンテーショナルなコンポーネントで構成することができている一方で、表示に必要なpropsを上から流し込むことが多いためバケツリレーが多くなります。 これに関しては、現在は許容するようにしていますが、複雑な機能であればあるほどpropsが多くなるので、その場合の対応は今後必要になりそうだなと思っています。 また、stateをfeatureのトップのコンポーネント(Container Component)にまとめていることで不要なコンポーネントまで再レンダリングがされるようになっています。 現在、再レンダリングでユーザビリティに関わるほどのパフォーマンスへの悪影響はありませんし、もしあったとしてもメモ化することで対応できるのではないかと思っています。 ドキュメントの運用 ADR(Architecture Decision Records)、README、Wikiを定期的にチームでメンテナンスするようにしています。 そうすることで新しいメンバーが開発に加わる時に、すぐに開発を始められる状態を作りたいと思っています。 また、技術には流行り廃りがあるため、いつかその技術を捨てる時がきます。その時に捨てやすくするためにADRとして技術の選定理由を文書に残すようにしています。 その他にも定期的にメンテナンスすることで、今の開発メンバーも理解に繋がりますし、見返すことで課題を見つけることにも繋がるので、これからも継続していきたいと思います。 今後のこと App Routerの対応 現在はPages Routerを使っていますが、Next.jsのバージョンは最新になっているためApp Routerが使える状況です。 ですが、App Routerの対応をするよりも新環境へのリプレイスを優先したいという考えで、現在はPages Routerでリプレイスを行っています。 ただ、Next.jsの進化はApp Routerが中心になっていくと思うので、ゆくゆくはApp Routerの対応をしたいと思っています。 SuspenseとErrorBoundary 現在は一部の機能でSuspenseとErrorBoundaryを使っています。 この2つを使うことでfeature内の実装がシンプルになるため、近いうちに全てのfeatureで対応したいと思っています。 おわりに ここまで読んでいただきありがとうございます。 とても長くなってしまいましたが「TUNAG」のリプレイスの際の技術選定理由や設計について、まとめさせていただきました。 リプレイスブロジェクトは始まったばかりで、まだまだかかると予想しています。 このような長期のプロジェクトを成功させるためにも日々、改善を重ねていきたいと思います。 株式会社スタメンではエンジニアを絶賛募集中です!! 興味を持っていただけたら、下記のリンクからご応募ください! お待ちしております! herp.careers
こんにちは、スタメンの手嶋、西川です。 普段はエンゲージメントプラットフォーム「 TUNAG 」の開発をしています。 プロジェクトの背景 技術構成・選定 リプレイス前 リプレイス後 構成変更における注意点 1. 意図せず情報がキャッシュされてしまうリスク 2. ダウンタイム発生のリスク リプレイスのフロー 振り返り 最後に プロジェクトの背景 これまでTUNAGは、プロダクトの成長に注力してきた一方で、内部品質や開発効率などに関する課題の解消が後手にまわっていました。 TUNAGが成長軌道に乗ってきた中で、エンジニア組織を拡大させつつ、継続的にプロダクトの価値を素早く世の中に届けていくためには、開発者体験(以下、DevEx)に関する改善が不可欠だと考えています。 これまでTUNAGのフロントエンドは、Ruby on Rails(以下、Rails)にReactを乗せる形で開発を行ってきましたが、Railsのインターフェイスに依存するが故に開発効率を妨げていたり、不具合の発生確率を高めてしまうというという問題があり、DevExとしては望ましくない状況でした。 また、フロントエンド単独でのデプロイやデプロイの切り戻しを行うことが出来ないなど、CI/CDに関しても多くの問題を抱えており、リリースに対してプレッシャーを感じてしまうエンジニアも一定数存在していました。 私たちのチームではDevExの改善、またユーザー体験の向上を目的として、2023年頭から本格的にフロントエンド環境をリプレイスするプロジェクトに取り組んでいます。 これまでは主にReactで開発を行ってきましたが、Reactの公式ページでもフルスタックフレームワークである「Next.js」や「Remix」が推奨されていることもあり、長期的な視点ではそのようなフレームワークを採用することで、ユーザー体験やDevExの向上に繋げることができると考え、今回Next.jsへのリプレイスを決断しました。 プロジェクトとしてはまだ道半ばではありますが、先日インフラの基盤変更も含めた1stリリースを完了させることができました。 この記事ではインフラに特化して、技術選定やリプレイスのフロー、プロジェクトの振り返りについて紹介します。(フロントエンドの技術選定や運用に関しては、 こちらの記事 をご覧ください。) 技術構成・選定 ここからは、フロントエンドのNext.jsへのリプレイスのために行った、インフラ基盤の構成変更についてご紹介します。 リプレイス前後のインフラ構成は以下の通りです。(※一部簡略化しています) リプレイス前 リプレイス後 リプレイス前の構成ではALBに直接向けていましたが、リプレイス後はCloudFrontを前段に、オリジンにALBとVercelを配置する形に変更しました。 このリプレイスプロジェクトでは、ページ単位での段階的な移行を想定しており、旧環境(Rails)と移行後のページ(Next.js)を共存させる必要がありました。このため、段階的に移行が可能な構成としています。 新たに前段に配置したCloudFrontでは、パスパターンに基づき、ALB(Rails)とVercel(Next.js)へのリクエストを振り分けています。Next.jsへのリクエストパスには「m」をプレフィックスとして付けています。また、Vercelの前段にProxyを設置することは非推奨とされていることや、構成が複雑になること、構成変更に伴うリスクが大きくなることから、CloudFrontの役割を最小限にし、キャッシュをしない構成にしました。 また、社内リリース期間中などにNext.jsへのアクセスを制限する目的で、Lambda@Edgeと比較した上で、CloudFront Functionsを採用しました。主な選定理由は以下の通りです。 Lambda@EdgeはRegional Edge Cacheで実行されるのに対して、CloudFront FunctionsはEdge Locationで実行されるため、レスポンスをより高速に返せること 用途が社外IPアドレスからのアクセス制限という軽量な処理であったため、CloudFront Functionsの最大実行時間の制限(1ms)に触れないこと(Lambda@Edgeは5秒) Lambda@Edgeはリクエスト100 万件あたり0.60 USD+実行時間の料金がかかるのに対し、CloudFront Functionsはリクエスト100 万件あたり0.10 USDと安価であること Next.jsのホスティング先としては、S3も候補に上がりましたが、Next.jsの機能をフルに活用するため、フロントエンドエンジニアが自律的に作業しやすくするためにVercelを選定しました。 そして、CloudFrontをALBの前段に新たに配置するにあたり、CloudFront経由のアクセスであることを保証するため、CloudFrontでカスタムヘッダーを付与し、ALBのリスナールールでそのヘッダーの有無を判定することで、ALBへの直接アクセスを制限しています。 構成変更における注意点 上記の構成変更を行うにあたって、大きく2つの注意点がありました。 1. 意図せず情報がキャッシュされてしまうリスク 前述したように、今回の構成変更でCloudFrontでのキャッシュは行なっていません。しかし、CloudFrontを配置することで、本来キャッシュすべきでない情報が意図せずキャッシュされるリスクがありました。そのリスクを回避するため、CloudFrontの設定や、Nginx、Vercelのキャッシュ設定について検証しました。特にHTTPヘッダーのCache-Controlの内容について、キャッシュすべきでないレスポンスにはprivateが含まれているか等の確認を行いました。 CloudFrontでは、 管理キャッシュポリシー の中から、キャッシュを無効にするポリシーであるCachingDisabledを使用しています。今回、CloudFrontのディストリビューションはCloudFormationで作成しており、以下のように設定しました。 CloudFrontDistribution : Type : 'AWS::CloudFront::Distribution' Properties : DistributionConfig : CacheBehaviors : - CachePolicyId : 4135ea2d-6df8-44a3-9df3-4b5a84be39ad # Managed-CachingDisabled 2. ダウンタイム発生のリスク Route53のトラフィックのルーティング先をALBからCloudFrontに切り替える際や、CloudFront・CloudFront Functionsを更新する際にダウンタイムが発生しないことを、AWS公式ドキュメントや本番と同じ構成にしたステージング環境で確認した上で、構成変更を行いました。 実際のリプレイスでは、ダウンタイムを発生させることなく、構成変更を成功させることができました。 また上記2点を検証した上で、構成変更を行う前に、一部のメンバーがCloudFrontのIPアドレスをhostsに登録し、ローカルからCloudFrontに接続して動作確認を行いました。 さらに、これらの問題が発生した場合にはすぐに検知・対応できるよう、アラートを事前に設定しました。設定したアラートは以下の通りです。 5xx エラー率 キャッシュヒットレート CloudFront がそのキャッシュからコンテンツを送信した対象のすべてのキャッシュ可能なリクエストの割合 (%)。今回の構成ではキャッシュをしないため、キャッシュヒットレートが0を超えた時にアラートが通知されるように設定しています。 CloudFront Functionsのコンピューティング使用率 関数の実行にかかった時間 (最大許容時間に対するパーセンテージ)。現状は35%前後で推移していますが、使用率が増加した場合に処理内容を見直すことができるよう、アラートを設定しています。 加えて、CloudFrontのログ分析のために、Athenaを用いたログ検索環境も整備しています。 リプレイスのフロー 冒頭にもご紹介した通り、リプレイスのプロジェクトは現在も続いており、主要なページから段階的に移行を進めています。 一度のリリースで1つのページ全体を置き換えてしまうと不具合が発生するリスクが高いため、以下のステップを経て細かくデプロイを行いながらリプレイスを進めています。 Next.jsの新ページ(pages)を作成 この時点ではアプリケーション内に新ページへの導線は存在しません 新ページへのアクセスを社内IPに限定する こちらの制御は上述のCloudFront Functionsで行っています 新ページに機能デプロイを細かく行う 基本的にエンジニアの動作確認以外の目的では、ページへのアクセスがないため仮に不具合が発生しても、影響範囲を最小限に抑えることができています スコープを限定してリリース(社内リリース)を行う TUNAGは弊社でも利用しているため、まずは社内限定でリリースを行っています その際にRailsのコントローラーでリダイレクト処理を入れ、アプリケーション内でRails(旧環境)にアクセスがあった際に、新環境(Next.js)にリダイレクトさせています 全体にリリースを適用させる 最後のstepとして上記のリダイレクト処理を全体に適用させました 同時にCloudFront Functionsで行っていたアクセス制限(2)の設定を削除し、全てのユーザーからのアクセスを許可するように変更しています 上記のステップを踏むことで、不具合の可能性を減らしかつ開発者が安心してリリースを行うことができています。 振り返り 繰り返しとなりますが、フロントエンドリプレイスの目的は、DevExの改善とユーザー体験の向上でした。DevExの改善に関しては、Railsとの依存がなくなりシンプルな形で開発ができるようになったことで、社内のエンジニアから喜びの声が沢山あがっています。 またCI/CDに関しても、以前の環境と比較して約10倍のスピードで完了しており、ユーザーへの価値デリバリーという観点で非常に大きな改善を行うことができました。 またデプロイに際して、開発者が必要以上に気を張らなければならない問題も解消され、デプロイ頻度を大幅に増加させることができました。 以前のページと比較して表示速度に関するパフォーマンスも向上しており、ユーザー体験としても良い効果が出ています。 一方で、Vercelの機能にはまだ活用の余地があるため、今後も継続してキャッチアップしていきたいと思います。 最後に TUNAGのフロントエンドリプレイスの背景、インフラの技術選定、リプレイスのフロー、振り返りについて紹介しました。同じ様にフロントエンドをリプレイスする際に、本記事が少しでも参考になれば幸いです。 株式会社スタメンではエンジニアを絶賛募集中です!! 興味を持っていただけたら、下記のリンクからご応募ください! お待ちしております! herp.careers
こんにちは、プロダクト開発部、モバイルGでiOSチームのエンジニアリングマネージャをしている 朝倉( @asashin227 )です。 2022年末に スクラム体制でのモバイルアプリGの変遷(2022年) という記事を公開してから8ヶ月ほど経ち、さまざまな変化がありましたので、ご紹介できればと思います TUNAG(ツナグ)について 2022年2月に以下の記事でスタメンの主力事業であるTUNAGの紹介をしました。 2023年の8月時点において昨年時点で見えていなかった部分の解像度が上がってきたため、改めてTUNAGについて紹介したいと思います。 tech.stmn.co.jp TUNAG(ツナグ)は、エンゲージメント経営の実践を通じて会社の成長や成功を支援するプラットフォームとして大企業から中小企業までざまざま規模の企業へ導入していただき、エンゲージメントの向上を支援してきました。 弊社のカスタマーサクセスと導入企業の担当者様の間で密にコミュニケーションをとる中で業種や業態によって、よりエンゲージメントに対するニーズが高い領域が見えてきました。 TUNAG(ツナグ)の強み TUNAGの提供する機能は大まかに以下の2種類に分類することができます。 企業と従業員、従業員同士のつながりを強固にする エンゲージメント機能 業務DX を実現する実務的な機能 この機能から「ITが行き届いておらず、また定着率が低くエンゲージメントが維持できない」という課題に対してTUNAGの強みが最も発揮できると考えています。 特にこの課題を抱えているのが、飲食、小売、物流、介護、製造、などの現場で働く「 ノンデスクワーカー 」が働く業界です。ノンデスクワーカーのほとんどは、立ち仕事などがメインで落ち着いた環境でPC利用などはできず、紙とペンによる非効率なオペレーションもいまだ多く残っています。そして離職などのエンゲージメントに関する課題も大きいです。 日本の労働人口の7割が現場で働くノンデスクワーカーと言われており、この市場においてTUNAGを広めることが” 一人でも多くの人に、感動を届け、幸せを広める。 ”という企業理念を実現することにも大きく近づくと考えています。 TUNAG画面イメージ ノンデスクワーカーとモバイルアプリの親和性 スタメンではTUNAGの強みを最も活かせるノンデスクワーカーをメインターゲットとして組織構造やプロダクトの開発フローを大きく変化させることにしました。 ノンデスクワーカー業界は、PCを使わず、また立ち仕事中心の環境なのでSaaSの導入も難しく、包括的な業務DXを1つのプロダクトで実現することが求められます。別の課題としては非正規雇用の割合が多く、毎年数百人単位で人が入れ替わるような企業も少なくありません。 これらの課題に対して、TUNAGでは現場の業務DXや人と組織のつながりを強固にし、エンゲージメントを高めることができると考えており、 モバイルアプリであれば立ち仕事中心の職場環境でもスムーズな導入と運用が実現できます 。 今後、TUNAGは「 ノンデスクワーカーのエンゲージメント向上から業務DXまで実現するALL-in-One Product 」というポジションを確立し、 モバイルアプリが最前線でこの道を切り開いていきます 。 モバイルアプリ開発体制への投資 ノンデスクワーカーの労働環境においてスマートフォンが最も強力なコミュニケーションツールであり、TUNAGの強みと価値を最も届けられる導線がモバイルアプリであることがわかってきたため、今年から組織体制や開発フローを徐々にモバイルファーストへシフトさせてきました。 現在時点でも様々な変化が起こっている真っ只中なので、いくつか紹介したいと思います。 人数が倍増! 2022年末の記事ではモバイルエンジニアが2〜3人という状況でしたが、2023年夏の現在ではiOS, Androidそれぞれ3名ずつの合計6名になりました! 東京拠点での採用活動も開始し、エンジニア組織の拡大にむけて急加速しています。 現在もチーム規模を成長させ続けるために積極的に採用活動を行なっています! エンジニアリングマネージャを新たに2名採用 人数の話にも関係ありますが、それぞれのモバイルOSごとにエンジニアリングマネージャがついています。 私自身、10年ほどiOSアプリ開発に関わっていますし、私以上に経験豊富なAndroidエンジニアもエンジニアリングマネージャとして、チームのマネジメントや、実質的なテックリードの役割を持っています。 モバイルGとしては今までにないほど強力なマネジメント体制が構築できていると自負しています。 モバイルGの朝会 モダン技術の導入 iOSでは2023年の頭からSwiftUIを取り入れた開発へシフトしています。 元々VIPERアーキテクチャで開発が行われてきたため、SwiftUIの導入に際して、いくつかの課題がありましたが、うまく解決して導入に至りました。 この話についてはiOSDC Japan 2023 最終日の スポンサーセッション でお話ししますので、ぜひ見にきてください! AndroidでもJetpackComposeを導入しており、両OSで宣言的UIの実装を行えます。 技術的な選択肢が増えることで効率的な開発が行える環境となっています。 チャット機能はSwiftUIで実装しました エンジニア成長のための新たな制度 モバイルGを含むプロダクト開発部全体で、成長支援を目的とした様々な制度が急速にでき始めています。 無限書籍購入制度 カンファレンス参加補助(参加費・交通費・宿泊費全額補助 & 営業日は業務扱い) 拠点間出張補助(東京拠点と名古屋拠点のコミュニケーションや業務効率を促進) これらの制度を活用することによって個人だけでなく、プロダクト組織全体の技術向上や知見の吸収が促進されていることがすでに実感できるものとなっています。 これからのモバイルGのミッション 前述の通り、「ノンデスクワーカーのエンゲージメント向上から業務DXまで実現するALL-in-One Product」を目指していく中で、モバイルGのミッションが大きく2つあります。 モバイルアプリのフルネイティブ化 これまで、TUNAGはWebをメインに開発を進めてきており、モバイルアプリはWebの機能を取り込む形で成長してきました。 多くの機能がWeb Viewで表現されており、モバイルネイティブなUXからは遠い存在となってしまっています。 WebViewを排除し、ネイティブ実装に切り替えることによってモバイルアプリだからこそ実現可能な操作速度やレスポンスの良さを実現できると考えています。 モバイルファーストな開発スタイルへのシフト モバイルアプリのフルネイティブ化を進めるためには、モバイルGにとどまらず、プロダクト全体の開発スタイルも変えていく必要があります。 すでにモバイル先行の機能開発体制にシフトしつつあり、Webでは実装せずにモバイルでのみ実装する機能も開発が進んでいます。 このためにプロダクト企画やデザイナーにも協力してもらい、設計デザインフェーズからモバイルエンジニアがガッツリ入り込んだプロジェクト進行が実現できています。 最後に プロダクトカンパニー化に向けて、まさに第二創業期のような変革が起きている真っ只中です。 エンジニア組織への投資姿勢は直近のブログ投稿からも感じていただけるのではないでしょうか。 tech.stmn.co.jp tech.stmn.co.jp これからのスタメンは、エンジニア組織へのさまざまな投資をさらに強化していきます。エンジニアにとって一番楽しいフェーズだと自信を持っていえる環境が揃いつつあります。 モバイルGをはじめ、プロダクト組織全体の拡大のため、今後も様々な変化が起きていきます。 日々の成長を自分ごととして感じられる、ライブ感のあるこの時間を同じ目標を持つ仲間として楽しみませんか。 スタメンではモバイルファーストなプロダクト開発を一緒に牽引してくれる仲間を募集しています。 「iOSDC Japan 2023」や「DroidKaigi 2023」へのスポンサーも行っているので、ぜひブースに立ち寄ってくださると嬉しいです! tech.stmn.co.jp もし興味を持っていただけたら、以下のリンクからご応募ください! カジュアル面談もお待ちしています! herp.careers
株式会社スタメンは、2023年9月1日から3日間にわたり、早稲田大学理工学部 西早稲田キャンパスとニコニコ生放送上で開催される「iOSDC Japan 2023」に、ゴールドスポンサーとして協賛し、イベントを盛り上げます。 iosdc.jp 本エントリーでは、登壇情報、ノベルティ、出展ブースについてご紹介いたします。 登壇情報 スポンサーセッションでは、iOSエンジニアの青木が『 VIPERアプリにSwiftUIを導入したら、View層の責務がより分離できた話 』というテーマで話します。 登壇者のコメント: 導入したいとずっと思っていたSwiftUIを、弊社プロダクトの環境下でどうやったら導入できるか、模索し意思決定した過程をお伝えします。皆さんのSwiftUI導入の励みになる話ができればと思いますので、是非セッションにお越し下さい! fortee.jp 登壇者:青木( @38Punkd ) 日時:2023年9月3日 15:05〜(20分) 場所:Track C スポンサーブースのご紹介 オフライン会場のスポンサーブースは、スタメンカラーのテーブルクロス、バナースクリーン、のぼり旗が目印です。 会場内で会話のネタになるようなコンテンツを用意しています。エンジニアメンバーもブースに立ちますので、会場に来られる方はぜひお立ち寄りください。 ノベルティグッズのご紹介 ノベルティ付きチケットをご購入された方には、ノベルティボックスの中にスタメンのオリジナルマルチクロスが同封されます。 13インチのMacBook Air、iPhone13と並べると、このサイズ感 スタメン開発組織のミッションである『プロダクトで事業の成長を牽引する』を英語コピーにした『Unleash your potential, Let product lead』のテキストをあしらったデザインです。 お手元のiPhoneや、メガネを綺麗にするのにぜひご利用ください! 出展ブースでもオフライン限定ノベルティを配布する予定で、エンジニアなら貰って嬉しいアイテムや、小腹が空いた時に嬉しいお菓子などを準備しています。 おわりに 開催期間中は「 iOSDCトークン 」の用意もあるので、iOSDCチャレンジに参加される方はお見逃しなく! iOSDC参加者の皆様と交流できるのを楽しみにしています!3日間楽しみましょう! カンファレンス情報 名称 : iOSDC Japan 2023 主催 : iOSDC Japan 2023 実行委員会 (実行委員長 長谷川智希) 共催:早稲田大学 理工学術院、 早稲田大学グローバル科学知融合研究所 協力:WASEDA-EDGE人材育成プログラム、 Beyond 2020 NEXT PROJECT, NEW 開催日時 : 2023年9月1日(金)〜9月3日(日) 場所 : 早稲田大学 理工学部西早稲田キャンパス + ニコニコ生放送 参加方法や詳細については、 公式サイト をご覧ください。 採用情報 スタメンでは、iOSエンジニアに限らず全技術領域で、プロダクトを成長させていくエキスパートを募集しています。もし興味を持っていただけたら、下記からご応募ください! herp.careers
こんにちは、スタメンCTOの松谷( @uuushiro ) です。 7/25に、 和田卓人さん (以下、t_wadaさん)をお呼びし、「 質とスピード 」の社内講演会を開催しました! t_wadaさんにご依頼したきっかけ スタメンが提供する「 エンゲージメントプラットフォーム TUNAG(ツナグ) 」はスタメンの創業事業で、サービス提供から約7年が過ぎました。この約7年間、プロダクト成長に注力してきた中で、内部品質や開発効率などに関する課題が後手に回っていました。 TUNAGが成長軌道に乗ってきた今、今後もエンジニア組織をスケールさせながら、継続的にプロダクトの価値を素早く世の中に届けていくために、開発者体験(DevEx)に関する明確な戦略が不可欠だと考えており、この課題に集中して取り組むために、今年の4月からCTO室にDevEx(Developer eXperience)チームを立ち上げ、2023年7月現在では3名のチームとして活動しています。 TUNAGにおけるRuby 3.0アップデートと開発組織の今後 はじめまして。2023年4月ごろよりスタメン・TUNAGプロダクト開発にジョインしました、@trowems23です。 スタメンの技術的負債解消戦略 こんにちは、リファクタリング大好きなミノ駆動です。2023年7月より株式会社スタメンにジョインしました。 また、長らくTUNAGの開発効率を妨げていたフロントエンド開発環境においても、今年から腰を据えて抜本的なリアーキテクチャプロジェクトに取り組んでいる状況です。 このようにスタメンでは、2023年のはじめから開発効率への向き合い方が大きく変わり、DevExチーム立ち上げやリアーキテクチャプロジェクトが発足したりと、内部品質や開発効率に対して大きな変化が生まれています。ただ、内部品質や開発効率への向き合い方は、特定のチームやプロジェクトだけで大事にしたい価値観ではなく、全エンジニアの共通の認識としていきたいです。 そんな考えの中で、和田卓人さんの「質とスピード」の講演は、「スタメンのこれから大切にしたいエンジニアリング価値観にマッチしていて、組織全体における共通認識の構築を加速してくれるのでは」と考え、この度ご依頼させていただきました。 講演当日 エンジニアだけでなく、弊社の代表や他の職種メンバーも参加し、全社でプロダクト運営における理解を深めることができました。 具体的な講演の内容については、以下の公開されている資料を参考にしてください。 speakerdeck.com セッションを通じて「内部品質とスピードはトレードオフではなく、品質が高いからこそスピードが上がる」ということを改めて強く意識することができましたし、ソフトウェアの内部品質についても、具体的な品質特性(テスト容易性、理解容易性、変更容易性)で表現することで、今後の内部品質に関する議論の質も上がりそうだと感じました。 また内部品質への投資の損益分岐点は意外と早く(1ヶ月以内)やってくるとの言葉を聞いて、現在DevEx(Developer eXperience)チームを中心として内部品質への投資を行っている活動も、思ったよりも近い未来で効果が見えてくるかもと期待が大きくなりました。損益分岐点を超えたことを、我々がどう実感することができるのかについては、自組織の中で実際にこれから確かめていきたいと思います。 参加者の感想 講演後にアンケートをとったのですが、とても好評でした。以下に参加者の感想の一部を引用します。 リファクタやテスト大事だよねってのはわかってますが、なぜ大事なのかの背景が補完されて自分の意思決定基準が変わったように感じました。 内部品質に力を入れることによって得られる恩恵を受益できるのは1ヶ月以内という話が、今後テストをしっかり書いていくことへのモチベーションになると考えた。 時間を言い訳にして質を落としてもいいのでは?という典型例になっていたので、その意識を変えれたのが一番大きかったです。 内部品質が犠牲にされやすい理由や、質が高いからこそ速いことが理解でき勉強になりました。内部品質を時間に換算して説明するなどbizサイドとのコミュニケーションにも活かせそうだと思いました。 良いエンジニアは質もスピードも両立している スライドは読んだことあったのですが、最新バージョンで口頭での情報補完もあって理解が一層深まりました。自分の考え方にも大きな影響があったように感じています。今回の公演自体が投資だと思うのでちゃんと効果が出るように業務に反映したいと感じました 品質とスピードについての自分の断片的な知識が、体系的に学べました。ありがとうございました! トレードオフスライダーは元々スコープしか動かせないとは知ってましたが、そもそも3つしかないことに気付かされました 今まで品質とスピードのバランスをどうしていこうかが悩みポイントでしたが、2つはトレードオフ関係に無いという話で悩む必要の無いことだったということ確認できたので、1ヶ月後の自分に恩恵が返ってくるように内部品質を今後もより意識していきたいと思った。 和田卓人さん最高の講演をありがとうございました!和田さんの見識を取り入れて僕たちは今よりももっとうまくやっていけそうです! 昔の現場を思い出して、今の環境はとても良い環境(技術力も、理解度も、人も、ビジネスも)だと改めて思いました。 自分の開発を進めていく上で間違っているか悩んでいた考え方が概ね間違っていないことを擦り合わせることができとても良い機会でした。ありがとうございました! やっぱり第三者視点言ってもらうことでバイアスがかって納得する人はいるので、こういう講演があるのはありがたいです。リアルにコメントが飛び交うのも良かったです。 講演は2時間近くでしたが、最初から最後までSlackチャンネルは大盛りあがりで、学びになるだけでなく非常に楽しい時間になりました。そしてなにより、今回の「質とスピード」の講演は、我々スタメンにおける「内部品質に対する共通認識」の構築を加速してくれたと感じています。t_wadaさんありがとうございました! 最後に スタメンでは、ソフトウェアの内部品質に向き合いながらプロダクトを成長させていくエキスパートを募集しています。 もし興味を持っていただけたら、下記から応募ください! herp.careers herp.careers
これはなに? こんにちは、リファクタリング大好きな ミノ駆動 です。2023年7月より 株式会社スタメン にジョインしました。 コミュニケーションには会議体やテキストベースなど様々な手段があります。 その中で雑談がなぜ重要であるかについて、私の考えを記したものです。 大事な前提 〜目的と手段の関係〜 人々の活動には 目的 があります。そして目的を満たすための 手段 を追い求めています(ここでいう手段とはシステムであったり情報であったり、「目的の役に立つもの」と考えてください)。 目的と手段の関係性を次の図で表現します。目的と手段それぞれの円の重なりが大きいほど、目的に対して相応しい手段である、ということをここでは表します。 この図を使った例を出します。 今の時期、だんだん暑くなってきましたね。「暑さを解消したい」という目的に対して、「扇風機を点ける」「エアコンを点ける」「かき氷を食べる」「南極に送り込む」などの手段が考えられます。 おや? 上図では「暑さを解消したい」という目的に対して「かき氷」という手段はあまり重なっていません。どうも相応しくない手段のようです。暑いと感じている人に詳しい事情を尋ねてみると「最近冷たいものを食べ過ぎちゃってさ、お腹の調子が良くないんだよ。食べ物以外で涼めるものがあればなぁ」という回答でした。 この人にとっては「胃腸に負担をかけずに暑さを解消したい」が 真の目的 だったのです。この目的に沿うようエアコンを提供したところ、目的が満たされました。 この関係性はソフトウェア開発も同じです。 例えば弊社スタメンの主要サービス TUNAG (ツナグ)は、企業のエンゲージメントの構築、つまりお互いを知って理解し、信頼し合う組織を作るための社内コミュニケーションを活性化させるプロダクトです。「 エンゲージメントを向上させる 」目的を満たす手段としてプロダクトを開発しています。よりこの円の重なりが大きくなるよう機能開発や機能改善が図られています。 ここまでが前提です。 ここから先が本題です。 会議体だと重要な情報を得にくい 私の生業はソフトウェア設計ですが、仕事柄、正確な設計には顧客課題や顧客要求など、ビジネス面の様々な情報を必要とします。そのため、PdMのほかビジネスサイドの皆さんへの相談やインタビューを必要とします。 こうした「何らかの重大な判断に必要な情報を収集する活動」は、別に私に限らず皆さん普段から当たり前のように実施されていると思います。 しかし私の経験上、 会議体での 相談やインタビューで「本当にほしい重要な情報」を得られた経験があまりありません。 そうではなく、 雑談でふとした拍子にポロッと出てきたセリフに重大な情報が含まれていた経験が圧倒的に多いです 。それはなぜでしょう。 意図せず重要な情報が削ぎ落とされてしまう 会議には必ず目標を設定します。「◯◯について意思決定する」「◯◯について認識を合わせる」などです。 ここでは「目的Aに関して意思決定する」を会議の目標とします。さて、このとき何が起きるでしょうか。 会議の目標を効率的に達成できるよう、会議の参加者は、目的Aに沿った準備をします。また、目的Aに則った会話が交わされます。つまり、 様々な情報の内、目的Aに沿ったものだけが話題に上がり、それ以外の情報は削ぎ落とされます。 一見効率的で正しいアプローチのように思えます。ところがそうではありません。 顧客が本当に必要だったもの 有名な「顧客が本当に必要だったもの」の図( Tree Swing Cartoon Pictures (early versions) より引用)。 この図に揶揄されるように、顧客の本当の要求が何であるのか、プロダクト開発において常に考え悩ませるものです。そのため我々は仮説検証やABテストなどを実施して 顧客の真の目的 を探りに行きます。 そして真の目的が本当は目的Bなのにも関わらず、偽の目的Aが真の目的だと勘違いしていることが往々にしてあります(そもそも顧客の真の目的がはじめから分かっていたら誰も苦労しない)。 偽の目的Aをテーマに据えて会議を進めた場合、目的Aに関する情報は話題に上がりますが、真の目的Bに関する話題は削ぎ落とされてしまいます。 会議の効率化が仇になって、本当に必要なものが手に入らない、または入りにくくなってしまうのです。 雑談では「情報の悪しき削ぎ落とし」が生じにくい 一方で雑談は特定の目的がありません。そのため、さまざまな情報が話題に上がりやすくなります。会議体で陥りがちな「意図しない情報の削ぎ落とし」が生じにくくなります。 このような特性があるため「 話者にとって重要だとは認識していないが実は重要な情報 」がポロッと話題に上がり、判断精度の劇的な向上に貢献することになるのです。 おわりに 以上解説しましたように、弊社スタメンでは雑談の効能を重要視しています。そしてサービスの改善革新や質の向上に役立てています。 弊社スタメンはTUNAGを中心に右肩上がりの成長を続けています。 そしてこの成長をさらに促進させるためにサービスの設計戦略を策定し、推進していく計画です( 詳しい戦略はこちら、 『スタメンの技術的負債解消戦略』 )。 2023年12月期 第1四半期決算説明資料 より この勢いで弊社スタメンはさらなる事業拡大を目指し、採用活動をより活性化していきます。現在従業員数は約80名。今の規模の2倍、3倍、5倍とスケールさせていきます。 エンジニア絶賛募集中です。私と一緒に働いてみたい、興味がある方は、ぜひ下記にアクセスしてみてください。 herp.careers
1. これはなに こんにちは、リファクタリング大好きな ミノ駆動 です。2023年7月より 株式会社スタメン にジョインしました。 この記事は、今後スタメンにおいてサービスの技術的負債を解消する設計戦略についてまとめたものです。 2. 背景、課題 株式会社スタメンは2016年創業。主要サービスである TUNAG (ツナグ)は、企業のエンゲージメントの構築、つまりお互いを知って理解し、信頼し合う組織を作るための社内コミュニケーションを活性化させるプロダクトです。TUNAGのバックエンドはRuby on Railsで開発され、ローンチから7年をむかえつつあります。 これまでTUNAGは、プロダクトをいかに伸ばすかに注力してきた一方、内部品質や開発効率など「開発者体験」に関する課題が後手に回っていました。本来プロダクトチームはユーザーにとっての本質的な価値にのみフォーカスできる状況が理想ですし、開発者体験が悪いと良いユーザー体験を提供することができなくなっていきます。 開発者体験を悪化させる大きな要因のひとつが技術的負債です。具体的には 変更容易性 の低下です。変更容易性とは、なるべくバグを埋め込まず、素早く正確にコード変更できる度合いです。変更容易性が低いと開発生産性が低下してしまいます。 3. 設計戦略解説 サービスの変更容易性を向上し、開発生産性の継続的向上を果たすため、以下の図に示す設計推進活動を私主導のもとこれから実施していきます。 %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#000000' } } }%% graph TD A[開発生産性の<br>継続的向上] --- B[変更容易性の<br>継続的向上] B --- リファクタリング B --- C[変更容易性<br>設計スキルの向上] リファクタリング --- D[費用対効果の<br>高い箇所] D --- コアドメイン D --- H[負債レベルの高い箇所] コアドメイン --- 中長期事業計画 コアドメイン --- E[有識者への<br>インタビュー] コアドメイン --- コンテキストマップ作成 H --- K[ツールによる<br>負債計測] H --- G[変更失敗率の<br>高い箇所] H --- I[変更の苦しい箇所] コンテキストマップ作成 --- F[ドメインエキスパートへの<br>インタビュー] コンテキストマップ作成 --- ソースコード解析 F --- 目的 F --- 登場概念 F --- アクター F --- プロセスサイクル C --- 設計ガイドライン C --- ハンズオン勉強会 設計ガイドライン --- データモデル 設計ガイドライン --- アーキテクチャ かなり多岐にわたるため説明が長くなりますが、ぜひ最後までお読みいただければと思います。 変更容易性の向上には大きく2軸の活動があります。既存の技術的負債を解消するリファクタリングと、今後の新たな負債を抑止するための変更容易性設計スキルの向上活動があります。 3.1 リファクタリング リファクタリングとは、外部から見た挙動を変えずに、プログラム構造を整理することです。変更容易性の高い構造に整理することがリファクタリングのゴールです。そのため、変更容易性の高いあるべき構造を設計したり、設計のためにドメインを分析したりと、多岐にわたるさまざまな活動が必要です。 3.1.1 費用対効果の高い箇所の特定 リファクタリングはただ実施すればいいというわけではありません。例えば粗悪なコードであっても、今後ほとんど仕様変更の見込みがない箇所を一生懸命リファクタリングして意味があるものでしょうか?リファクタリングは将来の変更コストを低減する活動です。従って、将来あまり変更されない箇所をリファクタリングしてもコストがかかるだけで役に立ちません。開発リソースは有限なので、限られたリソースの中でリファクタリングの費用対効果の高い箇所を狙う必要があります。 費用対効果の高い箇所の特定には、主に以下2つを複合的に考慮して判断します。 コアドメイン 負債レベルの高い箇所 3.1.1.1 コアドメイン どんな商品やサービスにも、「これがウリだ!」と呼べるような中心的価値があります。中心的価値を発揮する事業領域を、ドメイン駆動設計では コアドメイン と呼びます。コアドメインは差別化が図られ、企業の競争優位性を発揮する領域です。サブ的な領域よりも投資優先度が高いです。当然設計コストも同様です。競争優位性をより高めるために技術的負債を解消し、開発生産性を高めることが肝要です。 ただし、サービスが大きくなると何が中心的価値なのかだんだん分からなくなっていきます。また、時代によって人々の関心事は移り変わっていきます。特にコロナ禍前後でドラスティックに世の中の価値観が変化したのは皆さんの記憶に新しいところだと思います。何が中心的価値なのかを見定める必要があります。そのためには以下の調査や分析が必要になります。 中長期事業計画 有識者へのインタビュー コンテキストマップ作成 3.1.1.1.1 中長期事業計画 中長期事業計画とは、中長期的な経営ビジョンを実現するために事業としてやるべきことを計画したものです。中長期的にどのように事業価値を伸長していくのか、進むべき方向性が定義されます。そしてソフトウェアの変更容易性は、中長期にわたって開発生産性に影響を及ぼします。コアドメインを見定めるため、また変更容易性設計の投資価値を見定めるためにも中長期事業計画のinputは重要です。 3.1.1.1.2 有識者へのインタビュー 中長期事業計画だけでなく、有識者から生の声を聞くことも大事です。プロダクトマネージャーの他、経営レベルに近いメンバーに対し、コアドメインに相当する事業領域が何であるのかインタビューします。 3.1.1.1.3 コンテキストマップ作成 ソフトウェアサービスには、中心的存在となるモデルが必ずといっていいほど登場します。例えばECサイトにおける商品モデルです。このようなモデルはさまざまな状況、さまざまなユースケースで用いられます。しかし、全ての状況に対応可能な、一枚岩の万能モデルとして作り上げようとすると、ロジックが混乱する、変更が困難になるなど多くの弊害を招きます(以下は前職所属時のイベント登壇動画と資料)。 www.youtube.com speakerdeck.com そこで 万能モデルとは異なる設計アプローチ が必要です。背景、状況、目的が大きく異なる事業ドメインの単位でサービスをサブシステムに分解し、各サブシステムに 特化したモデル として設計することの重要性をドメイン駆動設計では説いています。状況別の各特化型モデルそれぞれが適用可能な範囲を「境界づけられたコンテキスト」と呼びます。 このようにしてコンテキストごとに分解したサブシステムの内、コアドメインに対応するサブシステムを特定し、コアにコア以外のロジックが浸潤しないよう明確に区分付けることの重要性をドメイン駆動設計では説いています。ここまでやって初めてリファクタリングの「選択と集中」を実施可能な、費用対効果の高い箇所の特定が可能になります。 コンテキスト境界がどこにあるのかはさまざまな観点での分析が必要です。例えば以下です。 ドメイン分析 ソースコード解析 3.1.1.1.3.1 ドメイン分析 コンテキストごとにどんな違いがあるのか観点はさまざまですが、例えば以下のような点で違いがあると私は考えます。 目的 アクター 登場概念 プロセスサイクル コンテキストの違い この図はECサイトを例にしたコンテキストの差異を説明した図です。 まず、大きな粒度で目的が異なります。コンテキストをまたぐような巨大な一枚岩モデルは、多目的に使われてしまうので単一責任原則違反と考えることができます。コンテキスト特化型モデルの設計は、単一目的に対応できるように設計することです。つまり、目的が大きく異なる境界がコンテキスト境界と考えることができます。 また、目的が異なると、アクターや登場概念が大幅に違ってきます。例えば、 在庫管理 アクター : 出品者 登場概念 : 入庫、出庫、安全在庫量 配送 アクター : 配送業者 登場概念 : 配送元、配送先、配送状況、配送料 このように周辺概念を整理することもコンテキスト境界の発見につながります。 また、コンテキストごとにプロセスサイクルが異なります。在庫管理では出品者が入出庫のサイクルを回します。一方で配送では配送業者が梱包し、配送先まで配送するサイクルを回します。お互いのサイクルは交わることがありません。 このような違いを探るため、ドメインエキスパートへのインタビューやイベントストーミングなどでドメイン分析します。 3.1.1.1.3.2 ソースコード解析 コンテキストの違いは当然ソースコードにも現れます。 これはある程度目処をつけて調査することが可能です。例えば、以下のようなモデルは複数のコンテキストにまたがっていることがとても多いです。 巨大なモデル サービスのワークフローの始めから終わりまでいるようなモデル このようなモデルは、状態によって以下が大きく異なる特徴があります。 更新対象のデータ 振る舞い これらの差異がコンテキスト境界の手がかりとなります。 3.1.1.2 負債レベルの高い箇所 リファクタリング優先度の高いのはコアドメインのロジックですが、その中でも負債レベルの高い箇所がより優先度が高まります。負債レベルの高い箇所の割り出しには、例えば以下を実施します。 ツールによる負債計測 変更失敗率の高い箇所の特定 変更の苦しい箇所の特定 3.1.1.2.1 ツールによる負債計測 技術的負債の分析はツールにより計測可能です。弊社では Code Climate Quality を利用しております。Code Climate QualityはGitHubと連携し、負債やファイルの更新頻度を自動でスコアリングしてくれます。技術的負債と変更容易性は未来の変更コストに影響しますから、負債レベルと更新頻度の双方が高いものほどリファクタリングの効果が高いと考えられます。 3.1.1.2.2 変更失敗率の高い箇所の特定 本番デプロイ後に修復を要するコードの割合を変更失敗率といいます。変更失敗率を計測し、失敗に関与したソースコードを割り出します。 3.1.1.2.3 変更の苦しい箇所の特定 エンジニアにとって変更の苦しい箇所を特定します。どのあたりのコードが厄介なのか、いつも変更に苦慮しているのかをアンケートなどで聞き出します。 3.2 変更容易性設計スキルの向上 負債の増加を抑止するには、エンジニアの設計スキル向上が大事です。 ガイドラインなどドキュメントを揃えたり、勉強会を開催するなどテコ入れを要します。 3.2.1 設計ガイドライン 設計にはゴールが必要です。ゴール方針となる設計ガイドラインを策定します。ゴールを据えることで、そのギャップとして技術的負債を認知できるようになります。 少なくとも以下2点のガイドラインをまず用意します。 データモデル設計 アーキテクチャ設計 3.2.1.1 データモデル設計 DBのテーブル構造が負債化しないよう、データモデルの設計方法を取りまとめておく必要があります。Railsの場合、テーブルと1:1になるActiveRecordがさまざまなレイヤと密結合になりやすく、テーブル構造が全体にダイレクトに影響を及ぼすため、特に設計に注意を払う必要があります。 設計にはTM(T字形ER手法)を用います。 イミュータブルデータモデル(入門編) from Yoshitaka Kawashima www.slideshare.net 3.2.1.2 アーキテクチャ設計 アプリケーションアーキテクチャ全体の再設計も必要です。 RailsはMVCフレームワークであり、普通はRails-wayと呼ばれるお作法に則って開発が進められます。しかしRailsは、スタートアップ時の加速力を得るため結合度を犠牲する「犠牲的アーキテクチャ」と呼ばれています。そのため開発が進み数年もするとモデルが複数の意味を持ち始めたり、最新のドメイン理解に対してテーブル構造が陳腐化して乖離が激しくなったりします。Rails-wayでは負債の解消や抑止が困難になっていきます。 この解決のため、モジュラーモノリス化することを目標に、以下のようなDDDベースのアーキテクチャへの移行を検討しています。このアーキテクチャの意図や方針、各責務の役割や設計方法をエンジニアに説明するため、アーキテクチャ設計のガイドラインを策定します。 %%{ init: { 'theme': 'base', 'themeVariables': { 'background': '#000000', 'primaryTextColor': '#6360DC' } } }%% classDiagram namespace Views { class View } namespace Models { class ActiveRecord } namespace Controllers { class Controller } namespace UseCases { class UseCase class QueryService { <<interface>> } class Dto } namespace Infras { class RepositoryImpl class QueryServiceImpl } namespace Domains { class Repository { <<interface>> } class Aggregate class Entity class ValueObject } ActiveRecord <.. RepositoryImpl ActiveRecord <.. QueryServiceImpl View ..> Controller Controller ..> UseCase Controller ..> QueryService UseCase ..> Aggregate UseCase ..> Repository Repository ..> Aggregate Aggregate --> Entity Aggregate --> ValueObject Entity --> ValueObject RepositoryImpl ..|> Repository QueryServiceImpl ..|> QueryService QueryService ..> Dto (※1:全体に対して適用するのではなく、コアドメインなど負債に対して特に注意を払う必要がある箇所についてDDDベースに移行します。負債が気にならない箇所、設計コストかける旨味があまりない箇所はRails-wayでいきます。) (※2:Rubyではinterfaceは心の中にしかないので、上図におけるRepositoryやQueryServiceのinterfaceは実際には存在しません。状況によってはマーカーinterfaceの代用としてmoduleを実装する場合があります。) 3.2.2 ハンズオン勉強会 設計スキルの大幅な向上には、なんといっても実際に手を動かすハンズオン勉強会が欠かせません。 プログラミングはただプログラミング入門書を読んだだけでは、実務に使えるプログラミングスキルは身につきません。仕様を満たすロジック構造を考えて実装する経験が必要です。設計スキルも同様で本を読めば身につくものではなく、泥臭く混乱したロジックをどのように整理すれば変更が容易な構造になるか、自分の頭で考えて設計する経験が必要です。 勉強会ではプロダクションコードを使ってリファクタリングの練習をします。泥臭いロジックほど設計の旨味があるためです。例えば拙著 『良いコード/悪いコードで学ぶ設計入門』 gihyo.jp に記載の設計パターンを学ぶ場合、そのパターンを適用できそうなロジックをプロダクションコードから探します。普段エンジニアが触り慣れている箇所が望ましいです。該当するロジックが見つかったら、挙動が同じで変更容易性の高い構造を設計し、コードを実装します。それぞれの成果をみんなの前で発表し、どんな工夫をしたかなどについて議論や質疑応答し、フィードバックを得ます。 私は数社でこの勉強方法を実施した結果、どこでも設計スキルの向上を果たせました。効果に再現性があると自負します。オススメです。 4. おわりに 以上説明した戦略のもと、変更容易性を向上し開発生産性を継続的に高めるべく邁進していきます。 弊社スタメンはTUNAGを中心に右肩上がりの成長を続けています。この成長をより高めるため、私に課せられた責務は非常に重要なものになっております。 2023年12月期 第1四半期決算説明資料 より この勢いで弊社スタメンはさらなる事業拡大を目指し、採用活動をより活性化していきます。現在従業員数は約80名。今の規模の2倍、3倍、5倍とスケールさせていきます。 エンジニア絶賛募集中です。私と一緒に働いてみたい、興味がある方は、ぜひ下記にアクセスしてみてください。 herp.careers
こんにちは、株式会社スタメンで TUNAG のiOSアプリエンジニアをしている青木 ( @38Punkd )です。 何気に今回の記事がこの Tech Blog への初投稿で、ワクワクしています。 TUNAGのiOSアプリは、これまでリアクティブプログラミングの手法として、 RxSwift を導入してきました。 そして今年度から、アプリがサポートするOSバージョンの下限を13.0に引き上げたため、Apple公式の非同期フレームワーク Combine が使えるようになりました。 アプリに対してサードパーティ製のライブラリであるRxSwiftへの依存度を下げたかったことと、純粋に新しい技術を試してみたいという好奇心も相まって、アプリにCombineを導入することを試みました。 実際にCombineを導入してみた感想と、導入する際の注意点をお伝えできれば思います。 SingleからFutureへの移行 通信は大別すると、結果の受け取りを継続して監視する必要のあるものと、結果の受け取りが一回きりで良いものの2種類がありますが、 今回は、Combineの中でも一回きりの非同期処理を監視する Future 型を使って、一回きりのAPI通信の結果を取得する実装をしました。 これまでは、一回きりのAPI通信部分は、RxSwiftのSingleを用いて以下のようなコードを書いていました。 結果を発行する側 Singleを用いて、非同期処理の結果を通知できるオブジェクトを返すメソッドを用意します。 // データレイヤー(APIと通信をする) class Repository { func fetchTaskList () -> Single <[ TaskEntity ]> { Single < [TaskEntity] > .create(subscribe : { observer in APIClient.request { response in observer(.success(`TaskEntity型`))) } return Disposables.create() }) } } 結果を受け取る側 上記のSingleを、subscribeメソッドを用いて購読し、結果を非同期に受け取れるようにします。 // ドメインレイヤー(ビジネスロジックを扱う) class Interactor { func requestTaskList () { Repository().fetchTaskList() .subscribe( onSuccess : { taskList in // API通信の結果取得した taskList を出力します } onFailure : { error in // errorを出力します }) .disposed(by : disposeBag ) } let disposeBag = DisposeBag() } これを、CombineのFuture型を用いて以下のように書きました。 結果を発行する側 Futureを用いて、非同期処理の結果を通知できるオブジェクトを返すメソッドを用意します。 // データレイヤー(APIと通信をする) class Repository { func fetchTaskList () -> Future <[ TaskEntity ], Error > { Future < [TaskEntity], Error > { [unowned self ] promise in APIClient.request .sink { completion in switch completion in case .failure( let error ) : promise (.failure(error)) case .finished : () } receiveValue : { response in promise(.success(`TaskEntity型`)) } .store( in : & cancellables) } } var cancellables = Set < AnyCancellables > () } 結果を受け取る側 上記のFutureを、sinkメソッドを用いて購読し、結果を非同期に受け取れるようにします。 // ドメインレイヤー(ビジネスロジックを扱う) class Interactor { func requestTaskList () { Repository().fetchTaskList() .sink { completion in switch completion in case .failure( let error ) : // errorを出力します case .finished : () } receiveValue : { taskList in // API通信の結果取得した taskList を出力します } .store( in : & cancellables) } var cancellables = Set < AnyCancellables > () } Futureは、非同期の結果や値を表すための型であり、RxSwiftでいう Single と同じ役割を担います。 sink メソッドは、監視可能なイベントを実際に監視するためのメソッドです。 イベントの監視状況は、sinkメソッドのトレイリングクロージャに入ってくる値(上記のコードでは completion という変数名で表しています)から確認できます。 この変数は、 failure と finished の二つのケースを持つenum型です。 監視中にイベントが発生(=この場合、タスク一覧の取得が完了する)すると、クロージャ型の引数 receiveValue に値が入ってきます。 最後に store メソッドによって、sinkメソッドの AnyCancellable 型の戻り値をcancellablesプロパティに保存します。 こうする事で、Future型をインスタンス生成した後も、イベントを監視できる仕組みが出来上がりました。 Combineを使う上で気を付けるべきポイントは2つありました。 sink メソッドの戻り値AnyCancellableは保持する必要がある Future はインスタンス生成直後に監視を開始する これらのポイントについて、詳細に解説していきます。 sink メソッドの戻り値AnyCancellableは保持する必要がある sinkメソッドはAnyCancellableを返却値として返します。 func sink (receiveValue : @escaping (( Self.Output ) -> [ Void ]( https://developer.apple.com/documentation/Swift/Void )) ) -> [ AnyCancellable ] (https : //developer.apple.com/documentation/combine/anycancellable) AnyCancellableは、非同期処理の中断を表すために使用される型です。 非同期処理が完了した後も監視を続ける場合、AnyCancellableのインスタンスを保持し、必要なときに中断することができます。 しかし、AnyCancellable型の戻り値をプロパティとして保存せずに捨ててしまうと、そのインスタンスは参照されず、すぐに解放されてしまいます。 その結果、監視が中断されます。 AnyCancellableの非同期を中断する方法は以下の2パターンがあります。 AnyCancellableのインスタンスが破棄される。 AnyCancellableに対して、 cancel メソッドを呼び出す。 AnyCancellableのインスタンスが破棄された場合に、AnyCancellableの非同期が中断されるのは、破棄されたタイミングでAnyCancellable自身がcancelメソッドを呼び出す仕様になっているからです。 なので、AnyCancellableインスタンスに対してstoreメソッドを実行して、プロパティとして保存しない場合、AnyCancellableインスタンスは解放され、監視が終了してしまいます。 class Interactor { // × // requestTaskListメソッドのスコープを抜けると、 // sinkメソッドの戻り値はすぐに解放されてしまう、良くない例です。 func requestTaskList () { _ = Repository().fetchTaskList().sink { ... } } } (おまけ) storeメソッドを呼ばない場合は、以下のようにも書けます。 class Interactor { func requestTaskList () { cancellable = Repository().fetchTaskList().sink { ... } } var cancellable : AnyCancellables? } なおこのAnyCancellables型のプロパティは、保持しているクラスが破棄されると、もちろん同時に破棄されますが、同時に監視も終了します。 なので、明示的に監視の中断処理を、デイニシャライザの中に書く必要はありません。 class Repository { // Repositoryのインスタンスが破棄されたら、cancellableも破棄されるので、 // このdeinitメソッドは不要です。 deinit { cancellable.cancel() } ... var cancellable : AnyCancellables? } Future はインスタンス生成直後に監視を開始する RxSwiftのSingleは、subscribeメソッドを実行するまで、監視を開始しません。この性質は Cold Observable と呼ばれます。 しかし、Futureはインスタンス生成された直後に監視を開始します。この性質は Hot Observable と呼ばれます。 Singleと違い、FutureはHotであることを意識しないと、以下のように意図しないタイミングで処理が走ってしまいます。 class Repository { let taskList : Future <[ TaskEntity ], Error > = Future < [TaskEntity], Error > { in cancellable = APIClient.request.sink { ... } } var cancellable : AnyCancellables? } class Interactor { ... } 上記のコードでは、 Repository がインスタンス生成された瞬間に、taskListを取得するAPI通信が走ってしまいます。 Future型を使う際は、意図しないメモリ消費を避けるために、ストアドプロパティとして定義するのではなく、メソッドの戻り値や計算プロパティとしてあげるのが良さそうです。 // ⚪︎ // fetchTaskListメソッドを呼び出して、初めてFuture内部が実行されます。 func fetchTaskList () -> Future <[ TaskEntity ], Error > { ... } // ⚪︎ // fetchTaskListプロパティを参照して初めて、初めてFuture内部が実行されます。 var fetchTaskList : Future <[ TaskEntity ], Error > { ... } // △ // fetchTaskListを保持するクラスがインスタンス生成された瞬間に、Future内部が実行されてしまいます。 // 意図しない挙動につながる怖い実装です。 var fetchTaskList = Future < [TaskEntity], Error > { ... } Combineに触れてみて 実際にCombineの機能の一部を導入してみた結果、書き方はRxSwiftと非常に似ていて、かつ処理の流れがより一層直感的なコードになったと感じました。 プロダクト保守性の観点からも、アプリの採用技術をApple公式のフレームワークに徐々に寄せていきたいと思っていますし、これを機に、Combineの導入をより推進できればと思いました。 弊社では、このようにして新技術を積極的に導入検討し、より良いプロダクトを追い求めて開発をしています。 人と組織を強くする HR Tech SaaSプロダクトを作りながら、技術でワクワクしたいソフトウェアエンジニアを、全技術領域で募集しています。 お得意の技術領域を問わず、ぜひカジュアルにお話ししましょう! herp.careers
アーキテクチャ図(完了後) こんにちは。当社が スポンサー参加したRubyKaigi 2023 が終わって1ヶ月以上経ち、6月は海外カンファレンスも多く忙しい日々を過ごしています。 最近はまたTUNAG全般をいじっています。 TUNAGのメインアプリ(Ruby on Railsベース)は 4月にRuby 3.0へのアップグレードした のち、5月前半にはRuby 3.1へのアップグレードが完了していました(ブログ記事なし)。執筆時点では、Ruby 3.2へのアップグレードは進行中との噂です。 2019年6月開催の 名古屋Ruby会議04 でもご紹介した通り、コンテナ基盤で動くRubyベースサービスとは別に、AWS Lambda上で動かしているバックエンドサービスがあります。これらのサービスでは、AWS Lambda公式提供runtimeの都合上もあり、最新版であり、AWS独自のサポートによるruby2.7 (コミュニティから出ているRuby 2.7) を利用していました。(注:一部AWS Lambda ruby2.5 runtimeも動いていましたが、こちらも2023年5月までにAWS Lambda ruby2.7 runtimeへの移行が完了しています。) 当社のプロダクトでのAWS Lambdaの利用状況については以下をご覧ください。(記述は執筆当時のものであり、既にリアーキテクティングされたなどにより、他のアーキテクチャで置き換えられた可能性もあります) tech.stmn.co.jp 我々のチームが技術検討した時点では、AWS Lambda ruby2.7 runtimeのサポート期日は、少なくとも2023年7月以降であると理解したため、急いでCustom runtimeを利用した基盤への移行を見送ることを決定していました。 アーキテクチャ図(当初) (注: Ruby 3.2ベースのruntimeがGA公開されてから6ヶ月と定められていた、というのが当時記述されていたと思うのですが、現在の公式ドキュメントからは削除されているようで、また、履歴として、最近 deprecated 扱いとされてしまった awsdocs も調査しましたが、見つけられることができませんでした。完全に蛇足ですが、今回の調査の過程でも困った、 https://aws.amazon.com/blogs/aws/retiring-the-aws-documentation-on-github/ の公式の報告(Retiring the AWS Documentation on GitHub, on 17 MAY 2023, by Jeff Barr)にもある通り、AWS DocumentationがGitHub上で管理できなくなったのは残念な限りです。) そんな中、Ruby 3.2 runtime now available in AWS Lambda by James Beswick | on 07 JUN 2023という朗報が先週入ってきました。 aws.amazon.com それを眺めた時点では、追加のruntime移行タスクを2023年11月末(より詳細な日付としては12月7日ですが)までに積んでおけばいいかくらいの温度感で手元の仕事に戻っていました。 次の週になって、RubyKaigi 2023関連の文脈で1つのツイートが社内チャットに上記の記事をリツイートされてきたことにより、急遽ruby3.2 runtime移行を推進することになりました。AWS Serverless Application Model (SAM) を利用しているものの、包括的なDevOpsが不十分であったこともあり、一手間かけなくてはなりませんでしたが、2.xから3.xとメジャーバージョンアップであったにもかからず、半日とかからず、productionリリースに漕ぎ着けることができました。 Lambda(Management Console) 効果 Ruby 3.1, 3.2あたりの注目ポイントとしてはYet Another Ruby JITであるYJITが導入され、production-readyであることが挙げられます。しかし、デフォルトでは無効であるため、今回はYJITを有効にしない形で、アップデートリリースのみを優先しました。 移行したLambda Functionは外部APIとの接続が最も占める割合が多いのですが、それが含まれないInvocationのDuration(Lambdaプラットフォームで計測され提供される値)では10-20%の高速化が観測できました。これだけの高速化はコミュニティリリースのRuby 2.7 MRI→Ruby 3.2 MRI (YJITなし)のみで経験したことがない次元であり、Lambdaで活用しているCompute基盤で利用しているCPUアーキテクチャが最新版になったのかLambda内部の何らかの改善が寄与しているのではないかと感じました。ユーザー体験、コスト面的にはよくなっているのでそれでいいかという気持ちです。それ以上プラットフォーム内部に詳しくなってもしょうがないですし。 10-20%の高速化を実現したLambda Functionでは、正規表現が割と多用されており、Ruby 3.2での同機能の改善によるものかもしれません。 まとめ Ruby 3.2.0がリリースされてから約半年した時点でAWS Lambda ruby3.2 runtimeがリリースされ、TUNAGプロダクトの一部で利用しているLambda functionで、ruby2.7→ruby3.2 runtimeの刷新を実施しました。特に問題が起きることなく、速度面、コスト面で若干の改善が見られました。 TUNAGへのYJITの本格導入や残りの半分ほどのruby2.7 runtimeのruby3.2 runtime完全移行、メインRailsアプリケーションのRuby 3.2移行、Ruby 3.3移行準備など、やりたいことがたくさんあるのですが、エンジニアが全く足りていません。 株式会社スタメンはRubyKaigi 2023ゴールドスポンサーです。Rubyを使う使わないに限らず、人と組織を強くする HR Tech SaaSプロダクトを作りながら技術でワクワクしたいソフトウェアエンジニアを全技術領域で募集しています。お得意の技術領域を問わずぜひカジュアルにお話ししましょう! herp.careers クレジット/おまけ 画像の一部にはAWS Architecture Icons (Release 16-2023.04.28)が利用されています。 aws.amazon.com awsdocs/aws-lambda-developer-guide@192b0ef7 の doc_source/lambda-runtimes.md : github.com なお、今日の朝ご飯はシンガポールチキンライスでしたが、写真はありません。