こんにちは。NEWT 1st ANNIVERSARY CALENDAR 7日目担当の高橋友貴です!
この記事では、この1年でNEWT iOSにどんな変化があったか、どんなことを技術的に考えたかについてお伝えできればと思います。
自己紹介
前職からiOSエンジニアとして働いており今年で5年目となります。令和トラベルとの出会いは2021年4月頃で、フルタイムメンバーとして入社する以前からPP※としてジョインして創業時から開発のお手伝いをしていました。そして昨年2022年の11月に正社員として入社。自分の全く知らない世界でチャレンジしたいという思いから旅行業界に飛び込みました!
※ 令和トラベルでは業務委託の方をプロパートナーと呼んでいます(以下、PP)
はじめに
海外旅行予約アプリ『NEWT(ニュート)』はこのたび1周年を迎えました🎉
この1年、エンジニアとして様々な機能開発・改善を行なってきました。機能開発が大きく進む中でも、技術的な変化や課題に向き合っていかなければなりません。そこでこの1年、iOSチームがどんなことを考えていたかお伝えできればと思います。
1年前のNEWT iOSについてはぜひこちらをご覧ください。
チーム体制について
iOSチームの体制は以下のように変化しました。(2023/4/1時点)
- FT: 1名 → 2名 + Web兼務1名
- PP: 3名 → 1名 + Android兼務1名
私を含めて2名新たにフルタイムとしてジョインしました🎉
全社としてもかなり組織拡大しておりTake off(卒業)された方々もいらっしゃいますが、たくさんの方々に携わっていただき感謝しております。
また、今年は「LEARN NEW」というValueを定めていることから個人的には新しい技術にもチャレンジしていきたいと感じています。フルタイムメンバーが増えることで、周りのメンバーもチャレンジを応援してくれる環境がさらに整ったのではないか思います。
技術について
設計 ~ TCA ~
ベースのアーキテクチャはThe Composable Architecture(TCA)を採用しています。
弊社には「GO FAST」というValueが定められています。どうすれば効率良く、クオリティを担保した上で最速リリースができるかを考えた上、理由は様々ですが大きくは以下の理由で変更していません。
- 設計方針が既にTCA側で固く示されている
- 機能開発を行う上で、人によって設計方針に差異が生まれにくい
- レビューコストの低下に
- SwiftUI x TCAの知見が比較的多いこと
さらに、Swiftの進化と共に、TCAはv1に向けてDiscussionが行われています。すでにリリースされている中にも1年前と大きく変化した部分があります。(2023/4/1時点)
- ReducerProtocol
- Supported Swift Concurrency
- Composable Navigation(Beta)
私たちが感じた大きな変化はReducerProtocolのリリースでした。
Before:
struct FeatureState: Equatable {} enum FeatureAction: Equatable {} struct FeatureEnvironment { var apiClient: ApiClient var mainQueue: AnySchedulerOf<DispatchQueue> } let featureReducer = Reducer< FeatureState, FeatureAction, FeatureEnvironment > { state, action, environment in // Return Effect }
After:
struct Feature: ReducerProtocol { struct State: Equatable { … } enum Action: Equatable { … } @Dependency(\.apiClient) var apiClient @Dependency(\.mainQueue) var mainQueue func reduce(into state: inout State, action: Action) -> EffectTask<Action> { // Return EffectTask } }
以前の形だとDIするときはバケツリレーで渡すことが前提の設計となっていましたがReducerProtocolと共にDependencyValuesも追加され、比較的容易にDIが可能となりました。Stateがツリー上に構成されるTCAでは大きな課題で、末端のStateのために不要な値渡し、importを行なっていましたがReducerProtocolの登場でかなりスマートになったと思います。他にも、コンパイラが正常に機能するようになったりパフォーマンス、メモリ使用量改善などが開発主のpointfreeから挙げられています。(参考)
API ~ GraphQL & Apollo ~
開発時初期からGraphQL ClientとしてApollo-iOSを利用しており、コードの自動生成を用いてgraphqlファイルからモデルを自動生成しています。
昨年Apollo-iOSはv1.0.0がリリースされ以下の対応がメジャーリリースされました。
- Swiftライクなコードでモデル生成
- 生成されたモデルのサイズ削減、可読性、機能性などの向上
- Multi moduleなプロジェクトで生成されたモデルを扱うための対応
- キャッシュ時のタイプセーフなAPI
特に大きかったのは生成モデルのEquatable対応です。NEWT iOSではGraphQLのQueryによって欲しいデータにアクセスでき、コードの自動生成でモデルをドラスティックに変更できる特徴を活かすためにTCAのStateには生成モデルをそのまま持たせて利用していたため、Equatableの対応が必要でした。
extension HogeFragment: Equatable {} extension FugaFragment: Equatable {}
v1.0.0リリースまでは上記のように生成モデルのextensionにEquatableを準拠させていましたがApollo-iOS側で生成時点で準拠するようになったので対応は不要になり、よりスマートなコードになったと感じています。
また、GraphQLはQueryを用いて欲しいデータにアクセスできる反面、Over Fetchingという不要なデータにアクセスしてしまうがためにレスポンススピードが遅くなってしまう問題も起きてしまいます。実際にNEWT iOSでもOver Fetchingがあったためにリストが表示されなくなってしまったり、タイムアウトしてしまうなど問題が起きました。現在では大きな問題は解消されつつありますが、引き続きQueryの見直しや最適化を進めていきます。
Multi Module
NEWT iOSはすべてのPackage, Feature ModuleをSwift Package Manager(SPM)で管理しています。リリース当初からSPMを用いて一元管理していたのでFeatureModuleの切り出しや、構成の変更が容易だったと感じています。
FeatureModule:
├── TaskFeature │ ├── Components │ │ ├── TaskBadge.swift │ │ ├── TaskCard.swift │ │ ├── TaskCategoryHeader.swift │ │ ├── TaskDetailTravelerList.swift │ │ └── TaskStatusBadge.swift │ ├── TaskDetail │ │ ├── TaskDetailCore.swift │ │ └── TaskDetailView.swift │ └── TaskList │ ├── TaskListCore.swift │ └── TaskListView.swift
FeatureModuleは機能ごとに切り出しており、上記はやることリスト機能のModuleです。
リリース当初は記述的にも少なかったためCoreとViewを同じファイルで管理していましたが、機能開発を進めるうちに両者肥大化していくので分離することにしました。
また、ComponentsはModule内で使い回す必要がある場合にViewとして切り出しています。
NEWT iOS内で共通して利用するComponentは
Styleguide
というModuleがあるのでそちらに移行するようにしています。ClientModule:
AnalyticsClient ├── Extensions │ └── View+Analytics.swift ├── Interface.swift ├── LiveKey.swift └── TestKey.swift
ClientModuleもそれぞれの役割ごとに切り出しています。
ApiClient
や UserDefaultsClient
などが該当し、上記はログ送信のためのClientModuleです。TCAのDependencyValuesの定義なども行い、必要なFeatureModuleにDIできるようにする役割も担っています。
おおまかに他の汎用的なModuleを含むとSPMが管理する構成は以下のようになっています。
Sources ├── FeatureModule1 ├── FeatureModule2 ├── ClientModule1 ├── ClientModule2 ├── SharedModels ├── Styleguide ├── NEWTFoundation └── NEWTUI
- SharedModels
NEWTアプリ内で共通して扱うモデル群。Apollo-iOSで生成されたモデルのExtensionなども含む。
- Styleguide
NEWTアプリ内で共通して扱うコンポーネント群。Viewに対する汎用的なExtensionも含む。
- NEWTFoundation
標準のFoundation frameworkに対するExtensionやHelper群。
- NEWTUI
NEWTのデザインシステムで定義されたColor、IconAsset群。
開発初期の段階では汎用的なExtensionやComponentが散らばっており、まだ移行しきれていない部分は少し残っていますが、この構成にしてからどこに何を置くべきかが明確にできたのではないかと感じています。
おわりに
いかがでしたでしょうか?日々「GO FAST」な開発を続ける私たちですが、技術的な課題や変化にも少しずつ向き合い、サービスとしても大きく飛躍した1年でした。1人1人の裁量も大きく「自由と責任はセット」という言葉をもとに改善しています。
いつでもカスタマーファーストを忘れずに、これからも「令和時代を代表する、デジタルトラベルエージェンシー」を創っていきたいと思います。
明日のNEWT 1st ANNIVERSARY CALENDAR は、コンテンツUnitリーダーで、この1年販促企画の中心にいたマーケティング部のMukaiが担当します!お楽しみに!
令和トラベルでは、現在全力で仲間探しをしていますので、少しでもご興味ある方はぜひ採用ページからご連絡ください。まずは気軽にお話を聞いていただける、ミートアップも開催しています。メンバー全員で温かく迎える準備はできています!
私たちが運営する海外旅行予約サービス、NEWTはこちらから。
*******************
現在、NEWTでは大感謝セール『NEWT FES』を開催中です。ぜひこの機会に海外旅行をもっとおトクにご予約ください。
NEWT FES 祝1周年!大感謝セール| NEWT(ニュート)
令和トラベルに関する情報発信を専門とした公式noteはこちらから。