UPSIDER Tech Blog

4年続けてきた本番運用を(ほぼ)完全にバトンタッチした話

みなさんこんにちは、UPSIDERでエンジニアをしています清水(通称シミケン)です。

僕はUPSIDERの創業期から関わらせてもらっており、Webシステムの設計・開発をメインでやってきたこともあって最初にプロダクトをローンチした2019年12月以来、ずっと本番運用にも関わってきました。

もちろん、フェーズによって関わり方は徐々に変わってきていたのですが、この度ようやく本番運用に関する業務を(ほぼ)すべて引き継ぎ、他のメンバーに運用をお任せできるようになったため、これまでのプロダクトやチームの変遷も踏まえて自分がどんなことをしてきたのかを振り返ってみたいと思います。

1年目(プロダクトローンチ最初期)

今でこそ非常に多くのお客様から法人カードをご利用いただいていますが、プロダクトローンチ当初、法人カードはまだ提供できておらず、振込処理の自動化機能の提供を行っていました。

(そのあたりの流れは以前執筆させていただいたこちらの記事に詳しく記載しています)

ユーザー様がアップロードされた請求書をデータ化して振込まで一気通貫で行うサービス(現在このサービスは終了しており、別プロダクトとしてローンチした支払い.comに引き継がれています)だったのですが、最初は共同代表の宮城や水野を含む社内メンバー総出で手動でデータの入力を行っていました。

宮城もデータ入力してました

月次バッチの運用を自動化しようとして間に合わなかった模様

こんな感じで、ユーザー様へ新しい価値を届ける機能開発に追われつつちょっとずつ運用効率を改善していったり、まだ存在していない運用上必要な機能をなんとかギリギリで実装したり、色々と泥臭く運用を繋いでいた記憶があります。

ちなみに、当時Webシステムのサーバーサイドの社員は僕一人だったこともあり、システム側の運用はほぼワンオペで回していました。

2年目(法人カードローンチ期)

ついに念願の法人カードをローンチでき、運用タスクもカードに関するものが徐々に増えていきました。

この頃には請求書のデータ化も外部に委託する体制が整っていたため、ようやく社内総出でデータ入力する作業からは解放されたのですが、それでもデータの最終チェック・登録作業は社内で実施していたためまだそのあたりのタスクが残っており、結局ユーザー数の急増&カードに関する運用タスクの増加と相まってあまり業務量としては減っていなかった(というか増えた?)ように感じます。

毎年恒例、あけおめバッチ処理もやってました。

年始に限らず、毎月初に実施するバッチ処理は僕らのサービスの価値を支える一つの要素になっており、

  • 前月の利用実績に基づいて付与されるポイントを、確定できるようになった月初のタイミングで即座に付与し、そのまま翌月の請求金額から差し引けるようにする
  • 毎月の請求額をできるだけ早くユーザーにお伝えする
  • 月が変わったことで初めて連携が可能になる情報を即座に会計システムに連携することで、月次決算の早期締めに貢献する

などの価値を実現するため、月が変わったその夜のうちに処理を実行して、朝までには必要な処理を終えておく必要があります。

完全に自動化できればよかったんですが、上記のとおり朝には処理が終了している必要があるため、不測の事態に備える意味もあって結局今にいたるまで月初処理は必ず人が張り付いて対応しています。

ちなみに、当時Webシステムのサーバーサイドの社員は僕一人だったこともあり、運用はほぼワンオペで回していました。(2年連続2回目)

3年目(運用チーム組成期)

プロダクトローンチから3年目に入って、今まで提供してきた振込処理の自動化機能を終了することが決まり、ここに割かれていた運用リソースがごっそり不要になりました。

そして時を同じくして、ついについに念願のサーバーサイドの正社員メンバーが増え、運用チームが組成されました。

これらによって本格的に本番運用をチームで回していく体制が整っていき、僕自身が運用に割くコストは大幅に減りました。ようやく運用時にバイネームでメンションされまくる時代にさよならすることができたのです。本当に一緒に運用をやっていくことを快諾してくれたメンバーには頭が上がりません。

一方で、法人カードのリリースから一年が過ぎ、本格的に認知度があがってきてユーザー数が一気に増えるようになったのもこの時期です。システムの負荷が上がることによる問題が出始めるだけでなく、それまでは想定していなかったイレギュラーな使い方をされることも増えてきたため、そうしたイレギュラーな利用方法にも対応できるような修正が必要になってきました。

ただ、ここでも運用チームを組成した恩恵があり、徐々に運用改善にコストもかけられるようになりました。上記のようなイレギュラーケースへの対応ができたり、これまではTech側に依頼をしてもらう必要のあったことがビジネスサイドだけで完結できるようになったりと、そういった意味でもチーム化の意味は非常に大きかったです。

ちなみに、毎月恒例の月初バッチだけはしばらく自分が担当していたんですが、2022年10月くらいからそれもお任せするようになりました。(年始のあけおめバッチだけは、なんとなくお願いするのが憚られて自分でやりましたが)

4年目(バトンタッチ期)

そして、ようやくほぼすべての運用をバトンタッチすることができたのはつい最近のことです。

とはいっても、もう今年に入ってからはほとんどの運用タスクをチームにお任せしていたため、残っていたのはほぼ今年リリースした仕訳機能に関するお問い合わせ対応やバグFIXくらいでした。

それもようやく先日必要な引き継ぎを終え、ごく稀に質問をもらう程度でそれ以外の運用タスクはすべてチームが巻き取ってくれています。

おそらく2024年のあけおめバッチも、ついに初めてチームメンバーにお願いする形になるのではないでしょうか。(とかいいつつ何故かまたやっているかもしれませんが)

これまでを振り返って

プロダクトローンチ当初からずっと本番運用をして酸いも甘いも味わってきた身としては、それを他の仲間に任せられるようになって、肩の荷が下りた気持ちが3割、頼もしい仲間に支えてもらえてありがたい気持ちが6割、ちょっとだけ寂しい気持ちが1割といった感じです。

自分にとってこのプロダクトは本当に子供のような存在で、誕生当初は「この対応を〜日までに終わらせないとこのプロダクトは死ぬ」みたいなこともあり、なんとかそれを乗り切るために死にものぐるいでしたが、改めて振り返ると本当に楽しく素晴らしい経験をさせてもらっているなと感じます。

今ではUPSIDERは本当に多くのユーザー様にご利用いただけるプロダクトになりました。属人的な頑張りでなんとか支えていくフェーズはとうに終わりを告げ、これからはさらに安全・安心にご利用いただけるようにより強固な仕組み・組織を構築していく必要があります。

そうした運用面も含め、本当に実現したい価値からはまだまだほど遠い状態ですので、爆速でそうした価値を提供していけるように頑張っていきたいと思います!

Tech Blog Reboot!

はじめまして、UPSIDERで2023年8月からVPoEを務めている泉です。

前回の投稿からほぼ一年、間が空いてしまったようですが、やはり発信していくことでエンジニアコミュニティーとのエンゲージメントを深めたり、会社のカルチャーや、技術・プロダクトを紹介する良い場になると考え、再稼働(Reboot!)していくというところで再稼働一発目の投稿を仰せつかりました。

今回は、「いま自分がフォーカスしていること」をいくつか紹介したいと思います。

UPSIDERは、これまでかなり爆速でサービスを展開し、瞬く間に数万社のユーザーに選ばれる法人カードのサービスを成長させてきました。

また「挑戦者を支える世界的な金融プラットフォームを創る」というミッションの基、請求書の後払いを可能にする支払い.com2023年8月に発表したUPSIDER Capital 等、昨年467億という調達で話題になりましたが、その資本をスピーディーかつシームレスに流通させるかという軸をぶらさずに、まさに「金融」という社会インフラをアップデートしていくことにコミットしています。

これだけの成長の裏には、解決しないといけない様々な課題が当然あり、TECH領域においても例外ではありません。

2023年9月に経営合宿を行いましたが、その中で最も重要視されたTECHの最優先課題は以下の通り:

  • 開発基盤改善
  • データ基盤整備
  • 共通基盤整備
  • TECH人材採用

ちなみに、経営合宿でこれだけディープにTECHのテーマをしっかり議論できたことには自分にとって非常に新鮮で、事業計画・事業戦略、競合環境、資本政策、プロダクト戦略等のテーマももちろん議論しますが、その一方でその実現に向けたイネーブラーがTECHにあること、あるいはその裏返しでTECHの課題が、事業進捗を遅らせることに直結することを認識し、経営チームでディープに向き合ってくれることが非常に心強かったです。

開発基盤改善

法人カード「UPSIDER」のサービス・アーキテクチャーは基本、モノレポ・マイクロサービス化されており、ちょっと古い言葉ですが、ユーザーが実際触れるUIを提供するSoE (System of Engagement)、決済取引や各種台帳を正確に記録するためのSoR (System of Records)、そしてそれをつなぐBFF、という形で整理をしつつあります。

現在開発のスピードにキャップをかけている1つの要因がこのBFFにあり、と言っても色々な過去の様々な変遷が絡みあって(リスペクトすべき過去の変遷!)BFF自体がかなり多くのビジネスロジックを持ってしまっております。

またTech Stack的にも SoE = JS(React/Vue)、BFF = Kotlin、SoR = Go、と3言語操らないとフィーチャーデリバリーができない、という状態にあります。Kotlinのエンジニアが不足しているのもあり、SoE, SoRの実装が終わっていてもBFFの開発がキャップになってしまい、思うように開発速度が上がらない。

その課題を解決するために現在 Rearchitecture のプロジェクトを進めており、BFFを本来の姿に差せること、あと技術スタックもFrontに合わせてTSにすることで、技術スタックのギャップを埋めてデリバリーを加速させることに取り組んでおります。

その他、Vue→Reactの移行プロジェクトや、テスタビリティー向上させるための「Flex Staging」プロジェクトなど同時並行で動かし、年内にもこれまでのデリバリー速度に一定制限をかけていた構造的な課題が解決できそうかと思っております。(これらについてもどこかのタイミングでより詳細に紹介できればと思います!)

データ基盤整備

次に指摘されたのがデータ基盤。

ビジネスメンバーがQueryを書いてバンバン分析したいのに、なかなかそれが実現できない!

これまでアプリケーションデータの一部をBigQueryにインポートして、RedashなどのBIツールで可視化するということをやってきたのですが、ほとんどがアプリケーションの生データに対して直クエリを行っており、だいぶ「秘伝のタレ」化してしまっています。

ビジネスが捉えたいドメインデータとアプリケーションデータはなかなか噛み合わないもので、いわゆるデータディクショナリの整理からファクトテーブルの抽出・ディメンションの定義など、かなり0ベースで構築を進めております。

データは、即時性の高い与信審査、毎日数万件の決済データを処理してリスク管理を実現するための生命線でもあり、ひいてはユーザーへの価値提供の源泉なのでここも強化にも取り組んでいます。

共通基盤開発

ここは少し時間軸は長めではありますが、これまでUPSIDERカード、支払い.comの認証基盤がバラバラだったこと、あるいは共通基盤が無いために、UPSIDERカード内でもサービスの循環依存などが起こっていたのですが、それを集約・収束していくための共通基盤開発も行っております。

TECH人材採用

そして、これらの技術関連の課題を解決するだけでなく、主戦であるプロダクト開発も当然行わなくてはなりません。ユーザーがプロダクトの機能不足でチャーンしてしまうことも事業的には痛手ですし、ユーザーがプロダクト価値を感じてくれたからこそここまで成長してきているのでそのモメンタムを加速して行かなければなりません。

アプリケーション開発においては、フロントエンジニアもまだ正社員では2名しかおらず、Goエンジニア、Kotlinエンジニアも足りておりません。

上に挙げた領域ではプラットフォームエンジニア、データエンジニアの助けも必要ですし、UPSIDER Coworkerを初めとする新たな領域の開発、その他MLエンジニア、そしてプロダクト構想を描くプロダクトマネージャー、プロダクトデザイナーもまだまだ実現したい世界を作るためには手が足りておりません。

そのため今はHRマネージャー自ら手綱を持ってエンジニア採用を強化する体制を敷き、採用チャネルの拡大、ブランディング強化を率いてくれております。当のエンジニアメンバーも採用にはかなり熱心に向き合ってくれ、まさに組織一体となって採用にもフォーカスを当てております。

かくいう自分もかなり色々な帽子をかぶって役割を担っていますが、すこーーーしずつ状況が改善してきているのを実感しております。

最後に

ということで、熱意有り余って色々書いてしまいましたが(それでもかなり端折ったつもりなのですが、汗)こんなUPSIDERをより深く広く知ってもらうためのTech Blog、また新たに発信していきたいと思っておりますので、末永くお付き合いいただけると幸いです!

spoti.fi

UPSIDER Tech.fmも開始したので乞うご期待!

Greetings!

Hello esteemed readers and fellow tech enthusiasts! I'm Izumi, the newly minted VPoE (Vice President of Engineering, if we're being formal) at UPSIDER. I took up the mantle in August 2023.

Has it really been a year since our last update? Time sure flies when you're having... well, meetings, coding marathons, and more meetings. Jokes aside, I firmly believe that by sharing our journey, we build stronger connections with the engineering community, offering a sneak peek into our company's vibrant culture, innovative technologies, and groundbreaking products.

So, when they handed me the proverbial mic and said, "Reboot the blog!", how could I resist?

Today, I'd like to share a glimpse of what's been keeping me and the team busy.

UPSIDER: Scaling Heights and Breaking Barriers

UPSIDER's growth trajectory has been nothing short of meteoric. Our corporate card service has earned the trust and adoption of tens of thousands of users. Our mission statement — "Creating a global financial platform that supports challengers" — isn't just a catchy tagline. Last year, we secured funding of a whopping 46.7 billion yen, which was instrumental in accelerating initiatives like shi-harai.com (an innovative post-payment of invoices solution) and the unveiling of UPSIDER Capital in August 2023.

But let's not get sidetracked - at the core, our focus remains on revolutionizing the very fabric of financial infrastructure.

Insights from the Management Offsite, September 2023:

At our recent management offsite in September 2023, we distilled the paramount TECH priorities of the coming months. Here's what topped the list:

  • Refining our development infrastructure
  • Laying down robust data infrastructure foundations
  • Building out a Unified Application Infrastructure
  • The Hunt for TECH Talent

On a light note, it felt like a cool splash of water to engage in deep-dive TECH conversations amidst all the management talk. While we do love our business plans, strategies, and competitive analyses, it's evident that TECH is the backbone supporting these visions. I’m heartened by how hands-on and engaged our management team was in addressing these crucial topics. It's clear we're not just all talk and no tech.

Refining Development Infrastructure

At the heart of our corporate card service, "UPSIDER", lies a blend of Monolithic Repository and microservices architecture. Though it may harken back to yesteryears, we've organized it into three primary systems:

  • System of Engagement (SoE): This is the user interface layer where users get their hands dirty.
  • System of Records (SoR): Our reliable scribe that meticulously logs payment transactions and various other ledgers.
  • BFF (Best Friend Forever... just kidding, it's Backend For Frontend): Our trusted bridge between the two systems.

A critical speed bump in our development highway is the BFF. Despite its storied past (we tip our hats to the previous transitions!), the BFF is laden with business logic.

Diving into our tech stack, we juggle three languages to roll out features: SoE gets its shine from Typescript (React/Vue), the BFF is crafted in Kotlin, and SoR rolls in Go. But there's a wrench in the works – a dearth of Kotlin maestros. This means that even if SoE and SoR are raring to go, BFF development hits a roadblock, putting a damper on our development pace.

Our current solution? The Rearchitecture project. We're on a mission to get the BFF back to its heyday and bridge the tech stack divide by transitioning to TS, which is more in line with the front end. We're optimistic this will put the pedal to the metal in terms of delivery.

And that's not all! We're juggling a couple of other projects – like our Vue-to-React switcheroo and the intriguing "Flex Staging" initiative aimed at amplifying testability. We're confident that by year-end, these infrastructural tweaks will rev up our delivery speeds. Stay tuned for more detailed updates on these projects - we're eager to spill the beans!

Elevating Our Data Infrastructure

Shifting our gaze, enhancing our data infrastructure stands out as a primary objective.

The burgeoning demand to craft queries and dissect data has left us in a bit of a pickle – it's proving to be quite the challenge.

We've dabbled in funneling some application data into BigQuery and presenting it using BI platforms like Redash. However, much of our querying happens directly on the raw application data, making it our closely-guarded "secret ingredient".

A mismatch lurks between the domain data the business yearns to seize and the existing application data. To rectify this, we're donning our construction hats, meticulously organizing a data dictionary, sifting out fact tables, and carving out dimensions.

It's no hyperbole to say that data drives our operations. From zippy credit screenings to managing the mammoth task of processing myriads of payment data daily for risk oversight, data is at the helm. Recognizing its vital role in delivering value to our users, we're pouring energy into fortifying this domain.

Advancing Shared Infrastructure

While this endeavor is a marathon rather than a sprint, we're knee-deep in curating a unified infrastructure. Our goal? Seamlessly merge the authentication infrastructure of UPSIDER Card and shi-harai.com. This masterstroke will untangle any service overlaps inherent to the UPSIDER Card.

Unified Application Infrastructure

While the journey is long-haul, we're actively constructing a cohesive infrastructure that merges the authentication platforms of the UPSIDER Card and shi-harai.com. This strategic move aims to streamline any service intricacies associated with the UPSIDER Card.

On the Hunt for TECH Talent

Beyond overcoming tech hurdles, our primary frontline remains product development. There's a real cost when users bow out due to lack of product features. Our current progress is driven by the undeniable value users see in our offerings, and it's imperative to maintain, if not amplify, this momentum.

Speaking of app development, our current roster includes a mere duo of full-time front-end developers, and we're somewhat starved of Go and Kotlin wizards.

But that's not all. We're on the lookout for platform savants, data engineering maestros, ML engineers, visionary product managers, and creative designers. With ventures like UPSIDER Coworker on the horizon, it's evident we need more hands on deck to shape the future we dream of.

Thankfully, our HR lead is seizing the initiative, amplifying our engineer recruitment drive. The entire engineering brigade is buzzing with enthusiasm about onboarding fresh talent, making talent acquisition a top organizational priority.

I, for one, juggle myriad roles, and it's heartening to sense a palpable uptick in our circumstances.

In Conclusion

My fingers may have run away with me in my fervor (despite efforts to be succinct!).

To offer a deeper dive into the world of UPSIDER, we're toying with the idea of a Tech Blog to foster ongoing dialogue. We're in this for the long haul, so we hope you'll join us for the ride!

Also, the launch of UPSIDER Tech.fm is on the horizon. Keep those ears perked up and stay tuned!

KotlinコルーチンでDBアクセスするときのアンチパターン

こんちには!

UPSIDERのWebチームでサーバサイドKotlinを書いているエンジニアのおかだです。

突然ですが、Kotlinコルーチン使ってますか? もちろん使ってますよね。

KotlinでWebアプリケーションを書くのであれば、リクエストごとにコルーチンを起動してハンドリングするでしょうし、その処理の中でDBにアクセスすることも多いだろうと思います。ですが、このありがちな処理を、ごく普通に書くだけで、わかりにくいバグを生む場合がある、そんな話を紹介します。

やってはならないことは単純。


アンチパターン
DBトランザクション内でsuspendする処理を、大量(DBコネクション総数を超える数)のコルーチンで並行実行してはならない


やってしまうとしたらバッチ処理ですかね。これをやってしまうと、コルーチンはコネクションを使い果たし、マイルドデッドロックとでも言うべき状態に陥ります。特に気をつけてほしいのは、これはスレッド数とはほぼ無関係だということです。スレッド数がコネクション数よりも少ないから大丈夫、とはならないのです*1

どのような機制でこの問題が起きるのか。図で見ていきましょう。

DBコネクションが8つ、スレッドが3つ、コルーチンが20個、という状況を考えてみます。車がスレッド、円柱の下に生えてる触手みたいなやつがコネクションです。スレッドが車のようなものだとは全然思ってないですけどね。コルーチンとの関係性で言えば、コルーチンが乗ってる感あるかなと・・・。図にするのって難しい。

まず、スレッドの数と同じ3つのコルーチンが動き出します。トランザクションを開始してコネクションをプールから借りてきます。

さてここで、アンチパターンに記載したとおり、この処理はトランザクションの途中でサスペンドします。たとえば、DBに何か保存したあとにサスペンド関数でメール送信する、とか。

ここで重要なのは、トランザクションを維持するためには、コネクションはそれぞれのコルーチンが持ち続けなければならないということです。UPSIDERではORMとしてExposedを使っていますが、Exposedはこのように振る舞います。仮にExposedでなくとも、ロジカルに考えて、こうする以外の選択肢ってあまり思いつきません。

最初の3つのコルーチンがサスペンドしたことで、スレッドが空きました。というわけで、実行されるのをワクワクと待ち構えている次のコルーチンがスレッドを割り当てられて、処理を始めます。またコネクションが3つ消費されます。

そしてこのコルーチンたちもトランザクション途中でサスペンドします。

問題は次です。コルーチンがサスペンドして行った処理は(この3台の車とは別のスレッド上で行ったことにしておきますが)、それほど時間のかかる処理ではなく、すでに完了しているとします。つまり、左上で -_- みたいな顔をしている連中の一部は、スレッドが空くのを待っているのです。

ですが、彼らはスレッドを割り当ててもらえません。最初から待ち続けている左下のコルーチンが優先されます。このコルーチンのスケジューリングアルゴリズムCoroutineDispatcher に依存するので、変更できる可能性がないわけではないですが、これをうまくコントロールする CoroutineDispatcher の実装を試みるのはおそらく無理筋です。

最終的には、コネクションはすべて、サスペンド中のコルーチンに持っていかれ、スレッド上にはコネクションを待つコルーチンが居座り続けることになります。

空くはずのないコネクションをどのくらいの間待ち続けるのかはコネクションプールの設定によりますが、仮に30秒だとすると、まだコネクションを得ていない12個のコルーチンがすべてコネクション取得を諦めてタイムアウトするまでに 30秒 * (12コルーチン / 3スレッド) = 120秒 かかります。そのあとようやく左上のコルーチンがスレッドを割り当てられ処理を再開、コネクションを解放していきます。

解決策

アンチパターンを構成する、トランザクションサスペンドか、大量のコルーチン起動、どちらかを回避しましょう。

1. トランザクション内でサスペンドするのをやめる

Kotlinコルーチンのアドバンテージをすこし犠牲にしてしまうかもしれませんが。条件次第では大した代償なく対応できるかもしれません。チーム開発で、この内部処理ではサスペンド禁止、というのを周知し守り続けるのはなかなか難しいものがありそう。

2. 一度に起動するコルーチン数を制限する

先にスレッド待ちしているコルーチンがいなければ、サスペンドしているコルーチンがすぐにスレッドを獲得して処理再開し、コネクションを解放できます。コルーチン数を制限する方法については「Kotlinでコルーチンの並行処理数を制限する」という記事で触れていますので、そちらも合わせて読んでいただければと思います。


career.up-sider.com

herp.careers

*1:スレッド数が逆にコルーチン数を上回るほど多い場合はこの問題は起きないのですが、そういうシチュエーションは普通なさそう

アカウント開設フォームのUI設計における大失敗と爆速改善ストーリー

こんにちは。Webチームでエンジニアとして働いている久保です。

今回は、アカウント開設フォームのUI設計における失敗と爆速改善ストーリーについて紹介していきます。


アカウント開設フォームの通過率が劇的に下がってしまった

UPSIDERをご利用頂く前に、独自の審査を行うために、企業ごとにアカウントを開設していただく過程があります。しかし、とあるリリース以降、アカウント開設のプロセスを通過する割合が極端に減少してしまった事がありました。これは弊社にとって、新規のユーザーが増えず、サービス全体の信頼感も落としかねない大きな問題となりました。

eKYCを完全に終えられていない・・・?

原因を調べたところ、どうも「eKYCを最後まで終えられていない」ということがカスタマーサポートからのヒアリングで分かりました。

具体的な数字を出すと、eKYCの審査通過率が30%ほどと正常な審査通過率(70%)の半分以下になってしまっており、そのせいでカスタマーサポートのスタッフのにも大量に問い合わせが来てしまい、リソースを逼迫する状態にもなっていました。

その原因の一つとして、eKYCフォームに外部ヘルプページに飛ぶリンクを複数設置していて、それらを閲覧しないと理解ができない入力フォームになっていたことが挙げられます。

「eKYC」は法律の概念が関わることから、現在のユーザーの状態を分かりやすく表示したり、これから何をする必要があるのかを適切に表示することが難しい部分です。そのため、さまざまな箇所でヘルプページを利用して解決しようとしていたことが原因の根底にあると、フロントエンドチームの分析により分かりました。

ヘルプページへ遷移せずともアカウント開設できるUIを目指す!!

フロントエンドチームでは、これらの問題を解決するために「ヘルプページへ遷移せずともアカウント開設できるUI」を最低の条件に置くとともに、改めて下記を共通認識とし、UIを刷新しました。

  • 適切なタイミングで適切な情報を提示する
  • 必要のない情報は出さないようにする
  • 直感的に理解できるUIを目指す

また、アカウント開設の入力フォームから開設が完了するまでのフローをFigmaに張り出し、他にボトルネックになってる部分がないかも調査しました。

下記画像は、改善に取り組んでいた際のFigmaです。このページ以外にも4,5ページFigmaが存在し、ボトルネックの洗い出しにプロジェクトメンバー全体で取り組んでいました。

正常な審査通過率に戻り、ボトルネックを解消!

結果的にUI刷新直後から、eKYC導入後の正常な審査通過率と言われる70%前後に戻り、カスタマーサポートへのお問合せも激減しました。

この経験があってからUIを設計する際に「本当にヘルプページがないとダメなのか?」と言う意識がチーム内で生まれ、あるべきUIを目指す姿勢ができたように思います。

まとめ

「アカウント開設フォームのUI設計における大失敗と爆速改善ストーリー」を紹介させていただきました。

最後までお読みいただきありがとうございました。

限られた時間の中で、どのようなUIを提供すべきなのかの判断は難しいところはありますが、それでもあるべきUIを考え、全力で開発・提供するというのはUPSIDERならではなのかなと思っています。

実は一方でリソース的な要因として、当時フロントエンドエンジニア、UI/UXデザイナー、PdMが不足していたことがあります。次の宣伝に続きますが、UPSIDERでは全方位採用を行っております。ぜひUPSIDERにお力をお貸しください。

宣伝

UPSIDERでは、挑戦者を応援する法人カード「UPSIDER」と、すべてのBtoB取引でクレジットカードを利用できるビジネスあと払いサービス「支払い.com」を提供しております。

UI/UXを向上し、ユーザーが使いやすいプロダクトを開発するために全力を注いでくれるエンジニアを募集しています。

career.up-sider.com

herp.careers

Kotlinでコルーチンの並行処理数を制限する

こんにちは!

UPSIDERのWebチームでサーバサイドKotlinを書いているエンジニアのおかだです。

突然ですが、コルーチンの並行処理数、制限したくなったことありませんか? 

ぼくはよくあります。

数千数万という数のコルーチンを同時に起動することもできる、と軽量さが強調されることもありますが、それはメモリ消費が少ないとかそういう話であって、コルーチンによって呼び出される側がその同時大量アクセスをさばけるかは別問題です。たとえば次のような制約が問題になることが時々あります。

特に、DBコネクションの取り扱いについてはKotlinコルーチンならではの落とし穴があったりするので、また別の記事で取り上げたいと思います。

追記:書きました → KotlinコルーチンでDBアクセスするときのアンチパターン - UPSIDER Techblog


まずはシンプルに実装してみる

さて、コルーチン数制限、雑でよければいろいろ書きようはありますが、結構あちこちで必要になってくるのでライブラリ化しておきたいところ。とりあえず、なるべくシンプルに作ってみましょう。

class LimitedCoroutineScope(
    private val underlying: CoroutineScope,
    limit: Int
) : CoroutineScope by underlying {

    private val semaphore: Semaphore = Semaphore(limit)

    private fun <T> withPermit(block: suspend CoroutineScope.() -> T): suspend CoroutineScope.() -> T =
        {
            semaphore.withPermit {
                block()
            }
        }

    fun launch(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> Unit
    ): Job = underlying.launch(context, start, withPermit(block))

    fun <T> async(
        context: CoroutineContext = EmptyCoroutineContext,
        start: CoroutineStart = CoroutineStart.DEFAULT,
        block: suspend CoroutineScope.() -> T
    ): Deferred<T> = underlying.async(context, start, withPermit(block))
}

suspend fun <T> limitedCoroutineScope(
    limit: Int,
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend LimitedCoroutineScope.() -> T
): T =
    withContext(context) {
        LimitedCoroutineScope(this, limit).block()
    }

使い方はこう。こちらもシンプルです。

fun doSomething() = limitedCoroutineScope(20, context = Dispatchers.IO) {
  val users = userRepository.getAll().map { user ->
    async { findExtraInfoOf(user) }
  }.awaitAll()
  // ...
}

説明の必要はあまりないと思いますが、一点だけ。3行目の async は、 CoroutineScope.async ではなく、 LimitedCoroutineScope クラスの async メソッドです。これは、ある型に対して同じシグネチャの拡張関数とメンバ関数が定義されている場合、メンバ関数が優先される、というKotlinの仕様に依拠しています。ちょっと紛らわしいかもしれない。

CoroutineContextを使った実用的な実装

実際のプロジェクトでコルーチンまわりを共通化しようとするとき、上記のように新しい型を作ってそこに機能をもたせるやり方だと拡張が難しくなります。 CoroutineScope ってネストさせることもよくあり、たとえば次のように書くだけで、コルーチン数制限はできなくなります。

fun doSomething() = limitedCoroutineScope(20, context = Dispatchers.IO) {
  withTimeout(60.seconds) {
    // thisはLimitedCoroutineScopeではないので、コルーチン数制限はできない
  }
}

そもそも解決の仕方がKotlinコルーチンの設計思想とマッチしてないんでしょうね、たぶん。同時処理数をセマフォで制限する、という点は問題なさそうなので、そこは踏襲しつつ、もっとKotlinコルーチンらしい設計に変えてみましょう。こういうときに使えそうなのが CoroutineContext です。

たとえば、UPSIDERでも使っているORMのExposed。コルーチンがサスペンドしてスレッドを失ってもDBトランザクションを保つためには、スレッドローカルではなくコルーチン側にトランザクションを紐付けておく必要があります。Exposedは CoroutineContext 内にトランザクションを保持することでそれを実現しています。トランザクションの途中で新しい CoroutineScope を起ち上げた場合も、 CoroutineContext なら親から子へ引き継がれるので、その処理は同じトランザクションの一部となります。

まず、セマフォをラップして、 CoroutineContext の要素( CoroutineContext.Element 型)として扱えるようにします。要素と言いましたが、 CoroutineContext は構成要素ひとつひとつが CoroutineContext でもあります。Compositeパターンですね。

internal data class CoroutineLimit(
    val limit: Int
) : AbstractCoroutineContextElement(CoroutineLimit) {

    internal val semaphore: Semaphore by lazy {
        Semaphore(limit)
    }

    companion object Key : CoroutineContext.Key<CoroutineLimit>

    override fun toString(): String = "CoroutineLimit($limit)"
}

続いて、 CoroutineLimitCoroutineContext に付け足してくれる CoroutineScope ビルダを作ります。 limit が0以下のときはコルーチン数制限はされない、ということにしておきます。

suspend fun <T> limitedCoroutineScope(
    limit: Int,
    context: CoroutineContext = EmptyCoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {
    val newContext = coroutineContext + context +
        (if (limit > 0) CoroutineLimit(limit) else EmptyCoroutineContext)
    return withContext(newContext, block)
}

最後に、 CoroutineLimit が見つかったらコルーチン数を制限するという機能付きのコルーチンビルダを、 CoroutineScope の拡張関数として用意します。最初の例と違ってわかりやすい名前にしてみました。

fun CoroutineScope.launchWithLimitedConcurrency(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = coroutineContext + context
    return this.launch(context, start, newContext.createLimitedBlock(block))
}

fun <T> CoroutineScope.asyncWithLimitedConcurrency(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = coroutineContext + context
    return this.async(context, start, newContext.createLimitedBlock(block))
}

private fun <T> CoroutineContext.createLimitedBlock(block: suspend CoroutineScope.() -> T): suspend CoroutineScope.() -> T =
    this[CoroutineLimit]?.let {
        {
            it.semaphore.withPermit {
                this.block()
            }
        }
    } ?: block

使い方は、 launchasync の関数名が異なるだけで、基本的に最初の例と同じです。この実装なら、 CoroutineScope入れ子にしても正しく動いてくれます。

fun doSomething() = limitedCoroutineScope(20, context = Dispatchers.IO) {
  val users = userRepository.getAll().map { user ->
    asyncWithLimitedConcurrency { findExtraInfoOf(user) }
  }.awaitAll()
  // ...
}

fun doSomething() = limitedCoroutineScope(20, context = Dispatchers.IO) {
  withTimeout(60.seconds) {
    repeat(100) {
      // 内側のCoroutineScopeにCoroutineLimitが引き継がれているので、
      // 並行処理数は期待通り20個までに制限される
      launchWithLimitedConcurrency { /* do something */ }
    }
  }
}

CoroutineLimit を共有しさえすれば、スコープが入れ子になっていなくても同じコルーチン数制限が適用されてしまう点は、役に立つ場合もあるかもしれない反面、自由度が高すぎてシステムの挙動が読めなくなる可能性がありそうです。 CoroutineLimitinternal としているのはそのためです。


宣伝

UPSIDERでは、成長企業のための法人カード「UPSIDER」と、すべてのBtoB取引でクレジットカードを利用できるビジネスあと払いサービス「支払い.com」を提供しています。

ぼくが所属するWebチームはもちろん、UPSIDERの開発組織のいたるところでエンジニアが足りません。いろんな新しい技術にもこれから挑戦していきます。

カジュアル面談もやっておりますので、少しでもご興味のある方は、ぜひご連絡ください!

career.up-sider.com

herp.careers

決済チームの「当たり前の品質」を支えているもの

こんにちは。決済チームでエンジニアとして働いている小須田です。

今回は、多様なメンバーが増えていく決済チームで、「当たり前の品質を保ち続ける」ための取り組みをご紹介します。

TL;DR

  • Issueの形式を定め、紐づくPull Requestの粒度をモジュール単位で確定させる
  • Pull Requestの形式を定め、マージの影響を明確にする
  • コードの満たす品質をCIで担保する

前置き

UPSIDERはエンジニアの採用に力を入れており、決済チームにも多様なバックグラウンドを持つ優秀なメンバーが次々と参加してくれています。それは、新たな強みが多く手に入る環境である一方で、一定以上の品質を保つこととの戦いの場でもあります。

その環境下で私たちが品質を保つ、つまり「当然なされるべきことが、確実になされる」ためには、仕組み化・自動化が必要になってきます。だれがやっても小さい労力で、必須部分が担保される状態が理想ということですね。そこが省力できればできるほど、より本質的な課題に注力する余裕も生まれてくるというものです。

今回ご紹介する内容は、上記の仕組み化・自動化に関するもののうち、タスク・コードの質を担保するためのGitHub上での取り組みに焦点を当てたものです。

逆に今回は、下記の内容には言及しません。

  • Issueに落とす前の、要件定義と基本的な設計
  • ディレクトリ構成など、ソースコードに関する具体的な内容
  • Pull Requestがマージされた後の、デプロイなどの内容

※また、本記事は決済チームのメインである、Go言語によるマイクロサービスを前提とした内容となっています。


Issueの形式を定め、紐づくPull Requestの粒度をモジュール単位で確定させる

IssueもPull Requestも、ドキュメント作成業務には共通することですが、一定以上の質を保ちつつ省力したいなら、定型化してしまうのが効果的です。

定型化で重要なのは、どこになにをどの粒度で書けば良いかが決めることです。そうすることによって、書く側も読む側も迷うことがなくなり、また新しいメンバーの記載漏れもグッと減ります。

UPSIDERでは、チームごとにIssue Templateを作成しており、決済チームでは機能開発時のIssueに下記の内容を記載することにしています。

  • Goals
    • 目的。Issueのタイトルと密接に関連するものとする
    • このセクションにはあまり多くの詳細を記入せず、端的に書く
  • Background
    • 背景。具体的な事例も書くことを推奨する
  • Implementation Ideas
    • 設計。概要とともに、テーブル定義、API定義、必要に応じた実装の詳細を記載する
  • References
    • 参照物。関連Issue、Slackでの重要な会話リンクなどを記載する

画像のように、必須項目なども指定できるIssue Formsが、Public向けのベータ版機能として存在しており、Privateリポジトリでも組織単位で申請することで利用できる場合があります。(参考情報: GitHubコミュニティでの投稿。2022/10/01現在の情報であり、変更の可能性があります) Issue Formsを使えない場合もTemplate自体は利用できるので、ガイドとしてのTemplateを作成し、それに沿わないIssueにはコメントで伝えていくのが地道ながら効果的です。

また、少し特殊な点として「Implementation Ideasの部分でPull Requestの粒度を確定させる」ことをしています。

粒度は基本的にGoのモジュール単位です。決済チームではマイクロサービスの単位でモジュールを切っているため、Pull Requestの粒度と合わせやすくなっています。

具体的には、下記の単位でモジュール = Pull Requestが切られています。

  • テーブル定義
  • マイクロサービスごとのAPI定義 (gRPC + protobuf)
  • マイクロサービスごとの実装

この粒度には賛否あると思うのですが、決済チームではPull Requestは小さく保つことを目指しています。これはレビューのハードルを下げ、マージを迅速に行うためです。 また、この粒度まで確定するルールにすると、実装に入るために一定の内容まで設計を進める必要があります。タスクの進め方も均質化を図ることができ、えいやっで実装してしまうようなケースを抑制する側面もあるのです。

このように、Issueでは形式・内容とともにPull Requestの粒度について、Templateや基準を設けることで均質化を図っています。


Pull Requestの内容と形式を定め、マージの影響を明確にする

Pull Requestも基本理念はIssueと同じです。 定型化して内容を担保し、できた余裕をそのPull RequestのSpecificな内容の議論に当てます。

Pull RequestでもTemplateを作成していますが、担保したい内容は当然Issueとは異なります。プロダクションコードへのマージが行われるため、Issueに比べ注意深く影響を確認できるような項目を増やしています。

下記にシンプルにまとめてみました。

  • What: 何を変えたか
  • Why: なぜ変えたか
  • Reference: 関連Issue, Pull Requestなど
  • Rollback Procedure: 単純にRollbackできるか、できない場合は何が必要か
  • Expected Impact: マージされた際の現行への影響は何か
    • ここには特に、影響や副作用が出やすい項目のチェックリストを用意
  • (Optional)Background: 特殊な事情など
  • (Optional)Not Implemented: ToDoとして残したもの
  • (Optional)Test Details: 特殊なテスト方式など

これらの項目は一般的なようで、実はメンバーそれぞれが経験してきた職場環境などで文化が異なるため、自分で書き方を考えたり他の人の書き方を読んだりするのにエネルギーを使う部分だと思います。 実際にレビューしている身としては、決まった場所に決まったものが書いてある(なければ指摘をすれば良い)という状態は、かなりストレスフリーです。現状決済チームのメンバーは、これらをきちんと書いてくれており、とてもいい状態だと思っています。


コードの満たす品質をCIで担保する

詳細なコードレビューに関しては、コーディングルールなど用意しているものの、メンバーの強みに頼っている部分が大きいと思っています。それらは今回のテーマとは少し外れるものなので、いまは言及しません。ただし、本質的な部分のレビューに注力するためにも、仕組み化による余裕が大事になってきます。

決済チームのPull Requestのコード品質に関する仕組みは主に2点で、CIによるBuild & Testが正常終了することと、特定権限のレビュアーからApproveされることです。(このレビュアーは自動でCode Ownerが任命されます)

今後追加したい内容としては、テックブログの第2回でも触れていたテストカバレッジの観点をPull Request上から確認できるようにしたいと思っています。検討は必要ですが、カバレッジをマージ条件に加える可能性もあるでしょう。

決済チームのCIはGitHub Actionsを利用していますが、UPSIDERは基本的にmonorepoを採用しているため、他チームの環境に合わせたWorkflowも数多く存在します。

当然変更内容に応じたWorkflowのみを動かしたいのですが、その場合はブランチ保護がうまく働かず、「動かしたWorkflowのみ正常終了すればマージ可能」ということができません。

これを解決するため、UPSIDERではMerge GatekeeperというPull Requestマネジメント用の機能を作成しました。これにより上述でやりたかった細かなマネジメントが可能になります。

実はMerge Gatekeeperは決済チーム独自のものではないのですが、MITライセンスでOSS化してあり、導入もとてもシンプルなので、この機会に紹介させてもらいました。ぜひお試しください。


あとがき

決済チームの当たり前の品質を支える取り組み、いかがでしたか?

シンプルに均質化、省力化しており、できた余力でメンバーにバリバリ活躍してもらおうという姿勢が伝わっていたら幸いです。今回はいわゆる守りのお話でしたので、今後は決済チームの攻めのお話もしていきたいと思います。

お楽しみに!

宣伝

UPSIDERでは、成長企業のための法人カード「UPSIDER」と、すべてのBtoB取引でクレジットカードを利用できるビジネスあと払いサービス「支払い.com」を提供しております。

まだまだプロダクトで実現したいことがたくさんあり、プロダクトの急激な成長に伴う課題も増えている中で、一緒に事業を前に進めてくれるエンジニアを絶賛募集中です。

カジュアル面談もやっておりますので、少しでもご興味のある方は、ぜひご連絡ください!!!

career.up-sider.com

herp.careers

支払い.comのカオスで整備中なエンジニア組織について

支払い.com で主にバックエンド全般を担当しているエンジニアの水村です。

支払い.comのエンジニア組織はまだまだ整備中で課題も多く、カオスな状況です。そんなカオスな状況を改善するため、やらないようにしていることや、意識的にやっていることなどをまとめました。

支払.com のサービスについては、支払い.comのサイトをご覧いただけるとめちゃくちゃ嬉しいです。(https://shi-harai.com/

TL;DR

  • 我々がやらないようにしていること
  • 意識的にやっていること
  • 開発フロー
  • エンジニア紹介

やらないようにしていること

いきなり話がブッ飛びますが、エンジニア組織でやらないと決めていることは次の通りです。また、いくつかの項目については詳細を記載しました。

アジャイル開発やスクラム開発をベースに開発を行っているのですが、デイリースクラムやスプリントなど、検討した結果採用していない手法もあります。

  • エンジニア抜きでの意思決定
  • スプリント
  • 無駄な会議
  • デイリースクラム的な毎日のミーティング、稼働の管理
  • 過剰なタスクの管理
  • 休日、深夜の Slack メンションや DM を遠慮して控えること
  • Slack DM によるコミュニケーション
  • ダメな物事を無視する行為
  • 偉そうにすること
  • 聖域を作ること
  • 働きすぎること

エンジニア抜きで意思決定をしない

エンジニアが意思決定に大きな裁量と権限を持っているので、基本的に意思決定にはエンジニアが関与します。

エンジニアが意思決定に大きな裁量と権限を持っているのは、カード側の事業も同じです。支払い.com事業でも同様に、ビジネスサイドの偉い人がこういったからそれに従う、といったような組織ではありません。

よって、エンジニアには自立的に動いて意思決定をすることが求められます。言い方を変えると、自走力のあるメンバーであれば、正社員でなくとも一定以上の権限と裁量を持って取り組むことができる環境です。

むしろ、このように大きな権限と裁量を持っているフリーランスや副業のエンジニアが多いのがUPSIDERという会社の特徴の一つです。

スプリントをやらない

フルリモート、フルフレックス、コアタイムなし、稼働時間に制限がなくバラバラ、副業が多いという環境であるからこそ、スプリントを採用することが難しいので採用していません。

スプリントを採用した場合、毎週リリースしなければいけないというプレッシャーから、品質がおろそかになったり、テストがおざなりになって本番障害につながるというリスクがあると思っています。

微妙な状態でリリースするのかどうかという判断を毎回するのも面倒ですし、バグがある状態でリリースブランチにマージされていると revert するのも面倒ですし、とにかく面倒なことが多いわけです。

リリース対象が増えれば増えるほどリリースに対する負荷が高くなり、テスト対象が増え、影響範囲も大きくなるので、細かくテストして細かくリリースできた方が高い品質を維持できるという認識からスプリントを採用していません。

ですが、スプリントを採用することによって開発サイクルにリズムが生まれて開発がやりやすくなるというメリットがあることも認識しているので、スプリントで開発することが最適だと思ったら採用すると思います。

無駄な会議、デイリースクラム的な毎日のミーティング、稼働の管理

会議などにより必要以上に時間を拘束することは、エンジニアという職種の特性上、業務に支障がでることが多いので、無駄な会議を行わないようにしています。

必要に応じて会議は設定していますが、副業でジョインしているメンバーが多いためデイリースクラムを行うことは実質不可能です。働く時間帯がメンバーそれぞれ異なるため、作業を開始するときに Slack で「稼働開始、終了」といった報告も不要としています。

各々のエンジニアの稼働については、性善説に基づいています。不正をしたところで github のコントリビュートが少なければわかりますし、その人が働いているかどうかは、一緒に働いていれば大体わかります。

ただ、デイリースクラムができないことにより、日々のコミュニケーションが少なくなってしまうことや、エンジニア同士の関係性を築くことが難しくなってしまうと思っています。

そのために、エンジニア同士が会話をしやすいような雰囲気づくりを心がけたり、ちょっとした雑談をするような任意参加のミーティングを設けたりするといった施策もおこなっています。

休日、深夜の Slack メンションや DM を遠慮しない

休日、深夜の Slack メンションなどは基本的に気を遣う必要はないというスタンスです。

理由は以下の通りで、どうしても今すぐに反応してほしい緊急事態の場合は電話をする運用になりました。

  • 非同期コミュニケーション前提なので、後で返事すれば問題ない
  • 通知は各自でオフにできるので休日、深夜はオフにしても問題ない

また、Slack DM に関してはあまり使用を推奨していません。会社的にもオープンなコミュニケーションを行うようにしたいという考えがあるので、基本的に Slack チャンネルでの発言を推奨しています。

ダメな物事を無視しない

ダメな物事を無視していると、割れ窓理論的に秩序が保たれなくなり、優秀な人がいなくなるので、なるべく早く手を打つように心がけています。

ですが、ある程度放置しても問題なさそうなことに関してはあえて放置して、成り行きに任せるケースもあるので、その辺りの見極めが必要だなと思っております。また、問題によっては「この問題は放置しても問題ないよね」という合意が必要だと思うので、事前に合意するという運用が必要になってくると考えています。今のところまだそのケースは発生していないですが、人が増えてくると必要になりそうかなという認識でいます。

偉そうにしない

まず、UPSIDERでは偉い人という概念がありません。基本的に全員フラットで役割の違いがあるという状態を目指しています。

「Tomo さん(弊社代表の水野)が言っているから正しい」というようなことはなく、間違っていると思ったら反対意見を言います。関係性としてはフラットですが、お互いへのリスペクトや礼節はある、という組織です。

あとは、パフォーマンスや日々の行動によって、自然発生的にリーダーやマネージャー的なポジションが決まっていっていくような感じです。実際、僕はリーダーをやってくださいと任されたわけではないですが、自然とそうなっています。もっとイケてるリーダーは他にたくさんいると思うので、いつでも代わってもらえるように環境を整備中です!

聖域を作らない、働きすぎない

これらの施策はまだ取り組み中ですが、目指している課題でもあります。

主体性と属人化、コードオーナーと責任感は、複雑に絡み合って切り離すのが難しいと個人的には考えています。ですが、同時にその人にしか作業できない聖域を作ってしまうので、共有したい業務の仕様を少しずつ切り出して他の人にやってもらったり、モブプロを行って他の人に仕様を共有する、ドキュメントを整備するなどで対応していっている最中です。

働きすぎない、というのは文字通り働きすぎないようにすることです。仕事の Slack を昼夜休日問わず見続けたり、仕事が無限にあるからといって毎日遅くまで働くと脳がおかしくなるので、働かない時間を意図的に設けています。スタートアップではこれがかなり難しくなってしまう傾向があるので、意識的にやる必要がある認識です。


意識的にやっていること

エンジニア組織で意識的にやっていることを次に記載しました。

  • 小さく頻繁にリリースする
  • リファクタリングを頻繁に行う
  • 技術負債を早めに解消する
  • コミュニケーションしやすい環境づくりをする
  • ジョインした人がミスマッチだった場合はすぐに対応する

小さく頻繁にリリースする

支払い.comでは早く作って早くリリースすることを心がけています。

リリースサイクルは特に定めていないので、機能が出来上がってテストが完了したら、すぐにリリースすることが多いです。簡単な修正や小さな機能は、すぐにテストが完了することも多いので、週に数回リリースすることも割とあります。スプリントを採用していないのでリリースまでの待ち時間(リードタイム)がほぼ存在しません。全体に関わる影響が大きい機能に関してはリリースタイミングの調整が必要になるので、もちろんそのようなケースではリリース時期を調整することはあります。

リファクタリングを頻繁に行う

細かいリファクタリングを頻繁に行うようにしています。

放っておくと負債はどんどん積み上がっていってしまい、品質を維持できなくなり、かつ素早くリリースすることが困難になるためです。というよりも、エンジニアが自発的にリファクタリングを行なって、コードベースをきれいな状態に保っているので、気がついたらなんかコードベースが綺麗になっていてありがたいという感じです。

もしかしたら AI が勝手にコードを綺麗にしているのかもしれません。

技術負債を早めに解消する

大きめな技術負債は早めに解消するように心がけています。

ローンチ当初は Nuxt.js とAnt Design という UI ライブラリを採用して画面を開発していたのですが、Nuxt.js が2系で Ant Design のレイアウト制約などがきつくて両方とも技術負債になってしまいました。

そこで、Nuxt.js と Ant Design をやめるという判断をローンチから3ヶ月ほど経過した際に行い、1ヶ月ほどかけて Nuxt.js をやめて、 Next.js にリプレイスを行いました。機能がまだそれほど多くなかったので、約1ヶ月という短期間でリプレイスは完了し、かつReact に強い優秀なエンジニアが何名かジョインしてくれたので、Next.js にリプレイスしてよかったと思っています。Ant Design をやめてからは、基本的に UI ライブラリは使用せず、必要に応じて小さなライブラリを導入するようにしています。管理系の画面は Nuxt.js のままなので、こちらもそのうちリプレイスするか、Nuxt.js の3系にバージョンアップしたいところです。

コミュニケーションしやすい環境づくり

フルリモート、フルフレックス、コアタイムなし、働く時間も自由という環境は、とても自由度が高く働きやすい環境ではあるのですが、コミュニケーションが希薄になってしまい、チームの一員として動くことに障壁が存在します。

僕自身も多くのメンバーとリアルで会ったことがありません。よって、コミュニケーションをしやすくするための施策として、定期的に雑談をする任意参加のカジュアルなミーティングを設定しました。

English-speaker が何名かいるので、基本的には英語で話すことを推奨しています。また、英語が苦手な人でも話しやすいような雰囲気づくりを心がけたり、仕事の話だけではなく、お互いのプライベートな話や最近の出来事などを英語で話すようにしています。こうすることによって、仕事のちょっとした相談などをしやすくしたり、ミーティングの場でも話しやすい環境づくりにつながればと思っています。

このような施策以外にも、例えば新しくジョインした人に対しては、毎日軽く話すような軽めのミーティングを設定することも考えています。さらに、UPSIDER 全体の施策としては、毎週木曜日 WeWork にエンジニアが集まってカジュアルに話す WeWork day というのを設けていますので、もし UPSIDER のエンジニアとちょっと話してみたい方がいらっしゃいましたら、是非ご連絡ください。いつでもウェルカムです。

ジョインした人がミスマッチだった場合はすぐに対応する

正直なところ、面談をしただけでは判断できないことが多く、一緒に働いてみないとわからないことが多いです。

一緒に働いていると、成果物が出るまでの早さや適切なタイミングでのコミュニケーション、コードの品質など、いろいろとわかってくることが多いので、そこで初めてちゃんと評価ができる認識でいます。もちろん、面談時に判断できることも一定あるので、面談の際にはあらかじめ決めた質問をして、人によって面談の内容にばらつきが出ないように標準化して面談をするようにしています。

ミスマッチが発生した場合は早めに手を打つようにしており、こちら側で改善が可能なことに関しては改善し、相手に改善が必要だと思ったことは率直に伝えるようにしています。結果はどうあれ、なるべく早く対策を打つことが、お互いにとってポジティブであると考えています。


開発のフローについて

特にこれといって変わったことをやっているわけではなく、一般的な開発フローです。

git のブランチ構成は develop, main ブランチと各機能単位のブランチを作成する一般的な構成です。タスクの管理は主に github project を issue で管理しており、Epic issue を作成し Epic issue の中に各種 sub task の issue を作成して追記するようにしています。Epic issue のサンプルは次のような記載です。English-speaker のエンジニアがいるため、基本的に全て英語で Issue を記述するようにしていますが、限定的に日本語で書いても OK としています。

開発のフローは notion に定義し、どのような流れで機能を開発するのか、意思決定の基準は何かなどを記載しています。ただ、一般的な開発の流れだと思うので、普通のエンジニアであれば迷うことなく開発が進められる認識です。


エンジニア紹介

支払い.comで活躍しているエンジニアの特徴を紹介します。

  • 素早く機能を作り上げること
  • コードの品質が高くバグが少ないこと
  • 自立性があること
  • 重要な部分に関してはきちんと相談ができており、その判断を間違えることがない
  • 相談の必要ない部分に関しては自分で判断して機能を作る

これだけ書くとものすごく仕事ができて、人間的にも非常に優れているように思うかもしれません。ですが、実際のところ、新しいゲームが発売されると気配が消えるエンジニアもいますし、部屋がめちゃくちゃ汚かったり、そもそも机と椅子がない状態でベッドの上働いているエンジニアもいたりします。

また、学歴がとても良いというわけではなく、僕は IT 系の専門学校卒(日本電子専門学校という素晴らしい専門学校です)ですし、新しいゲームが発売されると気配が消えてしまうエンジニアも同じ専門卒です。高卒のエンジニアもめちゃくちゃ活躍してますので、学歴はぶっちゃけ関係ないなって感じです。

エンジニアとして濃い経験をしているメンバーが多いので、自走力やタフネスなど、それぞれがそれぞれの強みを持ったチームだと自負しています。ブラック企業で叩き上げられたエンジニアが多いので、とにかくタフネスが違います。

このように、非常に優秀だけどどこか欠点がある(?)エンジニアに支えられています。

支払い.comのエンジニアを何名かピックアップして簡単にご紹介します。エンジニアの多くが30代であり、経験豊富なメンバーが多く、ほぼ全員がフリーランスです。とてもいい人が多いので大変助かっておりますし、みんな優秀で自律的にいろいろと動いて取り組めるタイプです。

フロントエンド

  • Oさん
    • フロントエンドの全般を担当
    • とにかく実装がめちゃくちゃ早くてクオリティも高いし、レスポンスも早い
    • 副業で週2-3日の稼働だが、夜に働いていると思って翌朝に気づいたらフロントエンドの機能がほぼ出来上がっているので妖精かもしれない
  • Oさんが連れてきたOさん
    • 副業でジョイン(週1ぐらいの稼働)
    • 実装が早くバグが少なくて最高
    • Slack のアイコンが猫
  • Mさん
    • 副業でジョイン(週1の稼働だがおそらく週2〜ぐらい稼働している)
    • タスクをお願いして気づいたら終わっている
    • OpenAPI のスキーマ分割をお願いしたら、なんか知らんけどいつの間にか終わっていた
  • 僕の友達のOさん
    • 別プロジェクトで Go をメインに書いているが、 React の経験豊富なのでジョインしていただいた
    • 今は稼働少なめだが、能力はめちゃ高いので将来的にいろいろやってもらえそうな感じ

バックエンド

    • バックエンド全般を担当していたが人が増えたので最近はマネジメントを担当
    • エンジニアとビジネスサイドが協調して働ける環境整備をおこなっている
    • 最近ジョインした English-speaker のエンジニアと3歳児並みの英語力で週に2-3回打ち合わせをしている(大変ご迷惑をおかけしています。。)
  • Aさん
    • バックエンドの支払い登録周りの API など、コアな機能を担当
    • 三十代最後の夏が終わった
    • 大作のゲームが発売されると1ヶ月ほど姿が消える
    • ちゃんと働いているときはきちんと成果を出すので黙認している
    • 自宅でとんこつラーメンのスープを作りながら MacBook で仕事をしていたらとんこつスープの脂分で MacBook のファンが壊れて MacBook も壊れた
  • もう一人のOさん
    • 別の会社で正社員をやりながら副業で支払い.comを手伝っていただいている(週1ぐらいの稼働)
    • バックエンドを主に担当していただいているが、僕からの無茶振りで管理画面(Nuxt.js)も作成していただいており、積極的にいろいろ手を動かしていただいている
    • 医療系の業界からエンジニアに転身してエンジニアのキャリアはまだ浅いがすごくしっかりしている
  • Vさん
    • 最近ジョインした外国籍で English-speaker のエンジニア
    • 別の会社で正社員をやりながら副業で支払い.comを手伝っていただいている(週2ぐらいの稼働)
    • 本業では PM, スクラムマスターなどマネジメントを担当されているが、支払い.comではバックエンドの api 開発などを担当していただいている
    • マネジメントができてコードも書けるので、あとはとにかく僕らが英語をがんばろうという気持ちです
  • Hさん
    • Hさんも最近ジョインした English-speaker のエンジニア
    • 技術レベルが高く、機械学習をやったり不正利用検知などを担当していただいている
    • ヴィーガンのため日本のレストランで食事をするのに苦労している
  • Yさん
    • バックエンド全般を担当
    • 最も複雑な支払い登録周りの処理を素早くキャッチアップしていただいている
    • 支払い.comのエンジニアチームの中では数少ない英語がちゃんと話せる日本人エンジニア(余談ですが、僕の英語レベルはだいたい3歳児並みで、週に2〜3回 English-speaker なエンジニアとミーティングをしています)
    • 来年ごろカナダに移住する予定
    • カナダはビザを取るのがそんなに難しくないらしい
    • カナダには雄大な自然がある

あとがき

エンジニアが働きやすい環境を作るためには、チームみんなの協力が不可欠なのですが、いろいろな改善案などを提案してくれたり、みんなに協力してもらってめちゃくちゃ助かっています。

まだまだ改善中でカオスなエンジニア組織ですが、みんなが働きやすい環境になるように、これからも継続して改善していきたいと思っています!

最後までお読みくださり、ありがとうございました。UPSIDER はエンジニアやプロジェクトマネージャー、プロダクトマネージャーなど幅広く募集中ですので、もしご興味がありましたら、ぜひ一度カジュアルにお話しましょう!

career.up-sider.com

herp.careers