UPSIDER Tech Blog

ユーザーの利便性にチームで向き合うエンジニアとカスタマーサポート #UPSIDER_Tech

今回はチームを超えて協業している2チーム、法人カード事業「UPSIDER」の開発チームにおいて顧客対応の改善業務をミッションとするエンジニアとカスタマーサポートチームの紹介です!ユーザーの声を起点に連携する両チームは、相互作用によってどのような顧客メリットを生んでいるのしょうか。

法人カード「UPSIDER」事業のCardチームでPO/EM & Backend Engineerを務めるRyo(早坂 涼 以下、Ryo)とカスタマーサポートチームでマネージャーを務めるYumiko(川手 裕美子 以下、Yumiko)に、当社VPoEのYusuke Izumi(泉 雄介 @yizumi 以下、Yusuke)が話を聞きました。

プロダクト開発チームと密に連携するカスタマーサポートチーム

──まずはおふたりの自己紹介をお願いします。

Ryo: 早坂涼と申します。北海道出身です。上京してからエンジニアとしてSIerに2年ほど勤めたあと、前職では動画配信サービスを運営するビデオマーケットで約5年、プロダクト開発に取り組みました。そこで開発の面白さを知ったのですが、一段落したところでサービスをより育てられる別業種の仕事に挑戦してみたいと思い、UPSIDERに入社しました。挑戦者を応援するサービスを提供していることに魅力を感じ、自分も一緒にチャレンジできるのではと思ったのが理由です。

Yumiko 川手裕美子です。出身は長野です。UPSIDERに入る前は、宿泊業界で長く仕事をしていました。宿泊施設で5年ほど、施設の立ち上げや運営に携わったあと、宿泊領域のサービスを手がけるスタートアップへ転職しました。主に宿泊施設に導入いただく、予約キャンセル料を回収するtoBプロダクトのセールス兼カスタマーサクセスとして、ユーザーのオンボーディングをサポートしていました。

コロナ禍をきっかけにサービスはクローズしてしまったのですが、ユーザーに近い立場で課題解決をする面白さを体感しました。そんななかでUPSIDERのことを知り、ミッションをはじめ興味をもったので、入社を決めました。

お客様に安心して使い続けていただくための顧客体験を提供するCX Engineering

──まずはCardチームにおけるCX Engineering領域について、紹介をお願いします。

Ryo: CX Engineering領域(通称、CXE)のミッションは、カスタマーサポートや社内の運用の改善を通して、ユーザーにプロダクトを安心して使い続けていただくための体験を提供することです。

これまではユーザーから改善の要望があってもなかなか開発に着手できないケースがあり、「改善の要望や不具合の指摘に対して率先して取り組むチームがあれば…」と考え、生まれました。当初はいちチームとして独立していたのですが、現在はUPSIDERのウェブ機能を開発するチームと合併し、チームの一つの取り組みとして注力しています。

──現在取り組んでいるタスクや技術的なチャレンジについて、具体的に教えてもらえますか。

Ryo: CXE領域では、UPSIDER運用オペレーション管理システムを提供することによって、開発・ビジネスチームを問わず社内メンバーがプロダクト運用をしやすい環境を作ることを目指しています。その際、運用業務の全体をふまえて「どうすればより活動がしやすいのか」をエンジニアが検討し、開発に取り組んでいます。

最終的にユーザーや社内メンバーがハッピーになれる施策は何なのか、常に自分たちで考えて行動する必要があるのが、このチームの面白いところでしょうか。自ら考えて自由に提案して開発に取り組めるのは醍醐味だと思います。

──立ち上げ以降、目指していた取り組みは実現できていますか。

Ryo: 2024年2月頃に一度チームとして「CXEチーム」を立ち上げ、そこから機能リリースを徐々に増やしていました。経理チーム、カスタマーサポートチーム、PRチームなど、それぞれのチームから改善の要望があるため優先順位をつけながら、業務を効率化させるなどのインパクトの大きなものから着手し、着実にリリースを続けられていると実感しています。2024年11月からは、チームとしての機動性を担保することを目的に、UPSIDERのウェブ機能開発チームと合流しました。

ユーザーの声を起点にプロダクト体験を進化させるカスタマーサポートチーム

──続いてYumikoさん、所属のチームについて教えてください。

Yumiko カスタマーサポートチームに所属しています。ミッションはユーザーの困りごとを解決することなのですが、UPSIDERの体験をユーザーの声を起点に進化させていくチームでありたいと考えています。日々のユーザーからの問い合わせに対応するだけではなく、「長期的にユーザーにとってどう改善をしたら「挑戦者を支える」ことができるのか?」を考えながら施策を検討、実施しています。

──UPSIDERのカスタマーサポートチームは、事業開発部門のなかでエンジニアチームに併設されました。開発とカスタマーサポートの密な連携がその狙いかと思いますが、組織変更を経て何か感じていることはありますか。

Yumiko カスタマーサポートはユーザーの声がいちばん多く集まるチームなので、さまざまな角度から問い合わせが寄せられますし、その結果プロダクトに対する解像度も高まります。そのため、プロダクトの改善について考えることも多いのですが、エンジニアと連携することでその本質的な解決策を技術的に講じられると感じています。

開発側もカスタマーサポート側もユーザー体験全体をふまえた解決策を考えようという共通意識があるので、より課題解決に向けて取り組みやすくなっていますね。

特にCXE領域に携わるメンバーは、ユーザーからのフィードバックへ真摯に向き合ってくれるので、とても救われていると感じています。例えば、カスタマーサポートからユーザーの声を伝える前に、エンジニアの方々がすでにその声を把握していて解決策まで考え始めてくれていたりするんです。

一次情報に触れる重要性を理解して、積極的に対応しようと動いてもらえるのは、とても嬉しいです。また、ユーザーから具体的な機能改善の要望があっても、その要望の目的をふまえてよりよい改善策を実現しようと動いてくれるのもありがたいですね。

システムやガイドラインの不完全性をチームで解決する

Izumi: システムには必ず不完全性があると考えていて、それはUXやシステム機能的な不完全さもあれば、一方で要件を満たすシステムを提供していても、ガイドラインの不完全性によってユーザーに届かないこともある。いずれもお客様の不安や不満に繋がってしまうので、開発やカスタマーサポートなど、職種にかかわらず誰かが解決しないといけないと思っています。それをチームや役割を超えて、連携しながら解決していきたいですよね。

Ryo: そうですね。またYumikoが話してくれたように、一次情報をいかに取りに行けるかは長い間考えてきた個人的なテーマでした。そして今は、その姿勢をチームや会社の文化として落とし込みたいと考えています。自分ひとりでユーザーの声をすべて拾い上げることは難しいですが、まわりを巻き込んだり仕組み化したりすることでそれも実現できますよね。

また、Yumikoと対等に「こうしたらプロダクトはもっとよくなるよね」と会話しながらユーザーの抱える悩みの解消に取り組めていることは、自分のなかで希望を感じています。開発とカスタマーサポートを一体化させて仕組みや文化から変えていく動きは、自分の入社当時と比べてもどんどん進んでいますし、これから先がとても楽しみです。

エンジニアとカスタマーサポートの連携で心がけていること

──エンジニアチームとカスタマーサポートチームが信頼し合って連携しているのだなと感じます。普段はどういったことを心がけてコミュニケーションをとっていますか。

Ryo: ビジネスサイドのメンバーのもつ「もっとこうしたい」「こうなったらハッピー」「ここにストレスを感じる」といった課題意識を、開発側がどれだけ解消できるかが鍵だと考えています。私たちは彼ら・彼女らのいちばん近くにいるエンジニアチームなので、定期的なヒアリングを通じてその課題意識をキャッチアップし、開発に反映していますね。ビジネスと開発を棲み分けるのではなく、お互いに協力し合える組織体制になっています。

──Yumikoさんは、開発チームの動きをどのように見ていますか。

Yumiko 本当にありがたいです。Ryoさんのコミュニケーションスタイルは特に、論理と感情のバランスがいいんです。正論を振りかざすこともなく、ビジネスサイドに寄り添ったヒアリングをしてくれます。細かく言わずともビジネス上の重要度を理解したうえで対策を考えてくれるので、とても信頼しています。気持ちのいいコミュニケーションをとれていると感じますね。

Ryo: 一緒に働いているメンバーは、AIとちがって感情をもっています。言葉を受け取った人がどう感じるのか、何かいやな気持ちをもたないかという点は、コミュニケーションするうえで常に気をつけています。そこに注意しないと、ゆくゆくはユーザーへの対応も誤ってしまう可能性があるので。

いちばん大事にしなければいけないのは、信頼ではないでしょうか。それはユーザーからの信頼だけではなく、社内のメンバーからの信頼もそうです。そのためにも、一緒に働きやすいと思える環境をつくり続けるのが大切だと考えています。

ルールを遵守しつつユーザーの期待に応えたい

──カスタマーサポートチームは、最前線でユーザーと向き合うチームです。Yumikoさんはマネージャーとして、そしてチームメンバーとともに、どのような意識で向き合っていますか。

Yumiko UPSIDERの取り組む金融業は規制業種なので、運営にあたってさまざまな社内ルールがあります。そのルールを遵守しつつ、ユーザーの抱える課題に向き合い続けることが大事だと考えています。ただルールで決められたことでも、ユーザーの要望にもとづいて幅広く対応策を検討するといったことは、意識して取り組んでいますね。「ルールに沿うことがすなわち運用ではない」と、チームメンバーにも伝えています。

──カスタマーサポートチームの考えや動きをふまえ、開発チームはどのようにプロダクトを改善していくのでしょうか。

Ryo: 不具合がある場合に直すのは前提として、改善要望についてもしっかり応えていきたいと考えています。そこでは、言われたことにただ対応するのではなく、本質に立ち返ることを常に意識してプロダクトに反映していけたらと。そしてさらに先には、要望をもらう前に潜在的な課題を解決できるようにしたいですね。データの分析を通じて、ユーザーが抱えている悩みや不満を拾い上げ、改善していく。チームとして心がけながら、実践していきたいです。

AIをはじめとするテクノロジーの活用と顧客メリットの創出

──生成AIなどの先進的な技術によって、カスタマーサポートをはじめどのような変化が生まれると考えていますか。

Ryo: チャットボットによるユーザーからの課題のヒアリングなど、すでに多くの企業が導入している技術は、自分たちも当たり前のように採用していきます。運用面についても、システム化を通してどんどん自動化していきたい。一方、AIの得意な領域は頼り、苦手とする領域は人が考えるなど、エンジニア、カスタマーサポート、AIの共存がかなう文化をつくりたいですね。

実際、DevOps的な取り組みを行ったり、開発中にGitHub Copilotを活用してエンジニアの書くコードの品質を担保でき始めているので、これまで気づけなかった不具合も拾い上げて改善することでユーザーからの問い合わせが減り、カスタマーサポートチームの負担も少なくなっていくでしょう。

Yumikoカスタマーサポートには、プロダクトの機能に関する質問がユーザーからたくさん届きます。回答文章の構成や要点整理にAIを活用できれば、よりわかりやすくスピーディに回答できる日がくるのではないでしょうか。業務の効率化によって生まれた時間は、ユーザーの体験向上について考えるために使えますし、人だからこそ解決できる課題に向き合っていきたいですね。

──今後、よりチームを成長させていくために、どんな仲間にUPSIDERに来てほしいですか。

Yumiko 役割や業務が異なっても、同じ目的意識をもってゴールに向けて協力できる人でしょうか。カスタマーサポートチームはユーザーの問い合わせから問題に気づいて、関係各所とやりとりしながら対応をリードしていく必要があります。それにはコミュニケーション能力も重要ですし、自分のチームに限らない全体的な視点で考えながら物事を進められるといいですね。

あとはルールにとらわれ過ぎないという話を先にしましたが、前例のない事象を解決できることを面白いと思える人、カオスな状況で能動的に動ける人にとって、当社はとても楽しめる環境だと思います。

Ryo: UPSIDERカードの開発チーム、特にCXE領域には、信頼を守れる人にいちばん来てほしいです。ユーザーやチームのメンバーに対してリスペクトをもてること、そして嘘をつかないことが大切です。あとは物事を自分ごととして考え、自律的に動ける人ですね。自ら余地を見つけて改善するのがチームの文化になっているので、同じように動けることに喜びを感じていただける方にマッチするのではないでしょうか。

Yumikoがカオスという言葉を使っていましたが、当社はまだ整っていないことも多いです。やることがたくさんあるのですが、その環境を面白い、挑戦しがいがあると思ってくれる人だと、一緒に仕事を楽しめるのではないでしょうか。

今回対談した2名のチームは絶賛採用中です!

herp.careers herp.careers herp.careers

ご興味を持っていただいた場合はぜひカジュアル面談にて詳細をお話しできればと思います!お申し込みお待ちしております。

herp.careers

UPSIDER UPSIDER Company Deckはこちら📣

speakerdeck.com

UPSIDER Engineering Deckはこちら📣

speakerdeck.com

🚧👷👷‍♀️🚧支払い.comで整備中のデザインシステムについて

こんにちは!!

株式会社UPSIDERの「支払い.com」でフロントエンドエンジニアをしていますOkahashi(@akaneburyo)です!

今回は、支払い.comチームで進めているデザインシステムの整備についてご紹介します!

きっかけ

これまで支払い.comチームの開発では、デザイナーにデザインをお願いすることもあれば、フロントエンドエンジニアやPdMがデザインを手掛けることもありました。

これにはデザイナー不在の期間があったことも大きく影響しています。

結果として、次第に以下のような課題が目立つようになってしまいました😢

  • 使い捨てのデザインファイルが乱立している
  • ページごとに微妙に異なる配色やレイアウトになっている箇所があり、統一感が無い

Figma上には100以上のページがあり、ワイヤーフレームや検討段階で作られた使い捨てのものも多く含まれています🔥

(ちなみに、このファイルはカジュアルに利用できるPlaygroundとして残しています)

そんな中、デザイナーのNaoyaさんが参画してくださったのをきっかけに、2024年9月頃から少しずつデザインシステムの整備を始めました。

支払い.comでデザインシステムを作る目的

支払い.comチームでは、主に以下の点を目指してデザインシステムを整備しています。

  • プロダクト全体の統一感を高め、使い心地を向上させる
  • デザイナーとエンジニア双方の実装コストを削減する
  • 実装とデザインファイル双方のメンテナンス性を向上させる

単純な使い心地の向上だけでなく、実装コストの削減とメンテナンス性の向上は将来的にユーザーへ提供できる価値の最大化につながると考えています。

やっていること

UPSIDERには、すでにカード事業で利用しているデザインシステムがあります。

これをベースに、Naoyaさんとフロントチームが主体となって以下を進めています。

(まずは小さく、できることから💪)

コンポーネントの定義

コンポーネントが持つスタイルや振る舞いを実装が可能なレベルに明文化し、Figma上で整理しています。

整理はフロントエンドエンジニアが主体となって行いつつ、デザイナーがレビューを行うことで認識をそろえながら進めます。

再利用されるコンポーネントの整備を丁寧に行い、エンジニアが「よしなに」実装する余地を減らすことで開発の効率化につながると考えています。

将来的にはコンポーネントの外側に発生している制約についても、AutoLayoutやDevModeを駆使して表現したいと考えています💪

デザイントークンの定義

まずは色から整備を進めています。

支払い.comでは、以下の2種類のトークンを定義しています。

  1. Base Token: 特定の要素に紐づかない汎用的なトーク
  2. Alias Token: Base Tokenを継承し、特定の要素に紐づいたトーク

これらは、Figma上でローカルバリアブルとして、それぞれ別々のコレクションに定義しています。

さらに、Base Tokenを直接利用してしまったり、Alias Tokenを意図しない用途で利用してしまうことを防ぐため、可能な限りカラーのスコープも制限しています。

フロントエンドではTailwind CSSを利用しており、トークンはテーマとして表現します。

ここでもFigmaと同様に、用途に応じてスコープを絞って定義します。

const BASE_COLOR_TOKENS = {
  primary: {
    [500]: '#08979C'
  },
  ...{そのほかの色定義}
}

const ALIAS_COLOR_TOKENS = {
    text: {
        primary: {
            dark: tinycolor(BASE_COLOR_TOKENS.primary).darken(7.5).toHexString(),
            base: BASE_COLOR_TOKENS.primary,
            light: tinycolor(BASE_COLOR_TOKENS.primary).lighten(7.5).toHexString(),
        }
    }
    ...{そのほかの色定義}
}

export default {
  theme: {
        colors: {
      // グローバルで利用可能な色は定義しない
        },
        textColor: ALIAS_COLOR_TOKENS.text,
        backgroundColor: ALIAS_COLOR_TOKENS.background,
        borderColor: ALIAS_COLOR_TOKENS.border,
        divideColor: ALIAS_COLOR_TOKENS.border,
        outlineColor: ALIAS_COLOR_TOKENS.border,
    }
    ...{そのほかの設定}
}

また、ここでは:hover :active などの一時的な状態で利用する色として、darklight の色も定義しています。

これらの色はtinycolorを利用してフロントエンドで計算を行っており、デザインファイル上のAlias Token には定義していません。

デザインファイルと実装のそれぞれでメンテナンスコストを最小化しつつ、全体で統一するために簡略化する意図で、実験的に取り入れています。

これらを元に、少しずつフロントエンドへ反映しています。

これから

まだまだ反映しきれていない箇所も多く、日々のプロダクトの開発と並行して段階的にリリースを行なっていきます💪

また、試験的な内容も少し含んでいるため、記載した内容からアップデートする可能性も大いにあります。

最終的な完成形についても、改めてブログでご紹介できればと思います🔥

乞うご期待ください!

事業成長に応えるスケーラビリティの未来:UPSIDERの技術戦略 #UPSIDER_Tech

UPSIDERで2023年8月よりVPoEを務めている泉です。

先日、株式会社UPSIDERはシリーズDとして総額154億円の資金調達を実施いたしました。 調達した資金は、既存事業の拡大と新規事業の開発に投資いたします。

前回の投稿から少し時間が経ってしまっていますが、ちょうど良い節目なので、半期の振り返りと、今後の技術アジェンダについてお伝えしたいと思います。

prtimes.jp

上期のふりかえり

この半年を振り返ると、期初に予想外の大きなインシデントが発生しました。利用者の皆さまにはご心配とご迷惑をおかけしたことを深くお詫び申し上げます。

その後は、再発防止を最優先に、開発プロセスやチェック体制の強化に力を注いできました。 そうした困難な状況にありながらも、事象に真摯に向き合いつつ、「果敢にチャレンジしている姿勢を評価したい」といった温かいお言葉を一部の利用者さまからいただくなど、多くの方々に支えていただきました。そのおかげで、利用社数や決済額の成長も堅調に続いております。

月間アクティブ利用企業数

累計決済額

月間決済数

月間決済数に関しては、2023年12月時点から202年9月時点で約1.4倍に増加しており、ピーク時には日次で約10万件の決済があります。利用企業数もありがたいことに右肩上がりで、それ故にシステム開発に関わる責任もこれまで以上に重くなってきました。

「安定したインフラで、すばやく動く」

十年くらい前ですが、Facebookが「MOVE FAST WITH STABLE INFRA」という技術戦略のスローガンを打ち出しました。

Facebookといえば「MOVE FAST AND BREAK THINGS(破壊してでも良いので素早く動くー著者訳)」のスローガンのほうが有名かと思うのですが、MVPのフェーズを経て社会インフラになって来た時期では、ほぼ真逆のことを言い始めたのです。

hbr.org

これは事業フェーズが変わる中での戦略変更としては、象徴的な出来事だと思っております。

UPSIDERはもともと「金融」という領域で出発したのもあり、その安定性・安全性には十分配慮してきておりますが、ここで再認識すべきは「我々はもうMVPではなく、社会インフラの一部である」という自覚であり、それに向けた適切な「技術投資」が今後必要になると思っております。

昨日、宮城が公開した「人に投資しよう。」のブログにもある通り、今回の資金調達では「AI技術で企業のお金の課題を解決するテクノロジー企業」としての評価をいただけたことは非常にありがたい一方、それを支える「安定したインフラ」という点も無視できない領域だと思っており、改めて、柔軟に動くための「安全・安定」的に使えるサービスにしていくためにコミットしたいと思います。 事業の規模感の変化とともに、システムも変化に対応できるように向き合わないといけない課題の質も変わってきた中、来期に進めるべき技術アジェンダをいくつかのイニシアチブをご紹介します。

モノリシックサービスの再設計

1つめの技術イニシアチブは、モノリシックサービスの解体です。

我々のシステムも5年近く稼働してきており、例外なく技術的負債が蓄積され始めています。特に、初期の設計では想定していなかったスケールや複雑な要件に直面することが多くなってきており開発生産性が課題になってきつつあります。

現在カード事業のサービスには「審査」「与信」「カード発行」「決済処理」「請求回収」といったコアな機能があります。 この内の「決済処理」に関しては、初期からの設計上、かなりモジュラーな形で実装されているのですが、それ以外の機能に関してはモノリシックなシステムで実装されておりました。今後要件がより複雑化したり、個社毎のこれまで機能を分離して開発ができるようにモジュール化する必要があります。

利用目的に応じて与信の組み方や、支払い方法が変則的になるなど、すでに様々なビジネスニーズがあるため、それぞれの機能群が独立的に進化できるようにしていくことで、敏速・安全にリリースできる環境を作っていく必要があります。

現在は、Reachitectureのチームを組成し、既存のオペレーション負荷や、事業上のインパクトで優先順位を考慮し、まずは請求回収のコンポーネントのシステム分離を行い、その上で独自のペースで進化できるようにモジュール化を行う予定です。

まずはもとのコードベースの複製をつくり、そのドメインに不要なエンドポイントやレポジトリを削った新しいコンポーネントを作成します。 この際、コードベースの負債は引き継がれてしまいますが、少なくとも新しい機能を作ろうとしたときに、既存のモノリスに足さなくて済み、新しい機能を下位のパスで切り出して別の技術スタックを利用することも可能です。

組織的にも2〜3人と少人数のチームが生まれ、コミュニケーションがしやすいようになり、かつリリースサイクルもプロジェクトがかなり小さい単位になったことで継続リリースに切り替えることができます。これにより、各機能が独立して進化でき、迅速かつ安全なリリースを実現することができます。

この手法は、すでに我々の「認証基盤」の分離でも実証されており、もともとモノリシックサービスに内包された認証の機能を、外部に分離し、2〜3名のチームを組成して技術負債の解消(リプレイス)や、二要素認証のバグの解消、SAMLSCIMのサポート等、他サービスへの影響を配慮をせずとも推進できました。

決済基盤のスケーラビリティ

私たちの決済基盤は、以前のブログでも紹介した通り、Spanner/Go/k8sを活用したアーキテクチャのおかげで、100倍、さらには1000倍のトラフィック増加にも対応してきました。

しかし、年末や年度末などの季節性により、決済数が急激に増加するタイミングが年に何回かあり、決済に遅延が生じるリスクが発生することもあります。

こうした課題に対処するために、私たちはさらなる最適化を進め、トラフィックが急増する見込みのある際には、水平スケーリングの上限を上げて負荷に応じてリソースを迅速に割り当て遅延を最小限に抑えたり、トランザクションのロックの単位を調整することでデータベースのボトルネックが発生しないように工夫しております。

これからも、私たちの決済基盤は、急激なトラフィックの増加に柔軟に対応できるよう、スケーラビリティの向上に取り組んでいきます。そして、利用者の皆さまに安心してサービスをご利用いただける環境を提供するために、技術投資とシステムの強化を継続していきます。

Internal Tooling開発

もう一つ、これまでになかった領域で今後強化していきたい技術投資の領域が「Internal Tooling」です。 どうしても社外向けの開発に目が行きがちですが、大規模な金融システムでは、内部オペレーションの盤石さが今後の事業成長の鍵を握ります。特に効率的でスケーラブルな内部ツールが、日々の業務の円滑化に直結し、全体の生産性を大幅に向上させる可能性があります。

例えば、カード事業では、新規利用者の審査やクレジットカードの与信を行うオペレーションチームが存在し、これらのチームが生産性高く、少数のメンバーでも数千社の企業をサポートできるよう、オペレーション面でもスケーラビリティを確保してきました。 これにより、少人数でも効率的に複雑な金融オペレーションを処理できるよう、内部ツールの強化が欠かせません。

これまで、この領域の開発は1〜2名という非常に少数のエンジニアで対応してきましたが、今後はさらに専任チームを編成し、ツールの開発と改善に注力していく予定です。 これにより、データの可視化、プロセスの自動化、エラー検出などを積極的に導入し、オペレーションチームがより迅速かつ正確に対応できる環境を構築し、事業全体の成長を支えていきます。

最後に

今回は、下期に向けたいくつかの技術イニシアチブについて共有しましたが、これ以外にもeKYC(本人確認)、不正利用対策、セキュリティ対策など、取り組むべき重要な技術領域が数多く存在します。私たちは引き続き、これらの課題に真摯に向き合い、金融システムの信頼性と利便性を向上させるべく、技術革新を推進していきます。

これらの取り組みを成功させるためには、技術面だけでなく組織としての強化も重要です。もし私たちのチャレンジやビジョンに共感いただける方がいらっしゃれば、ぜひ採用ページをご覧いただければ幸いです。新たな仲間と共に、より良い未来を築いていけることを楽しみにしています!

今後、それぞれの領域でのより詳細な開発内容についてもご紹介してまいりますので、楽しみにしていてください!今後も皆さまの期待に応えるべく、チーム一丸となってより良いサービスを提供し続けてまいります。

We are Hiring!

UPSIDERに興味を持ってくださった方のご応募を心よりお待ちしております!

herp.careers

複数ポジションがオープンしております。 迷われる方はぜひ、カジュアル面談でまずは相談しましょう!

herp.careers

AI時代のプロダクトアウト/マーケットインの捉え方|Product DeepLive イベントレポート

こんにちは!UPSIDERでHRをしていますNarisaです。 当社VPoP森(@diceK66)が『Product DeepLive』に登壇しましたので、イベントレポートをまとめます!

Podcastをすぐ視聴されたい方は、以下よりご視聴ください!

product-deepdive.connpass.com

Product DeepLiveとは?

Product DeepLiveとは、プロダクトマネージャー向けPodcast『Product DeepDive』の公開収録イベントです。Product DeepDiveはプロダクトマネジメントに関する深い洞察を提供するPodcast番組で、毎週水曜日の朝に配信されています。プロダクトマネージャーの蜂須賀さん(@PassionateHachi)とプロダクトコーチの横道さん(@ykmc09)のお二人がパーソナリティを務め、プロダクトマネジメントに関わる様々なテーマを掘り下げて”探求”されています。 今回のProduct DeepLiveでは、公開収録の視聴と参加者同士の交流がありました。 来場者特典として登壇者のカードをもらえて、中にはキラカードが用意されていたようです。

「AI時代のプロダクトアウト/マーケットインの捉え方」をテーマに収録

アマゾン ウェブ サービス ジャパン合同会社AWS)様から会場および懇親会時の飲食をご提供いただきました!開放的なスペースでとても素敵な会場でした。

今回のイベント収録では、AI時代のプロダクトアウト/マーケットインの捉え方をテーマにお話ししました。

収録内容では、以下のようなテーマに触れました。

  • AIがプロダクトアウトとマーケットインにどう影響するのか
  • AIによるインターフェースと体験の変化
  • AIの活用における現実的なアプローチ、リアルな課題と解決方法
  • AIプロダクトの開発と顧客対応の課題

詳細はぜひPodcastをご視聴ください!

▼前編

open.spotify.com

▼後編

open.spotify.com

収録の合間のQ&Aで生まれる盛り上がりはオフラインイベントの見どころの一つ。

収録は前後半に分けて実施され、その合間にはQ&Aタイムが設けられました。 Q&Aの内容は、残念ながら、完全なオフレコになるのですが、質問者様の日々の業務における課題をもとにした質問をたくさんいただきました。

質問者様には本のプレゼントというサプライズも

公開収録部分だと具体的にお話しすることが難しかったご質問にも、微妙なニュアンスを補足しながら回答することができ、オフライン会場ならではのコンテンツの一つになったのではないでしょうか。

Podcastをご視聴いただき、ご興味をもっていただきました方はぜひ株式会社UPSIDERのカジュアル面談にぜひお越しください!

herp.careers

UPSIDER Engineering Deckはこちら📣

speakerdeck.com

リリース2年半で累計決済額 700億円を突破した「支払い.com」を支える開発手法、開発チーム体制について

株式会社UPSIDERの「支払い.com」でエンジニアリングマネージャーをしている大聖寺谷です。

支払い.comとは請求書の支払いをクレジットカードで支払うことができ、 中小企業や個人事業主の資金繰りの改善を行うことができるサービスです。

リリースから今まで右肩上がりに成長を続けており、最近では累計決済額が700億円を突破しました。

prtimes.jp

今回のブログでは、支払い.comの成長を支え続ける、開発チームの体制や開発手法について紹介します。

支払い.comの開発チームと開発スタイル

支払い.comの開発チームは全体で10名以下のスモールな開発組織です。

メンバーの居住地はバラバラであり、フルリモートやフルフレックスを中心とした働き方になっています。

そのため開発手法やMTGなどについてもフルリモートかつ時間的に非同期であることを念頭において設計を行っています。

スクラムではなくカンバンで開発する

支払い.comではカンバンのような開発手法を採用しています。エンジニアの手が空いたタイミングでNotionで管理しているプロダクトバックログから優先度の高いタスクをアサインして開発を進めています。

多くの企業で採用されているアジャイル開発のスクラムですが、支払い.comの開発チームでは採用していません。理由としては、総合的に見て現在の支払い.comの開発チーム規模感やフェーズでは適さないと判断しているためです。

一方で、今後さらに人数が増えていった際やスクラム経験が豊富なメンバーがジョインした場合などは、改めて開発方式を検討したうえで、より最適なものを探したいと思っています。

プロダクトバックログ管理はNotionで行う

UPSIDERでは開発チームに限らず全社的にNotionを活用しているため、タスク管理ツールについてもNotionのDBを使っています。

以前はGitHub Projectsで管理していましたが、エンジニア以外 (PdMや事業責任者など)のメンバーがバックログを確認する機会も増えており、Notionに移行して良かったと感じています。

プロダクトバックログは、主に1つのEPIC (例: xxxの機能を開発する ) に複数の具体的なTASK(例: xxxの画面を実装する, xxxのAPIを実装する)が紐づくような形で管理しています。

プロダクトバックログはNotionのProject機能を使っており、EPICがProjectに該当します。

下記はプロダクトバックログの一部となります。

Epicのアイコンは絵文字を活用しており、遊び心も大事にしています

Epicのアイコンは絵文字を活用しており、遊び心も大事にしています

どんなタスクでどのチームが対応する必要があるかが分かるようにタグづけして管理しています。

実装に際しての要件や設計検討したものなどは各EPICに記載するようにしており、タスクアサインされたメンバーがすぐに開発に取り掛かれるようになっています。

下記は実際に今対応中のとあるEPICのキャプチャになります (見せられない箇所が多いのですが 😭)

フォーマットはEPICによってバラバラだったりしますが、課題背景や想定しているユーザーストーリー、想定している対応内容などを記載しています。

タスク管理ツールでありがちなものとして、タスクステータスの更新漏れなどがありますが、NotionとGitHubを連携しており、GitHub PRの状況に応じてタスクステータスが自動で 対応中レビュー中リリース待ちリリース完了 と遷移するようになっています。

こういった地道な開発運用改善などはメンバーが積極的に行なってくれています。 (いつもありがとうございます!)

また「今すぐにはやらないけど、いつかはやりたい!」「アイデアベースだけどこんなことやれたら良いかも」といったものは プロダクトバックログとは別の要望DBで管理しています。

要望DBについては開発チーム以外のメンバーもチケット追加が自由に可能なルールとなっており、カスタマーサポートやセールスメンバーを中心に、それぞれ自由に追加しています。

フルリモートを念頭においたコミュニケーションカルチャー

支払い.comの開発チームでは、フルリモート・フルフレックスの働き方を最大限活かしつつ、開発者が集中して作業できる時間を確保するために、定期的な同期ミーティングの機会を少なくしています。

しかし、同期コミュニケーションを完全に排除するわけではなく、週に3回の朝会や週1回の振り返りを行っています。

朝会では、各メンバーが現在の状況やタスクの進捗を共有する場としていますが、それだけではなく、「ひとこと」という欄を設けています。この「ひとこと」では、各自が最近の出来事や趣味(例えば、漫画やゲーム、スポーツなど)について自由に話す場としており、プライベートな雑談を織り交ぜながら運営しています。これにより、各メンバーが自己開示を少しずつできるようにしており、より円滑なコミュニケーションを促進する工夫しています。

さらに、チーム内では、SlackのHuddle機能を使って気軽に質問をしたり、一緒に作業したりするカルチャーも根付いています。

Huddleは、必要に応じてすぐに短時間のミーティングやペア作業ができ、オフィスで少し離れた席のメンバーに相談しにいくような気軽さで話せるのがとても便利だなと感じています。

こういった同期・非同期のハイブリッドなコミュニケーションを行うことで、フルリモートであっても高い生産性を実現できていると感じています。

開発チームとビジネスチームがフラットにそして強固に連携するカルチャーがある

私たちのプロダクトは開発して終わりではなく、ビジネス戦略を考えたり、ユーザーのサポートなどが必要不可欠です。

そこで支払い.comでは、開発チーム以外にも主に以下のようなチームが存在しています。

  • セールスチーム
  • カスタマーサポートチーム

チームが違っていても、Slackで誰とでもいつでも気軽にコミュニケーションがとれるようになっており、とても距離感が近く、職種に限らず「全員がユーザー志向を持ち、一丸となってプロダクトを磨き続ける」ということができているなと強く実感しています。

これについては以下のインタビュー記事で詳しく話しているので、ご興味をお持ちいただいた方はぜひ以下のnoteコンテンツにつきましても、お読みいただけると嬉しいです!

note.com

おわりに

本ブログでは現在の支払い.comの開発手法や体制を中心に紹介させていただきました。

一方で正直なところをお話しすると、まだまだ実現したくてもできていないことがたくさんあります。

このブログだけで全てを伝えることは難しく、もし些細なことでも気になることなどがありましたらお気軽にご連絡くださいませ。

herp.careers

herp.careers

課題は上流工程でのQA活動とテスト自動化|AI化された総合金融機関を支える品質保証とは

株式会社UPSIDERにてQAチームのマネージャーをしているNaokiです。昨年末にUPSIDERで就業をはじめQAチームの立ち上げをしています。 これから成長するUPSIDERを支えるQAチームを拡大したいと思い、絶賛採用中なのですがこれまで社外にQAチームの紹介をしたことがなかったので、筆をとりました。 私たちの取り組みを紹介します!

2024年2月に1人目が入社

今回の記事の登場人物

Naoki)SESのテスト会社に入社したところからQAエンジニアとしてのキャリアを歩み始めテスト実施やテスト設計、テスト計画や管理などQA業務に必要な作業をキャッチアップした後にフリーランスのQAエンジニアとして独立。fintech企業やECサイトでのQA経験を重ね2023年10月に業務委託としてUPSIDERにジョイン。2024年2月に社員転換。

Eri)新卒でSierにソフトウェアエンジニアとして入社。CG、医療機器の開発を行う。1度半導体業界へ転職。再びIT業界に戻ってくるのをきっかけにQAエンジニアを始める。第三者検証の会社で経験をつみ、事業会社へ転職。2024年6月からUPSIDERへジョイン。

Fukushima)ゲームの第三者検証の会社で、テスターとしてQAエンジニアのキャリアをスタートする。出向先の会社にてテスト計画、設計、実施やリソース管理などの業務を学ぶ。そこから飲食サイトの会社へ転職しさらにQAエンジニアのキャリアを積む。2024年3月からUPSIDERへジョイン。

ーーみなさんはなぜUPSIDERに入社しましたか?

Naoki) 最初は業務委託で入社しました。UPSDIERは雇用形態による差がほとんどなく、裁量の大きさに驚きました。より責任のある立場で会社とプロダクトにコミットしたいと思い、正社員になりました。もともとFintech企業はどこもお金というユーザーへの影響が大きいプロダクトをもつ性質からか、業界的にプロ意識が強い方が多い印象で、そのような環境が自分自身にあっていると感じ入社を決めました。

Eri) もともとソフトウェアエンジニア出身なのですがキャリアに悩んでいたところ偶然テストポジションに出会ったことから、QAエンジニアになりました。第三者検証という立場で働くなかで事業会社へ興味をもつようになり、UPSIDERはメンバーの人柄の良さ・裁量の大きさ、また英語環境という点に惹かれて入社しました。

Fukushima)私はNaokiさんに誘われたことがきっかけです。UPSIDERの急成長している環境に興味をもち入社を決めました。

品質保証だけではなくより良い顧客体験をつくりたい

ーー現在のQAチームのミッションを教えてください。

Naoki)品質保証チームは顧客満足のバックボーンにあたると考え、品質保証を超えて、“保証”だけではなくよりよい顧客体験を提供することを目指しています。直近では、セキュリティやコンプライアンスの基準を満たしつつ、アジャイルに開発を進めるため柔軟かつ効率的なQAプロセスの構築を目指しています。特に、リグレッションテストの自動化とスクラムへの早期関与がチームの強化ポイントです。 2024年8月ごろからはチームメンバーも増えたので、一定のSLOレベルのインシデント発生件数を品質保証水準の目安として掲げ、定量的な改善活動に取り組んでいます。

Naoki) 自社製品の品質を向上することは、業界全体の製品品質向上や、最終的に私たちの身の回りの製品向上につながると考えています。現在のQAチームは5名体制ですが、今後は10名規模のチームに成長させ、開発チームとの連携をさらに強化する予定です。

人数が増えたらより潜在的な不具合にもアプローチしたいと考えていて、バグバッシュやモンキーテスト、アドホックテストなど手法から新メンバーと一緒に考えたいです。

QAチームに限らず開発チーム全体で品質意識をもつ

ーー金融業界としてQAに求められる品質保証活動にはどのような特徴がありますか?

Naoki)やはりユーザーの”お金”に直接影響してしまう領域なので、限度額の設定や決済など特にお金の流れに関わる部分に関してはQAに限らず開発チーム全体で品質意識を持ってシビアに判断するようにしています。 開発チーム全体の意識の高さからか、QAに対する期待は高く、各QAメンバーに少しでも不安や懸念が生じたり、まずいと思ったときにはQA側の判断でリリースを止められるような裁量も持っています。

Eri)”サービスを止めない”はかなり意識をしています。当たり前ではありますが、気になる動作があった場合は懸念点が解消できるまで確認します。

Fukushima) ユーザーの事業活動にも影響しますし、ひいては自社の売上にも影響するので一定の緊張感はあります。

ーー業務の進め方には特徴はありますか?

特に明確な特徴はまだないかもしれません。 QAプロセス的には開発のスクラムチームに帯同できるところは帯同して上流工程から一緒に開発を進めています。 各機能テストやリグレッションテストは定常的なスケジュールをひきつつ、ある程度状況を見ながら臨機応変に対応する方針となっています。

今のプロセスが完璧かというと必ずしもそうではないところもあるので、開発やPdMを巻き込んで随時ブラッシュアップしていきたいですね。

エンジニアとともにテストケースを議論

ーーFukushimaさんは現在開発チームにて上流フェーズからQAとして開発に入っているとのことですが、開発フローとそのなかでのQAの具体的な業務内容を教えてください。

Fukushima) UPSIDERのQAチームでは、スクラムチームに帯同し、上流工程から品質保証の活動に携わっています。現在、開発チームでは2週間のスプリントを採用しており、QAはエンジニアと密接に連携して開発に取り組んでいます。 具体的な業務として、QAはリファインメントやプランニングに参加し、リリースに向けた仕様の確認や調整を行います。テストケースの作成はエンジニアが主に担当します。QAはそのレビューを行い、品質の観点からフィードバックを提供しています。 基本的にエンジニアによって手動テストも行ってもらっていますが、QAもGoogle Meetなどで話しながらエンジニアとテストを実施することで、テスト精度を高める役割を担っています。またレトロスペクティブや仕様の勉強会に参加し製品理解を深めています。 開発プロセスに入り込むことで、テストケースに対する気づきはたくさん得られます。「ここまでテストすべきでは?」「こういう理由があるので、ここのテストは不要」など開発チーム全体で議論ができています。

仕様理解に努めつつ、曖昧な場合は直接のコミュニケーション

ーー金融業界として求められるセキュリティ基準の高さと、アジャイルに開発をする柔軟性が求められる環境だと思いますが、QA業務を進めるなかでの難しさ・工夫を教えてください。

Fukushima) 開発チームにおけるQA業務では、金融業界特有のセキュリティ基準に対応するため、特に請求や仕訳といった複雑な機能の理解が求められます。これらの仕様をしっかりと把握し、インシデントを防ぐための事前の確認作業は非常に重要です。

アジャイル開発プロセスの中で柔軟に対応する必要がありますが、リリース期日が厳しく固定されていないため、リリースのタイミングを調整しながら、品質水準を担保しています。

開発チームとのコミュニケーションでは、正確な状況報告と迅速な対応を心がけています。また、必要に応じて直接話し合うことで、認識のズレを防ぐよう工夫しています。話が長くなりそうな場合や、伝えるのが難しい内容、または自分が理解できていない場合は、直接対話するようにしています。 あとはフランクなメンバーが多いので必要以上にかしこまらないようにしていますね。

アウトプット練習の場、ライトトークセッションなど横のつながりも

ーーQAチームメンバー同士はコミュニケーションをとりますか?

Naoki) QAチーム内ではフランクなコミュニケーションを大切にし、ウィークリーで定例を設けて取り組みや進捗の報告をしています。 また2週間に1回は振り返り会や、ライトトークセッションを設けています。本を読んだ、こういう体験をした、などを共有する時間として活用しています。 社内外での登壇練習にもなるので、フランクにアウトプットできる場を設けています。 またUPSIDERは英語が飛び交う環境でもあるので、チーム内の英語力向上の取り組みの一環として日報を英語で書く、といったことも行なっています。

テストカバレッジの向上と安定したリリースフローの確立を目指す

ーーEriさんは現在自動テストの推進をしているとのことですが、具体的な業務内容を教えてください。

Eri) 現在はテストツールを活用しながらリグレッションテストの自動化を進めています。 MagicPodはすでに運用されており、毎日テストが実行される一方、変更や不安定な部分が発生した場合にはメンテナンスを実施しています。

さらに、テストの実行速度や今後の拡張性を考慮し、Playwrightの導入をしました。Playwrightを利用することで、UIベースの確認に加えてコードベースのテストも可能になり、より効率的なテスト環境を構築しています。自動化項目が増え続ける中、開発メンバーもPlaywrightを積極的に活用しており、全社的に自動化の取り組みをサポートする動きが進んでいます。

ーーなぜPlaywrightを選んだのでしょう?

まずMagicPodやPlaywrightの導入をしていますが、手探りの状態というのが正直なところなので、より高い専門性をお持ちの方が入社したら、あるべき姿から一緒に議論したいですね。 前段の要素に加え、料金体系が影響しており、自動テストを回しやすい心理状態をつくりたかった、というのもあります。VPoEのIzumiの考え方としてコストをかけて品質が向上するなら投資をすべきだと考えているので、議論しながら最適な環境を作りたいと思っています。

ーーテストの自動化における課題があれば教えてください。

現状の環境下での課題としては、リグレッションテストの項目に優先順位をつけ、どの部分から自動化を進めるかを慎重に判断しています。全てのテストを自動化できるわけではないため、特に決済機能などのデータの準備が難しい部分は手動でのテストが依然として必要です。

また、自動化に関する知見や技術がまだ限られているため、特にPlaywrightの運用には経験が必要であり、チーム内のスキル向上も今後の課題です。自動化を進めることで、リグレッションテストにかかる時間を短縮し、新機能のテストにリソースを割くことができる理想的な体制を目指しています。今後は、ツールへのさらなる投資と技術のキャッチアップにより、テストカバレッジの向上と安定したリリースフローを確立していく計画です。

真面目な話と雑談がある環境ー緩急をつけて働きたい方へ

ーーどんな方に入社してほしいですか?

UPSIDERのQAチームでは、直近で入社する方に対して、いくつかの重要なミッションをお任せする予定です。

  • スクラムチームに帯同し、上流工程からの品質保証活動を行うこと
  • プロダクト全体に対するリグレッションテストの実施
  • ツールを活用したテスト自動化の推進

など、これらを通じて開発スピードと品質の両立を目指しており、開発チームの全てに専任のQAを配置する体制の実現が目標です。自走していける環境で裁量を持って働きたい方に、チームの品質保証活動をリードしていただきたいです。

  • 仕事の話と雑談、業務と学習(社内勉強会)など、緩急をつけて仕事したい方
  • 裁量をもって働きたい方
  • 改善や変更に対して柔軟に対応できる方

はUPSIDERにマッチしていると思います。

メンバーのバックグラウンドも多様なので、これまでの経歴や国籍に偏見がないメンバーが揃っている点もアピールポイントかもしれません。 ご興味をもっていただけた方はぜひカジュアル面談へのご応募をお待ちしています!

herp.careers

UPSIDER Engineering Deckはこちら📣

speakerdeck.com

メンバーの個人インタビューもぜひご覧ください!

note.com

note.com

ZodとOpenAPI Generatorで日付文字列をBrand型で扱う

UPSIDERでエンジニアをしている太田です。 (@Hide55832241)

この記事ではZodとOpenAPI Generatorを使用し、日付文字列をBrand型で扱う方法について紹介します。

TypeScriptを使用したフロントエンドのアプリケーション開発で日付をstring型で扱う際に困ることがあります。
日付をstring型として扱う際、APIから返却された未フォーマットの文字列と、UI用にフォーマットされた文字列を区別することが難しく、誤った表示や変換処理が発生しがちです。
またAPIリクエストでは正しいフォーマットで送信されているかどうかを保証できないため、不具合の原因になることがあります。

たとえばUIで表示する日付文字列が yyyy年MM月dd日 であった場合、この文字列をさらにフォーマットしようとすると想定外のエラーとなります。

// date-fnsのformatを想定
// APIレスポンスの日付文字列を想定
format(new Date('2024-01-20'), 'yyyy年MM月dd日') // Success

// 変換後の文字列を誤って指定
format(new Date('2024年01月20日'), 'yyyy年MM月dd日') // Error: Invalid Date

私たちが開発している 支払い.com でもこれらの課題を抱えており、Brand型を導入しました。
このプロダクトではOpenAPI Generatorを使用してコードを生成しています。そのため自動生成されるコードにBrand型をマッピングする必要がありました。
またFormやURLパラメータの検証にはZodを使用しています。Brand型をZodと組み合わせることでより扱いやすくなりました。

今回の記事では 支払い.com でBrand型を導入した知見を共有します。
大きくライブラリに依存した内容になりますが参考になれば幸いです。

設計

APIレスポンスの日付文字列を表示

// 表示コンポーネントではDate型で受け取ることで、フォーマットが必須であることを明確にする
const DateField = ({ value }: { value: Date }) => {
  return <p>{format(value, 'yyyy年MM月dd日')}</p> // formatはdate-fns想定
}

type ApiDateString = string & { __brand: 'ApiDateString' }

type Order = {
  // APIレスポンスの日付はBrand型で定義する
  shipmentDate: ApiDateString
}

const ShipmentDate = ({ shipmentDate }: { shipmentDate: Order['shipmentDate'] }) => {
  return <DateField value={new Date(shipmentDate)} />
}

ユーザーによる日付入力の値をAPIリクエストパラメータとして使用する

※ 以下の例ではuseStateを使用したサンプルですが、支払い.comではReact Hook Formを使用しています

const InputDate = (props: {
  value: string,
  onChange: (v: string) => void
}) => { /* 実装は省略 */ }

type ApiDateString = string & { __brand: 'ApiDateString' }

// APIリクエストパラメータの日付はBrand型で定義する
type PostParams = { shipmentDate: ApiDateString }

const postDate = (args: PostParams) => { /* 実装は省略 */}

const Form = () => {
  const [formData, setFormData] = useState<{ shipmentDate: string }>({ shipmentDate: '' })

  const handleSubmit = () => {
    // バリデーション
    if (!formData.shipmentDate) {
      return
    }
    postDate({
      ...formData,
      date: format(formData.shipmentDate, 'yyyy-MM-dd') as ApiDateString
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <InputDate
        value={formData.shipmentDate}
        onChange={
          (v) => setFormData(
            prev => ({ ...prev, shipmentDate: v })
          )
        }
      />
    </form>
  )
}

これらの設計のとおりBrand型を使用することで安全に日付文字列を扱えるようになりました。
実際にプロジェクトの導入する例を紹介します。

Zodを使用する

Brand型のZodスキーマを定義し、スキーマから型を得る

const API_DATE_STRING = z.string().brand('ApiDateString')
type ApiDateString = z.infer<typeof API_DATE_STRING> // string & z.BRAND<"ApiDateString">

// あとはZodを使用しない場合と同様に使用できる
type Order = {
  shipmentDate: ApiDateString
}

type PostParams = { date: ApiDateString }

Zodスキーマから得た型を使用しBrand型が定義できることはわかりましたが、まだ以下の問題があります。

  • ユーザー入力値の検証の処理を曖昧にしている
  • APIリクエストのパラメータを渡す際に、日付文字列の項目の詰替えが必要になっている
  • asを使用しBrand型にキャストしている

これらについてZodを使用し解決していきます。

ユーザー入力値の検証、型と値の変換

const FORM_SCHEMA = z.object({
  date:
    z
      .string() // 未入力状態など考慮し、入力はstringで受け付ける必要がある
      .refine(v => /* 文字列が日付として正しいか検証 */)
      .transform(v => format(v, 'yyyy-MM-dd')) // APIで扱う日付フォーマットに変換
      .pipe<API_DATE_SCHEMA> // 検証が完了し、フォーマットされた日付文字列はApiDateStringとして扱う
})

定義したスキーマを使用して検証し、後続処理で検証後の値を使用する。

  const handleSubmit = () => {
    // バリデーション
    const res = FORM_SCHEMA.safeParse(formData)
    if (!res.success) {
      return
    }
    postDate(res.data) // 検証後の日付文字列はApiDateString型となっているため、詰め替えが不要になった
  }

asの代わりにpipeを使うようにしただけではないかと言われるとそのとおりだと思いますが、検証、型と値の変換の定義を一箇所で行うことができ、シンプルで扱いやすくなったと感じていますがどうでしょうか?

transformでApiDateStringのフォーマットに変換すべきか

むずかしいところ
多くのユースケースでsubmitした値を使用し、APIリクエスト、その結果を使用してなんらかの後続処理を行うことを考えると有りだと思う。
しかしsubmitした値をQuery文字列へpushし、Query文字列から取得した値を使用しAPIリクエストを行うなど必ずしもApiDateStringへ変換すべきではないと思う。

FORM_SCHEMAから得られる型

検証前の値はstring型、検証後の値はApiDateString型として扱うことができる。
検証前は任意の値 (デフォルト値は空文字) を扱う必要があるのでstring型であることが望ましい。

  type Input = z.input<typeof FORM_SCHEMA> // string
  type Output = z.output<typeof FORM_SCHEMA> // string & z.BRAND<"ApiDateString">

OpenAPI Generatorで日付文字列をBrand型にマッピングする

APIスキーマからAPIのリクエストとレスポンスの型を自動生成することがあります。
OpenAPI Generatorを使用し、日付文字列をBrand型にマッピングする方法について説明します。

設定ファイル

typeMappings:
  date: ApiDateString

APIスキーマファイル

Order:
  type: object
  required:
    - shipmentDate
  properties:
    shipmentDate:
      type: string
      format: date

これで以下の型を生成できるようになります。

type Order = {
  shipmentDate: ApiDateString
}

ただし生成したファイルからApiDateStringをimportしていないため、エラーとなってしまいます。 生成したコードに追記するなどなんらかの方法でApiDateStringをimportするか、globalに定義することで解決できます。

今回はglobalに定義する方法を紹介します。

global.d.ts

// @/lib/zod/brandにApiDateStringを定義したものとする
declare type ApiDateString = import('@/lib/zod/brand').ApiDateString

また日時は以下の設定でコード生成できます。

設定ファイル

typeMappings:
  DateTime: UnixtimeNumber

APIスキーマファイル

Order:
  type: object
  required:
    - createdAt
  properties:
    createdAt:
      type: string
      format: date-time

生成されるコード

type Order = {
  createdAt: UnixtimeNumber
}

これでOpenAPI Generatorを使用したプロジェクトで日付文字列をBrand型で扱うことができるようになりました。

あとがき

日付の扱いは一見単純に思えても、プロジェクトが大きくなるにつれて複雑さが増していく部分です。
特にAPIとのやり取りやUIでのフォーマットなど、少しのミスで意図しない不具合を引き起こすことも少なくありません。
今回紹介したBrand型やZodやOpenAPI Generatorのような素晴らしいライブラリに助けられました。

もしこの記事が少しでも参考になれば幸いです。
フィードバックがあればお待ちしております。