現在自分用のアプリケーションとして、体重計やFitbitのようなトラッカーのデータを集約し、さらに手動で体調不良などのイベントを管理したり、運動管理したり、データに基づいてAIに分析させるようなアプリケーションをAIで作成しています。
その過程で、内部的な設計を変更したのですが、今後こういう判断を誰がしていくんだろう、あるいは誰がどうやって必要だと学ぶんだろう、とふと思いました。
目次
設計の変更
で、まぁ最初は今あるデータをホイホイ読ませて、それ十分だったのですけれど、読み取るデータが多岐に渡ってくるにしたがって、内部の設計について気になり始めました。恐らく、データソースと取り込みが密結合になっているだろうなぁ、と思ったので。聞いてみるとやはりそうだったのですが、何を言っているかと言うと、図示すると以下のようにデータソースと取り込みロジックが1vs1で完全に対応してしまっている状態です。
flowchart TD
subgraph External["外部データソース"]
GH["Google Health API (JSON)"]
TO["Google Takeout (ZIP / CSV)"]
EU["Eufy 体重計 (BLE / CSV)"]
end
subgraph TightlyCoupled["各機能内部(変換と永続化が一体化)"]
GH_All["Google Health クライアント\n・JSONパース\n・独自集計ロジック\n・個別 INSERT / ON CONFLICT SQL"]
TO_All["Takeout インポーター\n・ZIP / CSVパース\n・独自集計ロジック\n・個別 INSERT / ON CONFLICT SQL"]
EU_All["Eufy 処理(新規追加時)\n・データパース\n・個別 INSERT / ON CONFLICT SQL"]
end
DB[("SQLite データベース\n(daily_metrics / body_measurements)")]
GH --> GH_All
TO --> TO_All
EU --> EU_All
GH_All -->|"直接 SQL 実行\n(マージルールが分散)"| DB
TO_All -->|"直接 SQL 実行\n(マージルールが分散)"| DB
EU_All -->|"直接 SQL 実行\n(マージルールが分散)"| DB
classDef issue fill:#ffebee,stroke:#c62828,stroke-width:2px,color:#b71c1c;
class GH_All,TO_All,EU_All issue;
コレの何が良くないかと言うと、同じデータでもGoogle Health API経由なのかそれともエクスポートcsvデータなのかで完全に異なるロジックになってしまうわけです。これは拡張性と保守性の両方に問題があります。
なので、設計をデータソースと取り込みの間に、フォーマットするレイヤを増やして疎にしました。
flowchart TD
subgraph External["外部データソース"]
GH["Google Health API (JSON)"]
TO["Google Takeout (ZIP / CSV)"]
EU["Eufy 体重計 (BLE / CSV)"]
UI["画面からの手動入力 (Web UI)"]
end
subgraph Converters["データ変換レイヤ(副作用のない純粋関数)"]
GH_Conv["Google Health コンバータ\n(JSON ➔ 共通モデル)"]
TO_Conv["Takeout コンバータ\n(ZIP ➔ 共通モデル)"]
EU_Conv["Eufy コンバータ\n(BLE / CSV ➔ 共通モデル)"]
end
subgraph CoreDomain["アプリケーション共通仕様"]
Models["共通ドメインモデル\n・models.DailyMetric\n・models.BodyMeasurement\n・models.ActivityLog"]
end
subgraph Ingestion["データ取り込みレイヤ (internal/ingest)"]
IngestSvc["統一 Ingest サービス\n・トランザクション制御\n・安全なマージ制御 (COALESCE/部分更新)\n・source メタデータ自動付与"]
end
DB[("SQLite データベース\n(source カラム付き)")]
%% データフロー
GH --> GH_Conv
TO --> TO_Conv
EU --> EU_Conv
GH_Conv --> Models
TO_Conv --> Models
EU_Conv --> Models
UI --> Models
Models --> IngestSvc
IngestSvc -->|"一元化された SQL で永続化"| DB
classDef pure fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px,color:#1b5e20;
classDef service fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d47a1;
class GH_Conv,TO_Conv,EU_Conv pure;
class IngestSvc service;
これでまぁ、今後対応すべきデータソースが増えてもやりやすくなったはずです。とりあえずコードはだいぶリファクタされました。
そしてジェンガが出来る
思ったのは、今後主流になるだろうAIによる開発において、設計変更を伴うような判断を誰ができるのだろうか、ということです。今回の場合、継続的にインタフェースが増えそうだなぁ、と判断した時点で中間層をいれるようにしましたが、自分で言わないと、多分AIは前例踏襲で密な結合を増やし続けたと思います。
それは拡張を続けていくと破綻しそうです。それともAIならば頑張れるんだろうか?あるいは、AIが頑張れなくなるまで拡張を続けてしまうのだろうか。まぁそこまで使い続けられたらそのアプリはある意味成功なのかもしれませんが、であればこそ使い続けられるような状態にしたいところでもあります。
もちろん、最初から完全な要件を提示すれば、相応しい設計を最初からするに違いありませんが、それができるなら誰も苦労しません。アプリケーションはしばしば、自分も何が欲しいのかわからないところから始まります。特にAI時代、まず形にして、自分自身がまず何をしようとしていたのかを知るほうが、効率も良いでしょう。
しかしそれだと、恐らくジェンガが出来ます。そのジェンガは最終的にAIと自分の能力を超えるところまで積み上げられます。
ジェンガ上等かもしれない
まぁでも、いいのかもしれません。崩壊する前に仕様が完全に固まって固定されれば、それは賭けに勝った、という感じですし、たとえ目に見えて破綻しても、そのときには欲しかったものがかなり目に見えているはずですから、そこから仕様を改めて洗い出し、言語化して、AIに最初から作らせる、データは頑張って移行する、というのでも十分ペイするのかも。
もちろんそうなる前に設計変更すりゃいいんですけれど、これからはそれが出来る人自体が減っていくように思われます。だいたいそれを学ぶ場がなくなっていくのは、これはもう確定的なシナリオですし。であるならば、むしろやるべきことは、最初から作り直せる状態に留めること、なのかもしれません。つまり規模であったり、データ形式であったり、アカウントの範囲であったり、影響範囲であったり、そういったやり直すために必要な勘所を抑えて、スクラップビルドが現実的な範囲でアプリケーションを有効に活用していく、そういう対応が有効なのかなぁと、そんなことを思いました。
と言いつつ、僕はそんなめんどくさいことは嫌なので、自分の作るものについては、継ぎ接ぎが破綻しないよう考えながらやります。実際、一からはすごいしんどいと思いますよ。壊す必要がなければそれが一番ですからね。一応ニューゲームも現実的に対抗可能な範囲であるとして、最終手段の保険といったところでしょうか。
コメント