RAKSUL TechBlog

ラクスルグループのエンジニアが技術トピックを発信するブログです

ColaboratoryでMVPを作る

RAKSUL Advent Calendar 2021の22日目を担当する五百井(いおい)です。

ノバセル事業本部でデータサイエンティストをやっています。

本日は業務での経験から、ColaboratoryによるMVPの作成について書きます。データサイエンスとビジネスを結びつける内容であり、ColaboratoryやMVPといった用語を知らない方にも楽しんでもらえると思います。

Colaboratoryとは

ColaboratoryとはブラウザからPythonを記述、実行できるGoogleのサービスです。

Pythonに限らず、プログラミング言語を書いてプログラムを開発するにはローカルPCやサーバに開発環境を構築しなければなりません。構築には時間がかかることや、ITエンジニアでなければ難しい作業が発生することがあります。また、異なるPCやサーバで構築された開発環境は、たとえ同じ言語に関するものであっても、OSやソフトウェアのバージョンなどに微妙な違いがあり、ある環境で開発したプログラムが別の環境で動かないこともあります。

Colaboratoryを使えば、環境構築をする必要がなくなり、Googleが統一的に管理しているので開発者によって環境に差異が生まれることもありません。しかもGoogleのアカウントさえ持っていれば無料で使えます。また、Pythonはデータサイエンス分野でよく使われる言語ですが、NumPy、pandas、scikit-learnといった定番のデータサイエンス関連パッケージはインストール済みですし、必要に応じてパッケージを追加することももちろん可能です。

なお、ブラウザでプログラムを実行して結果が即座にブラウザに返ってくるUIをノートブックと言います。実験ノートのように実行結果を記録しておいたりメモを添えたりできるためこのように呼ばれます。ColaboratoryのUIは有名なノートブックのJupyter Notebookとほぼ同じです。環境構築なしで使えるJupyter Notebookといってもよいでしょう。

MVPとは

事業開発におけるMVPとはMinimum Viable Productの略で、必要最小限の機能を備えたプロダクトのことです。とくにスタートアップにおける事業立ち上げでよく使われる概念です。

たとえば、自動車をゼロから開発するとします。エンジン、ブレーキ、車体、車輪、ハンドル、センサなど、数多くの部品とそれらを組み立てる技術が自動車の開発には必要であり、完成には長い時間を要します。当然、完成するまで自動車に乗って移動することはできません。

一方、事業における自動車開発の目的が移動手段の提供だとすると、イラストのように簡易な移動手段をまず作れば顧客にすぐに使ってもらえます。また、早い段階で顧客から得たフィードバックを自動車に反映できます。ここでの簡易な移動手段がMVPであり、アイデアを思いついてからすぐにサービスを提供できる、完成品が顧客のニーズからかけ離れたものにならないといった利点があります。

※イラストは「Making sense of MVP」(https://blog.crisp.se/2016/01/25/henrikkniberg/making-sense-of-mvp) より引用

なぜColaboratoryでMVPを作るのか

データサイエンティストやAIエンジニアと呼ばれる職種の人は、人間には複雑すぎてできない問題を解いたり、人間がやるとものすごく時間がかかる作業を一瞬で完了したりする数理モデルやアルゴリズム、いわゆるAIを開発します。我々ノバセルが属するTVCM業界だと、たとえば広告主の要望を入力すると出稿すべき番組の最適な組み合わせを出してくれるウェブサービスがあれば便利でしょう。数理モデルやアルゴリズムの開発には試行錯誤がつきものなので、インタラクティブなプログラミングができるノートブックでの開発が好まれます。

一方、数理モデルやアルゴリズムが完成してもすぐにウェブサービスとして使ってもらえるわけではなく、実装をしなければなりません。既存の機能に影響を与えることなく、数理モデルやアルゴリズムによる新機能をサービス上に載せるには、データサイエンスとはまったく異なるエンジニアリングの技術が求められ、相応の時間がかかります。

ここで新機能の提供を素早く開始するのにColaboratoryでMVPを作ることが考えられます。前述の例に倣うと、広告主がブラウザへの入力だけで番組の組み合わせを得られるサービスが完成品だとすると、広告主がメールやSlackで要望を送信すると担当者から番組の組み合わせが返ってくるサービスがMVPとして考えられます。Colaboratoryごとプログラムを渡し、簡単な操作マニュアルをつければ、プログラミング経験がない担当者でもプログラムを実行してサービスを提供できます。データサイエンティストは開発に使ったColaboratoryを特別に書き換える必要がありません。

ちなみに、MVPを提供する際に最終的に自動化したい処理を人間が済ますことを「オズの魔法使い」と言います。

気をつけるべき点とおわりに

ただし、Colaboratoryはプログラミングを知らない人が動かすことを想定したものではなく、書かれたプログラムが書き換えられないように保護することもできません。操作ミスなどでプログラムが動かなくなってしまう危険性があります。また、Colaboratoryを動かすだけでサービス提供できるとはいえ、広告主の依頼を受領してからアウトプットを送信するまでのオペレーションコストがかかっているので、速やかに正式なウェブサービスへ移行することが期待されます。

最後に個人的な実感に基づく話なのですが、データサイエンティストはプロダクトをガリガリ開発するようなエンジニアでもなければ、営業をはじめとするビジネス職でもなく、ともすれば組織で中途半端な立ち位置に陥ってバリューを発揮できなくなる不安定な存在だと思います。両者のギャップを乗り越えてデータを活用しビジネスに貢献するには、組織編成からコミュニケーションスキルにいたるまでさまざまな観点からの方法がありますが、本日のお話もその一助になれば幸いです。

参考

StepFunctions を CDK + Typescript で構築するサンプル集

title: StepFunctions を CDK + Typescript で構築するサンプル集

こんにちは。ノバセル事業本部の星野です。本日は、Raksul Advent Calendar 21日目です。

最近の業務で、StepFunctions によって Web サービスを構築する機会がありました。 StepFunctions は AWS の各種サービスをつかったワークフローを構築するためのサービスです。 Lambda, ECS, AWS Batch, Aurora Serverless など様々なサービスの呼び出しをステートマシン形式で定義することができます。

 https://aws.amazon.com/jp/step-functions/

このとき、AWS クラウド開発キット(CDK) を触り、いくつかのサービスを使ったワークフローを構築したのですが、非常に便利な組み合わせだなあと感動しました。

本稿では StepFunctions を CDK を使って構築する際によくありそうな実装パターンをサンプル集としてまとめてみました。

続きを読む

Snowflakeの紹介とSnowCamp参加報告

こんにちは。ノバセル事業部システム開発部エンジニアの山中です。RAKSUL Advent Calendar 21日目を担当します。

今回はノバセルで導入しているSnowflakeについての紹介と、Snowflake主催のブートキャンププログラムSnowCampの参加報告をします。

「データクラウド」Snowflakeとは

Snowflakeとは、データウェアハウジング機能を中心としたデータプラットフォームで、あらゆる規模のデータを1か所に集約し、データに対する様々なタスクを優れたパフォーマンスで実施することができます。

ここでいうデータの様々なタスクには、例えば以下のものが含まれます。

  • データロード(データをウェアハウス内に蓄積する)
  • データクレンジング(データを整え、利用しやすいようにする)
  • データ分析(ウェアハウス内のデータを分析してビジネス示唆を出す)
  • データの蓄積(データを1箇所に集中管理する)
  • データの公開・共有

このように、データにまつわる多様なワークロードに対するマネージドサービスを提供しているため、Snowflakeは「データクラウド」を自称しています。

ラクスル社内ではノバセル事業部のデータ基盤として昨年より導入しています。Google BigQueryやAmazon Redshiftと比較されがちな製品ですが、Snowflakeの優れた点をいくつかご紹介したいと思います。

数億行のデータの複製が一瞬

Snowflakeでは、ゼロコピークローンという機能を利用することができ、データを複製することなく、テーブルのクローンを作成できます。

例えば、target_tableを、source_tableからデータを含めて複製したい場合、

CREATE TABLE target_table CLONE source_table

と書くと、source_tableと全く同じテーブルtarget_tableを作成できます。データを複製しないため、レコード数が億単位の巨大なテーブルであっても数秒のうちにクローンを作成できます。

この機能により、開発環境の構築・破棄がスピーディに行えたり、ロールバックが容易に行える(後述)ようになります。

任意の地点のデータへのロールバックが容易

Snowflakeでは、タイムトラベル機能というものがあります。これは、あるテーブルの任意の地点の状態を確認することができる機能です。

例えば昨日正午のテーブルの状態を確認したい場合には、

SELECT * FROM table_a AT(timestamp => '2021-12-20 12:00:00'::timestamp);

とクエリすると確認することができます。また、任意の地点でのデータを別のテーブルにクローンすることも可能なので、テーブルに対して誤った操作などをしてしまった際のロールバックにも利用することができます。例えば、

CREATE TABLE backup_table CLONE source_table AT(timestamp => '2021-12-20 12:00:00'::timestamp);
ALTER TABLE backup_table SWAP WITH source_table

と書くことで、source_tableテーブルを実質的にロールバックしたように見せることができます。タイムトラベル機能もゼロコピークローンと同じ仕組みで実現されています。

これらの機能は、マイクロパーティションと呼ばれる技術の上に成り立っており、マイクロパーティションについて詳しく知りたい方は以下のドキュメントを是非見ていただければと思います。

マイクロパーティションとデータクラスタリング - Snowflake Documentation

データの共有ができる

また、Snowflakeには、データシェアリング機能が備わっています。この機能を利用すると、企業アカウント間でデータを共有し、他社アカウントのデータがあたかも自社アカウントに存在するかのように利用することが出来ます。

企業間でデータ連携をする際に必要となる開発コストがほぼ0になるだけでなく、データの更新がリアルタイムに反映されるため、データの鮮度の向上にもつながります。

さらに、データを販売するプラットフォーム「Snowflake Data Marketplace」も提供されています。

Snowflake Data Marketplace | Access Live, Ready-to-Query Data

この機能は、Snowflakeのスローガン「Mobilize the world's data」(欲しいデータを欲しい時に使える)を実現する上でもっとも中心的な存在となっています。

上記以外にもSnowflakeには優れた点があります。Snowflakeの特徴について詳細を知りたい方は、以下のData VIz Labさんのブログなどをご覧ください。

【徹底解説】次世代データウェアハウス"snowflake"の特徴

SnowCampとは

ノバセルで導入しているSnowflakeですが、Snowflake日本法人が主催するブートキャンププログラムSnowCampに第1期生として参加してきました。

どんなプログラム?

SnowCampは、講義やディスカッションを通じて、以下の3つの目的を達成することを目指すプログラムとなっていました。

  • Snowflakeの技術や環境を正しく理解し利用するためのスキルを習得する
  • データドリブンな世界を実現していくためのビジョンを言語化できるようになる
  • 同じ理想を持ったネットワーク・コミュニティの形成

全4回のプログラムとなっており、自分を含め、8社から1人ずつが参加する形式になっていました。

Snowflakeの技術・機能に関する講義はもちろん、それらの機能を利用して自社のワークロードの改善案を考えたり、他社のデータと組み合わせどのようなビジネス価値を生み出せるのか、といった実践的な内容をディスカッションベースで考えていきました。

何を得られたか

データドリブンな意思決定を支えるための施策が提案できるようになった

ノバセルは「マーケティングの民主化」に向けて、TVCMマーケティングにおけるデータドリブンな意思決定を支援するサービスを展開しています。

Snowflakeは、このデータドリブンな意思決定を支えるためのプラットフォームです。Snowflakeを用いてどのようにノバセルの事業成長を支え、牽引していくことができるのか、講義やディスカッションを通じて解像度を高めることができました。

SnowCampで学んだことをベースにして、短・中期的な改善施策を提案していくことができるようになりました。

他社における取り組みを知り、ノバセルの強み弱みを認識できた

新卒として入社し半年ほど働いてきましたが、コロナ禍ということもあり、社外の方と関わる機会があまりありませんでした。

その中で、SnowCampは他社の取り組みについて深いところまで話を伺い、ディスカッションする機会になりました。

他社で行っている取り組みの中でノバセルでも取り入れていきたいことや、逆にノバセルだからこそやりやすいことなどが分かり、ノバセルの立ち位置を理解することができました。

井の中の蛙にならないよう、今後も積極的に社外の方と交流していこうと考えるきっかけになりました。

さいごに

今回は、ノバセル事業部において利用しているSnowflakeと、SnowCampプログラムについて紹介させていただきました。一緒に参加した他社様、招待いただいたSnowflake様、自分を送り出してくれたチームメンバーの皆様に感謝いたします。

また、ノバセルにおけるSnowflakeの活用事例も近いうちに紹介していこうと思いますので、お楽しみに!

先日開催されたSnowflake公式イベントSNOWDAYでも、ノバセルにおける活用事例を紹介しているので、是非ご覧ください。

https://www.snowflake.com/snowday-japan/?utm_source=dm2&utm_medium=email&utm_campaign=mick

(SNOWDAYへの登録が必要となります)

ーーーーーーーーー

ラクスルでは、エンジニア・データサイエンティストを募集中!

データ関連技術を活用して事業貢献に成長したい方から、カジュアルに話を聞いてみたい方までぜひご応募お待ちしております!

ハコベルはなぜFlutterでアプリ開発を行うのか

この記事は RAKSUL advent calendar 19日目の記事です。

はじめに

2021年9月にラクスル株式会社 ハコベル事業部に入社しFlutterでモバイルアプリ開発やっています。

今まではAndroidをメインで開発しており、Flutterを触るのは入社してからになります。宣言的UIで画面を作成するので少し慣れない部分もありつつ楽しく開発してます。

課題

現在、ハコベルでは3つのアプリをリリースしてます。

  • ハコベルカーゴ(軽貨物ドライバー向け)
  • ハコベルコネクト(一般貨物ドライバー向け)
  • ハコベルコネクト(配車担当向け)

ですが、それぞれReactNativeやJava, Swiftのネイティブで書かれていたり、WebViewメインの側ネイティブなど様々な言語、SDKで作成されています。

また、ドメインや技術スタックが異なるため開発、メンテナンスするメンバーが分かれていました。

それにより、以下のような課題が発生しておりました。(主に属人化ですね)

  • 必要な知識のインプットの学習コストが高い
  • それぞれのアプリの知見が活かされづらい
  • メンテナンスできる人が各アプリ1人のためメンテナンスされず放置されていた
    • 属人化している
    • 継続的なSDK/ライブラリのバージョンアップができない
    • 追加機能を実装できない
  • CI/CDにおけるワークフローの自動化がされていないため、ビルドやアプリ配布にコストがかかる
  • 継続的にメンテナンスするエンジニア確保も難しい

これらの問題に対して我々はFlutterを選択しました

なぜFlutterか?

Flutterは既に多くの実績があり、日本でも既に多くの開発実績があります。

弊社においても上記課題に対しての解決になると思い検討を進め、Flutter導入の意思決定をしました。

主な理由は下記になります。(主にReactNativeとの比較になります)

  • iOS, Android 両OS対応
    • メンテコストが低い
    • それぞれのOSにあった表示をサポートしてくれる
  • 学習コスト低い
  • SDKをアップデートが容易
    • 依存関係が少ない
  • パフォーマンスがReact Nativeに比べ良い

私も未経験から開発しましたし、Flutter経験1週間のエンジニアが他チームからジョインしてくれ直ぐに戦力となってもらえ、実際に学習コストの低さを実感しました。

また、今までの課題を解決すべく仕様や開発におけるドキュメントを積極的に属人化を防ぐために他のチームに向けて社内のMeetupで技術紹介をするようにチームで動いてます。

どんな実装してるの?💡

現在1つ目のアプリとして最も複雑なアプリである「ハコベルコネクト(一般貨物ドライバー向け)」を来月1月リリースに向けて絶賛Flutterリプレイス中です🚀

現在開発している環境やライブラリを簡単に紹介します。

Flutter

  • Flutter: 2.5.2
  • Dart: 2.14.3

ライブラリ

  • Riverpod
    • DI + StateManagement
    • hooksは使ってないです
  • freezed
    • immutable
  • auto_route
    • Router
  • Dio
    • HTTP client

CI/CD

  • CircleCI

アーキテクチャ

MVVMをベースにRiverpodと合うようにチームで話し合いながら模索中です。

現在の実装はクリーンアーキテクチャを意識した設計にしており、各層のアクセス順は次のようになります。

それぞれの依存関係解決にはRiverpodのProviderを用いております。

UI
↓
ViewModel // ビジネスロジック & 表示用データ保持
↓
Repository // データアクセス隠蔽
↓
Data Source // Firebase DBやAPIアクセス

データを取得し画面を表示する場合は、Riverpodが提供している FutureProviderStreamProviderを用いViewModelを作成しないケースもあります。この場合は、ProviderがRepositoryを参照しデータ取得を行っています。

DatabaseはバックエンドにFirebase Realtime Databaseを使ってることもありアプリ内にデータの保存はしていないので、利用してません。

今後の展望

まずはファーストリリース向け開発を終わらせることが目標ですが、サービスはリリースがゴールではなく、継続的にユーザーに価値をデリバリーできるようにしていきたいと思っています。

そのためにはリプレイス前に出た課題を再発させないためDX(Developer eXperience)改善を合わせて行っていきたいと思っています。

  • 属人化しないように、CI/CDの立て付け
    • アプリ配布以外のリリースまで自動化
  • アーキテクチャの共有, 明文化
  • 仕様の明文化 & マネジメント
  • 継続的なSDKアップデート仕組みづくり

また、残りの2アプリについてもFlutterでリプレイスを行う予定です💪

最後に

ハコベルチームでは一緒に働くメンバーを募集しています!

興味ある方はぜひこちらからご応募ください!

https://hrmos.co/pages/raksul/jobs/HIU-TECH9

ラクスルのアドベントカレンダー全編はこちらから

https://qiita.com/advent-calendar/2021/raksul

Composition APIのsetup関数をいい感じに分割したい

こんにちは、ハコベル事業本部ソリューションスクラムチームの大川です。 この記事はRaksul Advent Calendar 2021 18日目の記事です。

ソリューションスクラムチームではハコベルコネクト を開発・運用しています。 ハコベルコネクトは物流における配送依頼や配車業務などをWeb上で管理することで 情報共有の効率化や一部作業の自動化により配車工数削減を実現するためのサービスです。 自分はチームの中でフロントエンドエンジニアとしてUIの開発をしています。

先月入社したばかり&これまで主にReactを使ってきて仕事でVueを使うのは初めてですが、 Composition APIのsetup関数をいい感じに分割するアイデアを考えてみました。 アイデア自体はちょっとしたことですが、同じ課題感を持つ方の参考になれば幸いです。

背景

ハコベルコネクトのUIはVue (v2) で実装されており、 新規作成するコンポーネントやページはComposition APIを利用する方針です。 (v2向けのプラグインを利用しています)

Composition APIは従来のOption APIと比較して、関心事に沿ってロジックを分離・集約できて可読性が向上し、 再利用可能な関数を切り出すことができて便利だと感じています。 一方で、setup関数に全ロジックを集約すると関数全体が冗長になり、 別のcomposition関数に切り出そうとしても再利用性を意識できていないことがありました。

例えば、あるフォームの実装ではページのライフサイクルフックを設定する関数と フォームのイベントハンドリングを設定する関数で分けましたが、 それぞれの分割したcomposition関数がVuexやローカルのステート・Vueのライフサイクルに依存しておりページ固有のものとなっていました。 また、setup関数の実装方針が明確になかったので、ロジックの組み方や分割方法が開発者によってバラバラになりがちでチームでの共通認識を作りにくいのも気になっていました。

考えた方針

  • composition関数の分割時に再利用性を意識できていなかった
  • setup関数の実装方針に対して共通認識を作りにくい

という課題に対して「setup関数をusecaseとserviceに分ける」方針を考えました。 setupをserviceとusecaseに分けた概要図 それぞれの役割は以下のとおりです。

  • setup関数: serviceとusecaseを呼び出し、プロパティとライフサイクルフックを設定する
  • usecase: serviceを引数で受けて、そのコンポーネント(ページ)のロジックを実装する
  • service: usecaseが必要とするVuexやローカルのステートとアクションを用意する
    • Vuexと連携するservicesと、ページのローカルステート管理用のserviceの2つに分かれる

サンプルコードを書いてみる

ユーザーが登録した住所の一覧を表示するページを例に、 上の方針でページ遷移時の以下2つのロジックを実装してみます。

  • 住所の一覧を取得するイベントハンドラー
    • 住所の一覧を取得するAPIをリクエスト
      • APIがエラーを返したらエラーメッセージを表示
  • 初期化処理ができているかのフラグ
    • コンポーネントの表示切り替えに利用する

特に分割せずにsetup関数で実装すると以下のようになります。

import { defineComponent, computed, ref } from '@vue/composition-api'

export default defineComponent({
  setup (_, ctx) {
    // ページに必要なデータをロードできてから描画するためのフラグを管理する
    const isInitialized = ref(false)

    // Vuexのストアを取得
    // NOTE: コンテキストのrootオブジェクトはComposition APIのプラグインでのみ利用可能です
    const store = ctx.root.$store
    // アプリ全体でローディング表示を管理しているプラグイン
    const loading = ctx.root.$loading

    // 住所の一覧をVuexのステートから取得
    const locations = computed<LocationState["locations"]>(
      () => store.getters["locations/locations"]
    )

    // 初期化処理
    const onCreated = async () => {
      const { error } = await loading(
        store.dispatch("locations/fetchLocations", 1)
      )
      if (error) {
       store.dispatch('app/addError', { message: '場所情報の取得に失敗しました' })
      }
      isInitialized.value = true
    }

    // DOM操作がないのでonMountedを使わず初期化処理を実行
    onCreated()

    return {
      isInitialized,
      locations,
    }
  },
})

これだけだと問題があまりなさそうですが、 たとえばページに他の機能(検索や住所情報の削除)があった場合はさらにsetup関数が長くなりそうです。 また、onCreated関数の中でVuexアクションのディスパッチとローカルステートを更新しており、 この関数を別ファイルで実装するだけでは他のページやコンポーネントでの再利用性はなさそうです。

それでは、このコードをusecaseとserviceに分ける方針で分割してみます。 まずは、serviceを用意します。 appService.ts: エラーメッセージ表示用のVuexのステートと連携する

// appService.ts
import type { SetupContext } from '@vue/composition-api'

export const useAppService = (ctx: SetupContext) => {
  const store = ctx.root.$store
  
  return {
    addError: (message: string) => store.dispatch('app/addError', { message }),
  }
}

locationService.ts: 場所の一覧を取得するVuexのステート・アクションと連携する

// locationService.ts
import { SetupContext, computed } from '@vue/composition-api'
import type { LocationState } from '@/models/location'

export const useLocationService = (ctx: SetupContext) => {
  const store = ctx.root.$store
  const loading = ctx.root.$loading

  const locations = computed<LocationState["locations"]>(
    () => store.getters["locations/locations"]
  )

  const fetchLocations = (page: number) => {
    return loading(
      store.dispatch("locations/fetchLocations", page)
    )
  }

  return {
    locations,
    fetchLocations,
  }
}

pageStateService.ts: ページ(コンポーネント)用のステートを用意する

// pageStateService.ts
// コンポーネントと同じディレクトリで実装する
import { ref } from '@vue/composition-api'

export const usePageStateService = () => {
  const isInitialized = ref(false)
  const setIsInitialized = (newStatus: boolean) => {
    isInitialized.value = newStatus
  }

  return {
    isInitialized,
    setIsInitialized,
  }
}

続けて、usecaseを用意します。

// locationUsecase.ts
// コンポーネントと同じディレクトリで実装する

type UseInitializerParams = {
  fetchLocations: (page: number) => Promise<{ error?: Error }>
  addError: (message: string) => void
  setIsInitialized: (status: boolean) => void
}

export const useInitializer = (params: UseInitializerParams) => {
  const {
    fetchLocations,
    addError,
    setIsInitialized,
  } = params

  const onCreated = async () => {
    const { error } = await fetchLocations(1)
    if (error) addError('場所情報の取得に失敗しました')
    setIsInitialized(true)
  }

  return {
    onCreated,
  }
}

最後にsetup関数でserviceとusecaseを呼び出し、プロパティとライフサイクルフックを設定します。

import { defineComponent } from '@vue/composition-api'
import { useAppService } from '@/services/appService'
import { useLocationService } from '@/services/locationService'
import { usePageStateService } from './pageStateService'
import { useInitializer } from './locationUsecase'

export default defineComponent({
  setup (_, ctx) {
    // service呼び出し
    const {
      isInitialized,
      setIsInitialized,
    } = usePageStateService()
    const {
      addError,
    } = useAppService(ctx)
    const {
      locations,
      fetchLocations,
    } = useLocationService(ctx)

    // usecase呼び出し
    const {
      onCreated,
    } = useInitializer({
      fetchLocations,
      addError,
      setIsInitialized,
    })

    // 初期化処理を実行
    onCreated()

    return {
      isInitialized,
      locations,
    }
  },
})

上記の例以外にもいくつかサンプルコードを書いてみて、以下のような気付きがありました。

(よかった点)

  • setup関数でロジックを直接実装していないので可読性が上がった
  • usecaseがVue / Vuexに依存しないので、テストしやすくなりライブラリなどの変更に強くなった
  • API呼び出しのserviceはページのロジックとは分離できたので、再利用性が上がった
  • usecase / serviceという分け方を明文化できたので、チームで認識あわせやすくなった

(改善点)

  • serviceの実装方針が決まっていない
    • Vuexのserviceとローカルステートのserviceが別のディレクトリで定義されている
    • ローカルステートは直接setup関数で、Vuexのserviceはドメインモデルごとに定義したほうがよさそう
  • usecaseのロジックが複雑にならないときは、分割して定義するメリットが大きくない
    • setup関数でserviceを利用するだけでもよさそう
  • usecaseがリアクティブな変数を参照していないので、setup関数での結合が必要になる
    • refの値をusecaseの引数にしてイベントハンドラーを定義すると、refの初期値でハンドリングされてしまう

最初に感じていた課題に対しては一定の効果がありそうですが、 serviceの実装方法などは引き続き改善していく必要がありそうでした。

さいごに

今回は可読性や再利用性の向上とチームでの共通認識をあわせてみるためのアイデアとして、 Composition APIのsetup関数をusecaseとserviceで分ける方法を紹介しました。 まだアイデア段階なので、今後もComposition APIのメリットを意識して改善し続けていこうと思います。 他にもいい方針があればぜひ教えて下さい!

ハコベルではエンジニアを絶賛募集中です! 物流業界のDXを加速させる!ハコベル事業を牽引するテックリード候補を募集! | ラクスル株式会社