Slide 1

Slide 1 text

Copyright© M&Aクラウド スピンオフサービス構築で 培われた開発ノウハウをご紹介! PHPerKaigi2023 リジェクトカンファレンス Akito.Tsukahara

Slide 2

Slide 2 text

Copyright© M&Aクラウド 2 自己紹介 塚原彰仁 AkitoTsukahara 株式会社M&Aクラウド AkitoTsukahara akito_tsukahara

Slide 3

Slide 3 text

Copyright© M&Aクラウド 昨年2022年11月に 「資金調達クラウド」を リリースしました🎉 3

Slide 4

Slide 4 text

Copyright© M&Aクラウド スピンオフサービス構築で 培われた開発ノウハウを お伝えします! 4

Slide 5

Slide 5 text

Copyright© M&Aクラウド この発表で聞けることは? 今日話すこと ● スピンオフサービス開発で直面した課題と解決策 ○ 「サブドメイン」か?「パス」か?後回しにできない仕様決定 ○ 同じアカウントでPFを行き来できるようにする ○ 新しいサービスドメインを確立させていくには 今日話さないこと ● プロジェクトマネジメントに関するノウハウ

Slide 6

Slide 6 text

Copyright© M&Aクラウド ざっくりとサービスの紹介 ● M&Aクラウド ○ M&Aクラウドは会社・事業を売却したい売り手と会社・事業を買収したい買い手のマッチン グプラットフォーム ○ 資金調達することもでき、事業会社からの調達が可能

Slide 7

Slide 7 text

Copyright© M&Aクラウド 7 ざっくりとサービスの紹介 求人広告のように買い手がM&Aニーズを公開することで、売り手自ら買い手を探せる ダイレクトマッチングにより人に依存しないスケーラビリティを実現 買い手 月額無料 成功報酬 ・ 案件報酬はM&A成功報酬のみ ・最低手数料なし ・売り手ソーシングのチャネルが増える ・M&Aニーズを発信するだけで売り手を集客 ・仲介業者を介さずにダイレクトにやり取り可能 ・潜在層にもリーチが可能 手数料無料 売り手 ・買い手のM&Aニーズを自ら調べることが可能 ・仲介業者を介さずにダイレクトにやり取り可能 ・買い手のM&A担当者に直接コンタクトをとれる ・仲介業者を使わないので手数料が無料 買い手のメリット 売り手のメリット ・仲介手数料が無料 〇〇領域の会社を 募集します この会社と一緒に やっていきたい! 1.掲載する 2.オファー M&A・出資ニーズを掲載

Slide 8

Slide 8 text

Copyright© M&Aクラウド 新サービス「資金調達クラウド」 ● 資金調達クラウド ○ 資金調達目的で登録してくださるユーザーが増加傾向にある ○ M&Aクラウドで資金調達ができることの知名度が低い ■ 資金調達ユーザー向けのプラットフォームを開発しよう

Slide 9

Slide 9 text

Copyright© M&Aクラウド 出資企業 資金調達企業 9 資金調達クラウドのサービスフロー 完全無料で出資企業のマッチング(紹介)ができるプラットフォーム 手数料完全無料 1.掲載する 3.資金調達依頼 M&A・出資ニーズを掲載 2.発信する 〇〇領域の会社に 出資したい! この会社から 資金調達を受けたい 【メリット】 出資ニーズを発信するだけで、資 金調達ユーザーから連絡が来る 【メリット】 出資企業の出資ニーズを見ること で、適切なマッチングが可能

Slide 10

Slide 10 text

Copyright© M&Aクラウド リリースまでの大まかな流れ ● 開発スケジュール ○ PdMとビジネスサイドのすり合わせは3ヶ月程度 ○ 開発は3ヶ月(7月〜10月末) ○ リリースは11月1日(必達で!) ● スコープ ○ 新規作成は新プラットフォームの3ページ ■ リリースに間に合うように調整可能 ○ 既存プラットフォームと共存できるように機能拡張 ■ 同じアカウントで資金調達とM&Aの両プラットフォームを利用したい

Slide 11

Slide 11 text

Copyright© M&Aクラウド リリースまでの大まかな流れ

Slide 12

Slide 12 text

Copyright© M&Aクラウド 開発における課題と解決策 12

Slide 13

Slide 13 text

Copyright© M&Aクラウド ● サイト構成 ○ 「サブドメイン」と「パス」どっちで新サービスを用意するのか ■ この選択で開発作業が大きく変化する ■ finance.macloud.jpOR macloud.jp/finance ● 同じアカウントでどちらのサービスも利用できる ○ M&A目的でも後から資金調達に切り替えられるように(その逆も) ● 新しいサービスドメインを確立させていく ○ 既存サービスドメインと中途半端に混ざらないように 開発における課題

Slide 14

Slide 14 text

Copyright© M&Aクラウド ● サイト構成 ○ 「サブドメイン」と「パス」どっちで新サービスを用意するのか ■ この選択で開発作業が大きく変化する ■ finance.macloud.jpOR macloud.jp/finance ● 同じアカウントでどちらのサービスも利用できる ○ M&A目的でも後から資金調達に切り替えられるように(その逆も) ● 新しいサービスドメインを確立させていく ○ 既存サービスドメインと中途半端に混ざらないように 開発における課題

Slide 15

Slide 15 text

Copyright© M&Aクラウド (課題・なぜ)サイト構成 ● 新しいサービスを「サブドメイン」と「パス」のどちらでリリースするか問題 ○ サービスの関連性 ○ ユーザーの認識性 ○ SEOの影響 この判断はエンジニアチームではなく、経営サイドの判断が必要になる 将来的にプラットフォームをどのように拡大させていくのか、事業構想に直結する

Slide 16

Slide 16 text

Copyright© M&Aクラウド ● 後回しにできない仕様決定である ○ サブドメインなら ■ サブドメインによるログインセッション管理 ● 同じアカウントでPFを行き来するため ■ 既存フロントフレームワークへのページ追加だと対応が複雑に(Nuxt.js) ● できなくはないが、基本仕様から離れた実装になる ● 新しくフロントエンド環境を用意することも検討 ○ 新しいデプロイフローが必要になる 弊社は「サブドメイン」にする決定をしたので、サブドメイン採用時の対応内容をまとめていきます! (課題・なぜ)サイト構成

Slide 17

Slide 17 text

Copyright© M&Aクラウド (課題・解決策)サイト構成 サブドメインによるログインセッション管理方法(Laravel) 値をしてしないとnullになるが、 とすることでサブドメインのセッション共通化が行えるようになる 'domain' => env('SESSION_DOMAIN', null), config/session.php APP_NAME=macloud.jp #一緒に変更する必要あり SESSION_DOMAIN=.macloud.jp .env

Slide 18

Slide 18 text

Copyright© M&Aクラウド (課題・解決策)サイト構成 この対応でハマったポイント すでにログイン中のユーザーは古いドメイン(サブドメインじゃない)のCookieを保持しているため、 ログインセッションが狂ってしまう。(Cookie名の重複) そのためCookie名を変更する必要がある。その際にログイン中のユーザーは強制ログアウトされてしま うので注意! 'cookie' => env('SESSION_COOKIE', str_slug(env('APP_NAME', 'laravel'), '_').'_session'), config/session.php APP_NAME=macloud.jp .env

Slide 19

Slide 19 text

Copyright© M&Aクラウド (課題・なぜ)サイト構成 ● 既存フロントフレームワークへのページ追加だと対応が複雑に(Nuxt.js) ● もしも、どちらのサービスにも同じパスが必要になった場合 ○ macloud.jp/offers ○ finance.macloud.jp/offers ● pages配下にoffersディレクトリは1つしか置けないため、実装できない... → /about →/ Nuxt.jsでは/pagesフォルダ構 成によってルーティングが決 まります。

Slide 20

Slide 20 text

Copyright© M&Aクラウド (課題・なぜ)サイト構成 ● 既存フロントフレームワークへのページ追加だと対応が複雑に(Nuxt.js) ○ サブドメインの場合 ■ 基本のルーティング仕様から離れた実装が必要になる ■ ディレクトリを分けて、モジュールでルーティング機能を拡張する ● nuxt-router-module で実装できそうな見込み ○ パスの場合 ■ /AAA/mypage, /BBB/mypageとパスを分けるだけで対応できる

Slide 21

Slide 21 text

Copyright© M&Aクラウド (課題・なぜ)サイト構成 ● 別の課題としては、 ○ ビルドが遅い ○ 一方のチームとコンフリクトする確率が高い ○ FeatureFlagの切り替えが増えるほど、if分岐が増えて実装が面倒になる ■ コードの可読性が下がる ■ リリース後にコードを削除する手間が増える

Slide 22

Slide 22 text

Copyright© M&Aクラウド (課題・解決策)サイト構成 ● せっかくならフロントフレームワークを新しく用意しよう! ○ SSRできること ○ 素早くキャッチアップできること ○ 将来性のあるもの(?) →大事なのは、チームにあったものフレームワークを選択する

Slide 23

Slide 23 text

Copyright© M&Aクラウド (課題・解決策)サイト構成 詳しくはこちら!

Slide 24

Slide 24 text

Copyright© M&Aクラウド ● サイト構成 ○ 「サブドメイン」と「パス」どっちで新サービスを用意するのか ■ この選択で開発作業が大きく変化する ■ finance.macloud.jp OR macloud.jp/finance ● 同じアカウントでどちらのサービスも利用できる ○ M&A目的でも後から資金調達に切り替えられるように(その逆も) ● 新しいサービスドメインを確立させていく ○ 既存サービスドメインと中途半端に混ざらないように 開発における課題

Slide 25

Slide 25 text

Copyright© M&Aクラウド (課題・なぜ)同じアカウントでどちらかのサービスを利用できる ● 売却ユーザーは「M&Aクラウド」、調達ユーザーは「資金調達クラウド」 ○ 売却⇆調達で目的が変わっても同じアカウントで実施したい ○ 調達して、事業を拡大し、最後にM&AでExitできるようにサポートしたい ○ ユーザーがどちらのPFを利用しているのか意識することなく、サービスを利用できる ようにしたい ● 実現させるには ○ 新しいログインステータス ○ 文言の出し分け

Slide 26

Slide 26 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる ● 新しいログインステータス ○ usersテーブルに資金調達ユーザーのフラグ(isFunding)を追加 ■ 資金調達クラウド経由で登録・変更するとフラグがONに ■ 既存の調達ユーザーにもフラグを付与する ○ 売り手サービスドメインのエンティティに値オブジェクト(isFunding)を追加することで、 ログイン中の売り手が調達ユーザーかどうか識別できるように

Slide 27

Slide 27 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる ● 新しいログインステータス M&Aクラウド ユーザー 資金調達クラウド ユーザー isFunding ログイン true false

Slide 28

Slide 28 text

Copyright© M&Aクラウド (課題・なぜ)同じアカウントでどちらかのサービスを利用できる ● 文言の出し分け ○ ログインユーザーのステータスに合わせて表示を切り替える ■ サイトのヘッダー・フッター ■ 登録情報項目 ■ メッセージやメールの内容 ○ ページ ■ (back)middlewareで出し分けするための変数を設定 ■ (front)データをフェッチしてページ内で文言の出し分け ○ メール ■ Mailableを拡張してユーザー毎に出し分け

Slide 29

Slide 29 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる ● 文言の出し分け

Slide 30

Slide 30 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる /** * @var array|string[] * @description ユーザーの資金調達フラグに応じて、資金調達クラウドとして振る舞いたいページを指定する指定はRouteName */ protected array $routeNameCheckedByUser = ['AAAApage','BBBBpage']; /** * @param Request $request */ public function handle($request, ¥Closure $next) { if ($this->isIncludeRouteNameCheckedByUser($request)) { $user = $this->authUserHelper->getUser(); if (!is_null($user)) { $this->viewFactory->share( 'serviceAttribute', new ServiceAttribute($user->isFunding()->rawValue()) ); return $next($request); } } } App/Http/Middleware/SetServiceAttribute.php 一部抜粋した実装イメージ

Slide 31

Slide 31 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる class ServiceAttribute { public function __construct(private readonly bool $isFunding) { } public function isFunding(): bool { return $this->isFunding; } public function getName(): string { return $this->isFunding ? '資金調達クラウド' : 'M&Aクラウド'; } } App/DataTransferObjectServiceAttribute.php 一部抜粋した実装イメージ

Slide 32

Slide 32 text

Copyright© M&Aクラウド (課題・なぜ)同じアカウントでどちらかのサービスを利用できる ● メールの文言の出し分け

Slide 33

Slide 33 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる ● メールの文言の出し分け(サービス毎に情報を出し分けする設計) SellerMailable BaseSellerMailable SellingTargetComplete CreationToSeller Mailable SellerImporta ntMailable SellerMagazin eMailable Laravel 売り手向けメールの 抽象クラス 売り手向けメールの送信 元を定義した 抽象クラス 売り手ユーザーが案件登録し た時に送信するメールのサブ クラス

Slide 34

Slide 34 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる abstract class BaseSellerMailable extends BaseMailable { public function __construct( public ServiceAttribute $serviceAttribute, ) { } public function serviceNamePrefixSubject(string $title): static { return $this->subject("【 {$this->serviceAttribute->getName()}】 {$title}"); } public function serviceNamePostfixSubject(string $title): static { return $this->subject("{$title}【 {$this->serviceAttribute->getName()}】 "); } } BaseSellerMailable.php 一部抜粋した実装イメージ 出力イメージ 【M&Aクラウド】(メールタイトル) 【資金調達クラウド】(メールタイトル)

Slide 35

Slide 35 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる abstract class SellerMailable extends BaseSellerMailable { public function __construct( public ServiceAttribute $serviceAttribute, ) { parent::__construct($serviceAttribute); if ($this->serviceAttribute->isFunding()) { $this->from( config('mail.finance.address'), config('mail.finance.name'), ); } else { $this->from( config('mail.from.address'), config('mail.from.name'), ); } } } SellerMailable.php サービスに合わせてメールの ドメインを変更する 一部抜粋した実装イメージ

Slide 36

Slide 36 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる class SellingTargetCompleteCreationToSeller extends SellerMailable { public function __construct( public AbstractSellingTarget $sellingTarget, public Seller $seller ) { parent::__construct(new ServiceAttribute($seller->getIsFunding()->rawValue())); } public function build(): SellerMailable { $title = $this->serviceAttribute->isFunding() ? '調達情報の入力が完了しました。' : '売却情報の入力が完了し ました。'; return $this ->serviceNamePrefixSubject($title) ->to($this->seller->getEmail()->rawValue()) ->markdown('emails.selling_target.complete_creation.to_seller'); } } SellingTargetCompleteCreationToSeller.php 一部抜粋した実装イメージ メールタイトル、宛先、メー ルテンプレートを用いて生成 する

Slide 37

Slide 37 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる ● メールの文言の出し分け

Slide 38

Slide 38 text

Copyright© M&Aクラウド (課題・解決策)同じアカウントでどちらかのサービスを利用できる この対応でハマったポイント(未来形) ● 技術的負債になっていく ○ 案件を複数化する機能ニーズが高まってきたタイミングで、 負債解消としてリファクタリングが必要になる ■ パーミッションのような扱いにしてもよかった ● パーミッション毎にページを用意する ■ ログイン基盤の作成 売り手情報にisFunding を持たせている

Slide 39

Slide 39 text

Copyright© M&Aクラウド ● サイト構成 ○ 「サブドメイン」と「パス」どっちで新サービスを用意するのか ■ この選択で開発作業が大きく変化する ■ finance.macloud.jp OR macloud.jp/finance ● 同じアカウントでどちらのサービスも利用できる ○ M&A目的でも後から資金調達に切り替えられるように(その逆も) ● 新しいサービスドメインを確立させていく ○ 既存サービスドメインと中途半端に混ざらないように 開発における課題

Slide 40

Slide 40 text

Copyright© M&Aクラウド (課題・なぜ)新しいサービスドメインを確立させていく ● サービス毎の情報差分を分けてそれぞれサービスドメインで表現する ○ 共通部分は継承しつつ、新しい情報は新規サービスドメインで表現 ■ 出資に関する募集情報を参考に

Slide 41

Slide 41 text

Copyright© M&Aクラウド (課題・なぜ)新しいサービスドメインを確立させていく ● 共通部分は継承しつつ、新しい情報は新規サービスドメインで表現 ○ サービスを分けることでサービスドメインの重要な情報が変わる ○ 既存のサービスドメインで対応するとファットクラスになってしまう ■ 2つのサービスで利用するデータを全部持っているクラスが誕生する ○ 一方のサービスで機能改修時に思わぬ事故が発生したりもする ■ 不具合を発生させないような設計にしたい

Slide 42

Slide 42 text

Copyright© M&Aクラウド (課題・なぜ)新しいサービスドメインを確立させていく

Slide 43

Slide 43 text

Copyright© M&Aクラウド (課題・なぜ)新しいサービスドメインを確立させていく ● 共通部分は継承しつつ、新しい情報は新規サービスドメインで表現 Offer 募集情報クラス

Slide 44

Slide 44 text

Copyright© M&Aクラウド (課題・なぜ)新しいサービスドメインを確立させていく ● 共通部分は継承しつつ、新しい情報は新規サービスドメインで表現 BaseOffer 抽象募集クラス AcquisitionOffer InvestmentOffer 買収募集クラス 出資募集クラス

Slide 45

Slide 45 text

Copyright© M&Aクラウド (課題・なぜ)新しいサービスドメインを確立させていく ● 影響範囲があまりも広かったため、一時的に Offer 募集クラス InvestmentOffer 出資募集クラス

Slide 46

Slide 46 text

Copyright© M&Aクラウド (課題・なぜ)新しいサービスドメインを確立させていく ● 影響範囲があまりも広かったため、一時的に ○ これも技術的負債をいつ返すのかというお話し ■ 1. 作ったらすぐに返済する ■ 2. 次の改修前に返済する ● 普段の業務効率を著しく悪くするなら1のパターン ● そうでなければ2のパターン

Slide 47

Slide 47 text

Copyright© M&Aクラウド ● 技術的に負債について ○ 財務的に利息と元本が投資の利回りよりも低ければ、負債はポジティブ ○ ソフトウェアでも同じことが当てはまるはず 開発における課題と解決策 最初に市場へ出るために内部品質を犠牲にしたとしても、より良い内部品質で後から市場に出た 場合よりも、この決定によって得た金額が高ければ、利益を生み出します。 しかし、ある程度不確かなことがあるため、そのような利益を予め見積もるのは難しいので、こ こにはリスクがあると言えます。 引用:InfoQ(技術的負債を管理する)

Slide 48

Slide 48 text

Copyright© M&Aクラウド 本日のおさらい 48

Slide 49

Slide 49 text

Copyright© M&Aクラウド まとめ ● 「サブドメイン」か?「パス」か? ○ 両方に対してのメリット・デメリットを用意する ● 同じアカウントでPFを行き来できるようにする ○ 「サブドメイン」か「パス」かで難易度が変わってくる ○ 「サブドメイン」は対応できるのか、できるとしてどれくらいかかるのか調査 ○ 既存Viewの出し分けは、多少の辛さを覚悟しておく(メール大変だった。。。) ● 新しいサービスドメインを確立させていくには ○ クラス設計においては、適宜分けていく ● 技術的負債と投資のバランス、負債の返済タイミング

Slide 50

Slide 50 text

Copyright© M&Aクラウド 参考資料 ● https://tech.macloud.jp/entry/2022/08/30/122131 ● https://ymmooot.dev/articles/3/ ● https://www.infoq.com/jp/articles/managing-technical-debt/

Slide 51

Slide 51 text

Copyright© M&Aクラウド ありがとうございました! 51