くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

オンライン決済サービスをリリースしました

こんにちは、バックエンドエンジニアの @akira です。
今回は8月12日にリリース致しました、オンライン決済サービスの開発について振り返ってみようと思います。

今まではくらしのマーケットの支払い方法として現金のみ利用可能でしたが、オンライン決済サービスがリリースされたことでクレジットカードも選択できるようになりました。オンライン決済利用可能店舗は順次拡大しています。

くらしのマーケットにオンライン決済を導入することの難しさ

くらしのマーケットでは、以下 4 つのフェーズが主に存在します。

  1. ユーザーが予約を入れる
  2. 出店者が作業日を確定する
  3. 作業日当日、出店者がサービスを提供する
  4. 出店者がお会計をする

今回のリリースにより、ユーザー側の予約キャンセルも可能になりました。ユーザーのキャンセルはキャンセル料金が発生するケースもあり、そのキャンセル料金を出店者が回収できることが今回リリースしたオンライン決済のメリットです。

作業日当日にオプション作業を追加したり、事前に見積もっていた作業が不要になるケースもあり、予約時の金額とお会計の金額が異なるケースがあります。もし出店者がお会計の金額を誤った場合、金額の訂正を社内システムから行うこともできます。

これらの整合性を取りつつ、カード決済を導入することは非常に複雑で大変でした。

Technology Stack

今回リリースしたオンライン決済サービスの技術スタックは、以下の通りです。

  • TypeScript
  • NestJS
  • TypeORM
  • PostgreSQL
  • Domain-Driven Design
  • Clean Architecture
  • Docker
  • AWS Fargate
  • Stripe

言語は

  • 社内で長らく使ってきた言語
  • 型付き言語

の二点から TypeScript を選択しました。次点としては Golang などもありました。

また、今回初めて Domain-Driven Design(ドメイン駆動設計、DDD)を採用しました。DDD については後ほど詳しく触れます。

決済代行会社は Stripe を利用しています。

アプリケーションは AWS Fargate 上にデプロイして稼働しています。

DDD を 0 から理解する

私は昨年9月にみんマに入社したのですが、社内システムのバグを少し修正した後で今回のプロジェクトに参画しました。ちょうどその頃、オンライン決済のユーザー側開発がスタートしたばかりであり、私が最初に取りかかったタスクは、既存機能の一部を DDD を用いてアプリケーションコードのみリプレイスすることでした。

結果として、このリプレイスは失敗しました。この失敗談は、6月10日に開催しました「失敗に学べ!くらしのマーケットの開発「失敗」LT 会【オンライン/入退出自由】vol.1」にて発表しました。

minma.connpass.com

スライドは Speaker Deck にありますので、ご覧ください(趣旨:DB スキーマをリプレイスせずにアプリケーションコードのみ DDD で書き直すのは、DDD の考え方に反するものであり、アンチパターンである)。

speakerdeck.com

リプレイスに失敗後、本格的に DDD を学び直すことを決意し、Eric Evans 著書の『Domain-Driven Design: Tackling Complexity in the Heart of Software』(原著)を読破しました。このインプットにより、DDD の理解が非常に明確になりました。

ドメインの理解を深める

DDD を理解し、開発スピードが上がってきた一方で、ドメインの理解不足が顕著になってきました。DDD ではビジネスドメインの理解が必須になります。このビジネスドメインにはさらに二つ意味があると個人的に思っていて、

の双方を理解する必要がありました。

「ビジネスドメインそのもの」については、チームの PdM に詳しい仕様などを確認することができましたが、「ビジネスドメインが扱う領域のドメイン」(オンライン決済・クレジットカードの仕組み)については、きちんと理解していませんでした。

そこで書籍『カード決済業務のすべて―ペイメントサービスの仕組みとルール』を購入し、チームで回し読みすることで理解を深めました。この理解が後ほどオーソリの各種操作を実装する際に役立ちました。

DDD をチームに浸透させる

本格的に DDD でチーム開発を進めるため、チームの PdM や QA メンバーと打ち合わせを行い、認識をすり合わせました。大まかに以下の内容を共有しました。

  • ビジネスドメインの知識を常にアップデートすること
  • ビジネス用語ではなく、ユビキタス言語を使って開発者と会話すること
  • 開発者がビジネスドメインについて間違った理解を示していた場合は、必ず指摘すること

上記は基本的に普段の業務とかけ離れたものではないため、そこまで違和感もなかったのではないかと思っています。ただし、二点目のユビキタス言語を使って会話する部分は、開発を進めていくと大変だったように思います。

DDD で開発し始めた頃は、ビジネスエキスパート( PdM や QA メンバー)と開発者でそれぞれ違う単語で同じ事象を説明することが多く、それによって仕様の理解が異なってしまうことが多々ありました。後半になると、自然と認識の擦り合わせを積み重ねていたためか、ほとんど発生しなくなりました。


ここまでの技術的な詳細については、3月末に公開しました記事「DDD 開発をしてみての振り返り」に書かれていますので、合わせてご覧ください。 tech.curama.jp


主要機能を実装

ドメインモデルの数多くのリファクタリングを経て、ビジネスロジックの実装フェーズに入りました。この時は「ビジネスドメインが扱う領域のドメイン」(オンライン決済・クレジットカードの仕組み)についてはある程度知識がありましたが、一方で実装対象であるビジネスドメインそのものについては理解不足でした。

フローチャートを活用して、チームメンバー全員で処理フローを徹底的に理解したことと、仕様を理解しているかの確認を行う目的で、あまり詳しくないと実感している機能の仕様を説明し合う場を設けることで、ビジネスドメインそのものについての理解を深めました。

また、Stripe API についての理解も不足しており、ひたすらドキュメントを読んで API を実行し続けました。

Stripe API の制約を、 Clean Architecture のインフラ層とドメイン層のどちらに実装するかは難しかったですが、API を call することでチェックできるものはインフラ層、それ以外のものはドメイン層に実装しました。

ビジネスドメインの知識や Stripe の知識が揃ってきたタイミングで、主要機能の実装スピードも上がってきました。

DDD で気をつけるべきこと

無事オンライン決済サービスを DDD で実装することができたのですが、いくつか反省点がありましたのでお伝えしたいと思います。

ユビキタス言語が定まらないと、永遠にリファクタリングが続く

DDD は、ビジネスエキスパートと開発者の間で定義したユビキタス言語をそのままコードとして実装する設計手法です。そのため、開発を始める段階で、ある程度ユビキタス言語が定まっている必要があります。逆にいつまでもユビキタス言語に揺らぎがあると、それだけコードも常にリファクタリングし続けなければなりません。

よって、全てのユビキタス言語をきちんと定義した上で DDD の開発に着手するのが理想です。その状態でスタートできれば、ドメインモデルの再設計(リファクタリング)の頻度を減らすこともでき、DDD 開発のスピードアップが見込めるでしょう。

現実的には、 DDD を採用する前にユビキタス言語を整理し、将来的にどの程度変更される可能性があるかを事前に把握しておくと、ドメインモデルの設計段階で「このユビキタス言語が変更になる可能性があるので、このようにリファクタリングされるかも」などと想定することができます。

事前にビジネスドメインをよく理解しておく

DDD はユビキタス言語が変更されたタイミングで、ドメインモデルも即座にリファクタリングすることを推奨しており、初期の段階ではこれに従っていたのですが、後になればなるほどリファクタリングのコストが大きくなっていくのを痛感しました。

リファクタリングしなければいけないことに変わりはないため、できる限り初期のうちに完成形を目指してドメインモデルを設計すべきです。そのためには、ラフなドメインモデルを設計するタイミングでビジネスドメインについてある程度詳細な知識が必要でした。今回はそれが不足していたため、後半に入っても必要に応じて大胆にリファクタリングを実施してきました。

学習コスト

DDD に則った開発は、その概念(Strategic Design)から実装方法(Tactical Design)まで一定の形式に従う必要も出てきます。プログラミング言語であれば Object Oriented Programming が可能な言語、アプリケーションアーキテクチャーであれば Layered Architecture (あるいはその発展形)を採用することも推奨されています。

ある程度キャリアのあるエンジニアであれば、それらを学習してアウトプットすることは問題ないかもしれないですが、メンバーの中には比較的キャリアの浅いエンジニアもおり、その方々の学習コストをあまり考慮できていませんでした。

また、 DDD のコードをレビューできるメンバーが自チームのみ限られてしまう、といった事象も発生し、学習コストの高さを感じました。

DDD のメリット

一方、DDD を採用したことで大きなメリットもありました。

コードがとにかく綺麗

DDD と Clean Architecture が非常に相性が良く、コードが非常に分かりやすいものとなりました。

ドメインモデルをリファクタリングする際も、基本的にドメイン層だけ変更すれば良いですし、バグが見つかった場合の修正も比較的容易でした。

ビジネスドメインをきちんと理解した上で開発できる

これはむしろ DDD の恩恵そのものと言えますが、ユビキタス言語をそのままコードとして実装するため、ビジネスドメインを理解した上で開発することができます。

特に今回は、前述の「ビジネスドメインが扱う領域のドメイン」(オンライン決済・クレジットカードの仕組み)まで理解した上でコードをかけたのが一番良かったです。

DDD はクックブックではない

最後に、DDD の開発を一通り経験して個人的に主張しておきたいことがあります。それは「DDD はクックブックではない」ということです。

DDD の価値はその開発プロセス自体にあり、上部だけを擬えてもあまり効果は出ないと私は思います。

『Domain-Driven Design: Tackling Complexity in the Heart of Software』が出版されたのは 2003 年ですから、DDD を採用し始めた頃は「これってちょっとレガシーな設計手法なのでは」と正直思ってしまったこともありました。

しかし、DDD は概念の一種であるため、いつまでも応用ができます。特に Strategic Design の部分は、どれだけ技術が移り変わっていっても不変ではないでしょうか。

一方で、 Tactical Design の部分は時代とともにアップデートされていくでしょう。その一例として、昨今流行の Microservice Architecture があります。

よく「一マイクロサービスは、一つの境界づけられたコンテキストに相当する」といった考え方を見受けますが、Eric Evans 氏はこの考え方に警鐘を鳴らしています。

prescriptive guidance such as "each microservice is a bounded context" approaches the "cookbook" end of the spectrum, and diverges from the sweet spot of DDD.

「こうすればできる」といった方法論はクックブックに過ぎず、そのやり方では DDD の真価を発揮することはできないでしょう。そのため、本格的に DDD で開発をするのであれば、DDD を根本から理解することから始めるべきだと私は思います。