Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Djangoモノリスからのサービス分割:FastAPIへの移行
Search
sara.ohtani.mt2
September 09, 2026
Programming
37
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Djangoモノリスからのサービス分割:FastAPIへの移行
sara.ohtani.mt2
September 09, 2026
More Decks by sara.ohtani.mt2
See All by sara.ohtani.mt2
大人数会議のカオス化を防ぐふりかえりフレームワークを考えてみた
smatsu
0
680
【供養】DynamoDBでも部分一致検索したかった
smatsu
1
1.9k
サンプル発話からVUXを考える
smatsu
0
110
Other Decks in Programming
See All in Programming
片田舎のおっさん、 Swift Buildのダイアモンド問題解決の不具合修正PRを出すが、解決方法がキャッシュをしないようにすることであり、ビルド時間が伸びると言われてマージされないので高速化もする/swiftbuild
yimajo
0
360
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
1
150
XHTMLが残したもの
yosuke_furukawa
PRO
2
430
Go を使い始めて 2 ヶ月の学び / My first two months with Go
contour_gara
0
420
RSSとCodexを使ってX投稿自動化してみた
ochtum
0
110
Deep dive into the select statement (GopherCon UK)
jespino
0
170
MIZARU@SPAJAM2026 第二回予選
1901drama
0
110
iOS開発×AI駆動開発 〜最近使って便利だったスキルの話〜
nogu66
0
140
新卒PdEのリアル
ryu1013
1
420
Vibes Containers 〜AIで変わるコンテナ設計と運用〜
tkikuc
2
500
Discordを用いたラボオートメーション関連情報収集の自動化
noguhiro2002
0
510
【DroidKaigi 2026】「アクセシビリティを利用するとき、 アクセシビリティもまたこちらを利用している」 〜マルウェアによる攻撃と防衛について〜
halunoyo
0
410
Featured
See All Featured
XXLCSS - How to scale CSS and keep your sanity
sugarenia
249
1.3M
Test your architecture with Archunit
thirion
2
2.4k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
How to Think Like a Performance Engineer
csswizardry
28
2.8k
For a Future-Friendly Web
brad_frost
183
10k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
1
260
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
360
30k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6.2k
Introduction to Domain-Driven Design and Collaborative software design
baasie
1
970
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
590
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
What's in a price? How to price your products and services
michaelherold
247
13k
Transcript
巨大Djangoモノリスから1機能を切り出す FastAPI移行の現場と判断 大谷 紗良 2026.09.09 PyCon JP 2026 アフターイベント 〜非公式リジェクトコン〜
Copyright © RevComm Inc.
自己紹介 • 大谷 紗良(Sara Ohtani) • RevComm Inc. ◦ Software
Engineer • @sara_ohtani_mt2 バックエンド開発とか組織づくりとか 2 Copyright © RevComm Inc.
今日の話 Djangoモノリスからサービス分割し、FastAPIに書き換えた事例紹介 実際の現場で考えたこと、ハマったことを共有し 今後の参考になればと思います 3 Copyright © RevComm Inc.
プロジェクト概要 2023年5月スタート。段階的に切り替え・拡張していて、現在も一部続いています 2023.05 2023.12 2024〜 現在 プロジェクトスタート チーム立ち上げ 移行の第一段階リリース (app分割)
段階的に切り替え・拡張 DB分割は継続中… メンバーは入れ替わりつつ、2〜6人 大谷はバックエンドエンジニアとして参加 Copyright © RevComm Inc.
1. サービス全体の内容と規模感と課題 2. 課題解決のための「サービス分割」という判断 アジェンダ 3. どこで分割するか 4. フレームワークの変更 5.
ORMの変更でハマったこと 6. 移行してみた結果 Copyright © RevComm Inc.
1. サービス全体の内容と規模感と課題 2. 課題解決のための「サービス分割」という判断 アジェンダ 3. どこで分割するか 4. フレームワークの変更 5.
ORMの変更でハマったこと 6. 移行してみた結果 Copyright © RevComm Inc.
どんなサービス? ビジネス音声コミュニケーション 録音・録画 気づき 電話 ビッグデータ化・資産化 商談の蓄積 商談状況の把握・共有 商談成功・失敗要因の特定 業務の効率化
Web会議 文字起こし トーク解析 感情認識 出現単語 トレンド分析 ダッシュボード(統計表示) 対面での 会議 7 AI解析 生成AI 議事録作成 議事録の自動作成 営業活動の分類・共有 コミュニケーションスキル向上 スキルレベルの可視化 セルフコーチングによる成長 Copyright © RevComm Inc.
Language and tools Copyright © RevComm Inc.
Language and tools 今日話すところ Copyright © RevComm Inc.
Language and tools 今日話すところ Copyright © RevComm Inc.
巨大モノリス 電話・Web会議・対面会議のAI解析B2Bサービスのバックエンド • 複数サービスのAPIが大量に同居(約600件) • AIによるとテストコード抜きでも数十万行 11 Copyright © RevComm
Inc.
課題:サービスの拡大に伴う認知負荷の増大 変更が何に波及するか、全体を見渡すのが難しい コードの側 組織の側 • 約600APIが1リポジトリに同居 • 担当チームは分割されて増えていく • 変更の波及範囲が読めない
• メンバーも増えていく • 触るのが怖い箇所が増える • 「誰が何を知っているか」も分散 Copyright © RevComm Inc.
さらに 2017年のfirst commitから約5年経ち、2023年・・・ 13 Copyright © RevComm Inc.
サービス規模は日々拡大・・・ (※2025年3月時点) 14 Copyright © RevComm Inc.
巨大モノリス、の老朽化 2017年のfirst commitから約5年経ち、2023年・・・ パフォーマンスなどの課題から 大幅なリニューアルが必要なところも出てきた 15 Copyright © RevComm Inc.
巨大モノリス、の老朽化 2017年のfirst commitから約5年経ち、2023年・・・ パフォーマンスなどの課題から 大幅なリニューアルが必要なところも出てきた どうせリニューアルするなら しようよ!「分割」ッ! 16 Copyright ©
RevComm Inc.
1. サービス全体の内容と規模感と課題 2. 課題解決のための「サービス分割」という判断 アジェンダ 3. どこで分割するか 4. フレームワークの変更 5.
ORMの変更でハマったこと 6. 移行してみた結果 Copyright © RevComm Inc.
他の手段はなかったか? もちろん分割しない選択肢もある モノリスのまま整理 別サービスに分割 内部のモジュール境界を明確にする デプロイ単位は変わらない デプロイ・DB・チームの単位を分ける Copyright © RevComm
Inc.
マイクロサービスのデメリット 別サーバー・別DBとの通信が増える レスポンス速度がむしろ下がるかも Django → マイクロサービス のネットワーク越し呼び出しが増える DBも分ければさらに1ホップ パフォーマンス改善は他の取り組みと合わせて検討する必要がある 分割=速くなる、ではない
クエリ見直し・API再設計などと合わせて改善見込みを立てる Copyright © RevComm Inc.
モノリス→マイクロサービス化が適さないと思うケース • すでに巨大すぎる・複雑すぎる(分解=破壊) • パフォーマンス改善の見込みがなく、悪化を許容もできない • 強い整合性・トランザクションが頻繁に必要 • 新しくできたばかりでドメイン境界が固まっていない •
全体のチーム・組織規模が小さい、あるいは巨大すぎて 分割する方が運用コストがかかる Copyright © RevComm Inc.
モノリス→マイクロサービス化が適さないと思うケース • すでに巨大すぎる・複雑すぎる(分解=破壊) • パフォーマンス改善の見込みがなく、悪化を許容もできない • 強い整合性・トランザクションが頻繁に必要 • 新しくできたばかりでドメイン境界が固まっていない •
全体のチーム・組織規模が小さい、あるいは巨大すぎて 分割する方が運用コストがかかる Copyright © RevComm Inc.
マイクロサービス化の判断が適さないと思うケース • すでに巨大すぎる・複雑すぎる(分解=破壊) • パフォーマンス改善の見込みがなく、悪化を許容もできない • 強い整合性・トランザクションが頻繁に必要 • 新しくできたばかりでドメイン境界が固まっていない •
全体のチーム・組織規模が小さい、あるいは巨大すぎて 分割する方が運用コストがかかる 今回は当てはまらなかった Copyright © RevComm Inc.
1. サービス全体の内容と規模感と課題 2. 課題解決のための「サービス分割」という判断 アジェンダ 3. どこで分割するか 4. フレームワークの変更 5.
ORMの変更でハマったこと 6. 移行してみた結果 Copyright © RevComm Inc.
どこで分割? ビジネス音声コミュニケーション 録音・録画 気づき 電話 ビッグデータ化・資産化 商談の蓄積 商談状況の把握・共有 商談成功・失敗要因の特定 業務の効率化
Web会議 文字起こし トーク解析 感情認識 出現単語 トレンド分析 ダッシュボード(統計表示) 対面での 会議 24 AI解析 生成AI 議事録作成 議事録の自動作成 営業活動の分類・共有 コミュニケーションスキル向上 スキルレベルの可視化 セルフコーチングによる成長 Copyright © RevComm Inc.
どこで分割? 連絡先管理機能 ビジネス音声コミュニケーション 録音・録画 気づき 電話 ビッグデータ化・資産化 商談の蓄積 商談状況の把握・共有 商談成功・失敗要因の特定
業務の効率化 Web会議 文字起こし トーク解析 感情認識 出現単語 トレンド分析 ダッシュボード(統計表示) 対面での 会議 25 AI解析 生成AI 議事録作成 議事録の自動作成 営業活動の分類・共有 コミュニケーションスキル向上 スキルレベルの可視化 セルフコーチングによる成長 Copyright © RevComm Inc.
連絡先管理機能 • 比較的シンプルな機能だった ◦ 会社名・担当者名・電話番号・カテゴリーの管理 ◦ 発信・着信時などに名前を表示したり ◦ CRMではない •
他機能への影響が小さかった ◦ 参照 ◦ 障害発生時の他機能への影響 • パフォーマンス面で課題があり、リニューアルしたかった ◦ レスポンススピードUP ◦ 登録可能上限数UP 26 Copyright © RevComm Inc.
連絡先管理機能 • 比較的シンプルな機能だった ◦ 会社名・担当者名・電話番号・カテゴリーの管理 ◦ 発信・着信時などに名前を表示したり ◦ CRMではない •
他機能への影響が小さかった ◦ 参照 ◦ 障害発生時の他機能への影響 • パフォーマンス面で課題があり、リニューアルしたかった ◦ レスポンススピードUP ◦ 登録可能上限数UP これ自体を特に分割したいというよりは社内の先行事例にしやすかった 27 Copyright © RevComm Inc.
まずはアプリケーション層から 旧APIも残して1本ずつ切り替え。問題があればすぐに旧APIに戻せるように DBはそのあと 1 旧APIは残す 2 1本ずつ切り替え 3 すぐ戻せる Django側のエンドポイントは
消さない 呼び出し元を新APIへ順に向ける 問題があれば呼び出し先を 戻すだけ Copyright © RevComm Inc.
構成 Before / After Before 連絡先テーブルを直接参照 Client Django Client Django
PostgreSQL After 連絡先管理API PostgreSQL ▲ ここが増えた Djangoから連絡先テーブルへの直接参照はなくなり、連絡先APIを経由するようになった ※ Django自身のDB接続は、連絡先以外のテーブル向けに残っている Copyright © RevComm Inc.
仕様を整理してテストコードを整備 テストコードも仕様書もなかったので・・・ コードから現在の仕様を読み解き、基本的には今、動いている通りに移行 テストコードを整備 比較的シンプルな機能であることで助かった 30 Copyright © RevComm Inc.
とにかくPRは小さく 変更すべき仕様はPdMと相談しながら、できるだけ別PRに分けて開発 ちょっとした機能であるために後手に回っていた問題に対応できた リファクタリングもこまめに行なったが、とにかくPR・フェーズを分ける 31 Copyright © RevComm Inc.
テストコードの維持とPRを小さくする文化を育てる 仕組みと文化の両軸が大事 • チームでWorking Agreementを設定 • チームのふりかえりの中で意識できているか話し合う チーム分割のメリット! 文化は小さいところからの方が育てやすい 32
Copyright © RevComm Inc.
1. サービス全体の内容と規模感と課題 2. 課題解決のための「サービス分割」という判断 アジェンダ 3. どこで分割するか 4. フレームワークの変更 5.
ORMの変更でハマったこと 6. 移行してみた結果 Copyright © RevComm Inc.
フレームワーク選定 選択肢A:Djangoのまま切り出す • 元コードからの変更が少なくできる • 多機能すぎて連絡先には不要な機能がある • 既存のものより速くするのは難しい 34 Copyright
© RevComm Inc.
フレームワーク選定 選択肢A:Djangoのまま切り出す 選択肢B:別のフレームワークへ移行 • 元コードからの変更が少なくできる • 移行コストがかかる • 多機能すぎて連絡先には不要な機能がある •
必要な機能に絞って軽量化できる • 既存のものより速くするのは難しい 35 • 高パフォーマンスを強みとしている Copyright © RevComm Inc.
フレームワーク選定 選択肢A:Djangoのまま切り出す 選択肢B:別のフレームワークへ移行 • 元コードからの変更が少なくできる • 移行コストがかかる • 多機能すぎて連絡先には不要な機能がある •
必要な機能に絞って軽量化できる • 既存のものより速くするのは難しい • 高パフォーマンスを強みとしている FastAPI × SQLAlchemy 36 Copyright © RevComm Inc.
ちなみに:FastAPIのパフォーマンス ベンチマークでの比較 2025-02-24Round 23 https://www.techempower.com/benchmarks/#section=data-r23&f=zik0zb-zik0zj-zik0zj-zik0zj-zik0zj-zik0zj-zik0zj-ziimf3-zik0zj-zik0zj-zik0zj-zik0zj-zik0zj-zik0zj-zik0zj-jz6rj 37 Copyright © RevComm Inc.
FastAPI以外の選択肢と学習コスト • Flaskなどの他のフレームワークはほぼ検討しなかった • 学習コストは会社全体を対象に考える ◦ 弊社の場合はチーム間の人移動がカジュアルにある ◦ 社内で導入事例があると具体的に相談もしやすい ◦
弊社では他のチームでFastAPIを利用している実績があった • 現在はGoなども一部で導入しているが、この2023年時点では未導入 ◦ 新規での開発ではなく、提供済み機能の分割移行なので 新言語の採用は避けた 38 Copyright © RevComm Inc.
FastAPIでよくあるディレクトリ構成 ディレクトリ構成の“公式”はない。Djangoのstartappコマンドのようなものもない Type別構成 app/ ├── routers/ ├── models/ ├── schemas/
└── services/ 技術的な種類でディレクトリを分ける ドメイン別構成 app/ ├── contacts/ │ ├── router.py │ ├── models.py │ └── schemas.py └── users/ 機能単位で分ける。将来のサービス分割と相性が良い Copyright © RevComm Inc.
今回はあえて移行前の構成に寄せた Django時代の views / models / serializers の分離をベースに構成 Django(移行前) src/{project}/
├── views/api/{domain}/v1.py ├── models.py └── serializers/ FastAPI(移行後) app/ ├── domain/values/ ├── models/ ├── repositories/ ├── serializers/ ├── use_cases/{domain}/ └── views/ 結果的にクリーンアーキテクチャ寄りの構成に Copyright © RevComm Inc.
なぜ移行前の構成に寄せたか 移行期における人間にとっての認知負荷の低さを優先した 2023年の判断 移行期は「どこに何があるか」が分かることが最優先 Django時代の構成に寄せて認知負荷を下げる 他チーム含めた、人間にとっての読みやすさを優先 今ならどうするか AIの理解しやすさを優先 FastAPIによくある形(ドメイン別)にしたと思う 「よくある形」はAIにとっても読みやすい
Copyright © RevComm Inc.
Django→FastAPI書き換え例① 同期 / 非同期 WSGIの同期前提から、ASGIの async / await 前提へ Django:同期
FastAPI:非同期 def get_all(request): records = BusinessContact.objects.filter(...) total = records.count() # すべて同期呼び出し async def execute(cls, tenant_id, phone_number): async with async_db_session(...) as session: result = await IsvizContactRepository() .filter_in_phone_numbers(session=session, ...) WSGI・同期が前提 ASGI・非同期が前提 書き方はシンプル async def / async with / await を一貫させる 1つ同期を混ぜるとイベントループを塞ぐ Copyright © RevComm Inc.
Django→FastAPI書き換え例② リクエストの型付けとバリデーション 検証コードを自分で呼び出す形から、型ヒントだけで配線まで揃う形へ Django:手動配線 class ContactSerializer(serializers.Serializer): name = serializers.CharField() phone_number
= serializers.CharField() def create_contact(request): s = ContactSerializer(data=request.data) s.is_valid(raise_exception=True) data = s.validated_data 宣言的なSerializerは書けるが、 is_validを呼び出す配線は自分で書く 呼び忘れると検証されないまま処理が進む FastAPI:配線も自動 class ContactItem(BaseModel): name: str | None phone_number: str | None @field_validator("name", mode="before") def trim_space(cls, v: str) -> str: return v.strip() 型ヒントで検証・パース・エラー形式生成 APIドキュメントも自動生成 Copyright © RevComm Inc.
1. サービス全体の内容と規模感と課題 2. 課題解決のための「サービス分割」という判断 アジェンダ 3. どこで分割するか 4. フレームワークの変更 5.
ORMの変更でハマったこと 6. 移行してみた結果 Copyright © RevComm Inc.
ORMの変更 フレームワークの変更に伴って ORMもDjango ORMからSQLAlchemyになった FastAPIそのものへの書き換えより戸惑うところがあったので紹介 45 Copyright © RevComm Inc.
ハマり①:idが取れない! 新規連絡先を作成するとき、同時に新規カテゴリーを作成し紐づけて保存すると カテゴリー自体は作られるのに作成した連絡先には紐づいていない現象 46 Copyright © RevComm Inc.
ハマり①:idが取れない! 新規連絡先を作成するとき、同時に新規カテゴリーを作成し紐づけて作成すると カテゴリー自体は作られるのに作成した連絡先には紐づいていない現象 ある! 47 ない! Copyright © RevComm Inc.
ハマり①:原因は add → flush → commit の流れ execute() の返り値からIDを取ろうとしたが、flush() 前なのでIDがまだ採番されていなかった
add() flush() セッションに登録するだけ SQLはまだ発行されない SQLが発行され PKが採番される commit() トランザクション確定 SQLAlchemy Django ORM 3段階。flush 前は ID が None save() で即DB反映。この感覚で書いていた Copyright © RevComm Inc.
ハマり①:NG と OK execute() の返り値ではなく、add() したインスタンス自身から ID を取る NG #
NG: execute() の返り値からIDを取ろうとする result = await session.execute( insert(Category).values(name="新規カテゴリ") ) category_id = result.inserted_primary_key # → この時点ではflushが反映されておらず # None になる OK # OK: add() したインスタンス自身から取る new_category = Category(name="新規カテゴリ") session.add(new_category) await session.flush() # ↑ ここでSQLが発行され、PKが採番される category_id = new_category.id # → インスタンスに直接反映されている Copyright © RevComm Inc.
ハマり②:INSERT のはずが read レプリカ向けの接続に get_bind() でクエリ種別ごとに接続先を切り替えていたが、write と認識されずエラー primary(write) session.execute(...) get_bind()
で判定 read レプリカ ← INSERT insert() なのに write と判定されなかった Copyright © RevComm Inc.
ハマり②:NG と OK insert(...).values(**data) の形にすると write と認識される NG OK #
NG: values() を使っていない await session.execute( insert(Contacts).returning(Contacts), insert_contact_data, ) # OK: insert() で values() を使っている await session.execute( insert(Contacts) .values(**insert_contact_data) .returning(Contacts) ) # → Error cannot execute INSERT in a read-only transaction 「values を使っているか」に気をつけ続けるのはつらい → session.add() へ Copyright © RevComm Inc.
結論:session.add() を使う _flushing で確実に write 判定される。ORM のオブジェクト関係に任せてまとめて add() できる session.add()
new_category = Category(name="新規カテゴリ") new_contact = Contacts(name="山田", phone_number="03...") new_contact.categories.append(new_category) session.add(new_contact) await session.flush() await session.commit() # カテゴリーもまとめて # write 判定 → primary add() のメリット • _flushing で確実に write 判定 • values を使っているか気をつけなくてよい • ORMのオブジェクト関係に任せて まとめて add()できる • ハマり①の問題のためにもadd()の方がいい Copyright © RevComm Inc.
1. サービス全体の内容と規模感と課題 2. 課題解決のための「サービス分割」という判断 アジェンダ 3. どこで分割するか 4. フレームワークの変更 5.
ORMの変更でハマったこと 6. 移行してみた結果 Copyright © RevComm Inc.
成果 1. パフォーマンス改善 2. デプロイ頻度の向上 3. テストコードの整備 4. 認知負荷の低下(?) 54
Copyright © RevComm Inc.
1. パフォーマンス改善 APIやデータ登録状況にもよるが、かなりの改善が見られた ただし一番クリティカルな効果があったのはAPI分割とクエリの見直し • EXISTSではなくJOINを使うようにする • JOINしたまま検索せず、取得する連絡先のidを絞ってからJOINする • 多対多のテーブルをJOINしないでいいユースケース用にAPIを分ける
など 55 Copyright © RevComm Inc.
1. パフォーマンス改善 テナントあたりの連絡先登録数に応じて遅くなっていたレスポンスが改善し 連絡先登録数の上限をあげることができた ※検証時点、 APIやデータ条件にもよる 56 Copyright © RevComm
Inc.
2. デプロイ頻度の向上 複数チームが開発したものをまとめてリリースするのではなく 1チームで開発したものに絞られているので調整が楽になる ↓ 57 Copyright © RevComm Inc.
2. デプロイ頻度の向上 複数チームが開発したものをまとめてリリースするのではなく 1チームで開発したものに絞られているので調整が楽になる ↓ 週1固定曜日リリースから、週複数回リリースができるようになった ↓ 58 Copyright ©
RevComm Inc.
2. デプロイ頻度の向上 複数チームが開発したものをまとめてリリースするのではなく 1チームで開発したものに絞られているので調整が楽になる ↓ 週1固定曜日リリースから、週複数回リリースができるようになった ↓ 小さくリリースできて影響範囲の把握もしやすくなった🎉 59 Copyright
© RevComm Inc.
3. テストコードの整備 ドキュメントも必要だし書いてはいるけど、けっきょくはテストコード • どう動くべきかの担保をすることができる ◦ なぜこう動くかはコメントやドキュメントが必要 • 自然言語特有の誤解がない ◦
AIも誤解しない 60 Copyright © RevComm Inc.
4. 認知負荷の低下(?) • 分離=無関心、ではない • ただ、使われ方を整備したことで処理の流れがわかりやすくなった • 最低限、連絡先API内のことはチームで確実に把握できている安心感もある • 利用箇所は日々増えているし、機能も基本的には増える
◦ 想定外の利用をされないようにバリデーションが重要 61 Copyright © RevComm Inc.
4. 認知負荷の低下(?) • 分離=無関心、ではない • ただ、使われ方を整備したことで処理の流れがわかりやすくなった • 最低限、連絡先API内のことはチームで確実に把握できている安心感もある • 利用箇所は日々増えているし、機能も基本的には増える
◦ 想定外の利用をされないようにバリデーションが重要 ◦ FastAPIはPydanticによるバリデーション・型ヒントも特徴 62 Copyright © RevComm Inc.
残る課題 アプリを分割したことについては、やった甲斐があった DBの分割はまだこれからなので手探り • 他のDBと紐づけたいデータが多いとつらそう ◦ 現状は多くはないが・・・ • 分割したらテーブルの再設計もしたい ◦
色々と要望があるので、それに対応できるものにしていきたい ◦ 連絡先、奥が深い・・・(会社と担当者の紐づけ、電話番号、email…) このプロジェクトを参考にビデオ会議のAPIを分割するプロジェクトも進行中 63 Copyright © RevComm Inc.
Thank you! 64 Copyright © RevComm Inc.