4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに(導入)

業務で BFF(Backend For Frontend) を実装するために、TypeScript(以下 TS)を書く機会がありました。

「フロントからリクエストを受け取り、バックエンドへ流してレスポンスを返す」

――処理の流れ自体は抽象的には理解できていました。

ただ、実際のコードを見ると何をしているのか全く分からない

研修で Java はある程度書けるようになっていたので、バックエンドのコードは読めます。しかし BFF は TS。

.pipe(
  map((res) => res.data),
  catchError(catchErrorHandler),
)

これを初めて見たとき、正直な感想は

「アラビア語を見ているのと大差ない」

でした…

私たちの現場では

  • GraphQL
  • RxJS
  • TypeScript

を組み合わせた構成を採用しています。
TS の基礎すらない状態では、概念が一気に押し寄せてきて理解が追いつかない

そんな中、エンジニアの友人から言われた一言も刺さりました。

「これからエンジニアとしてやっていくなら、TS と Python は必須だよ」

今はそんな焦りから、少し解放されたので
今回はかなり 限定的なテーマ ではありますが、

  • TS が読めない
  • Observable が何をしているのか分からない

そんな 過去の自分 のような人の助けになればと思い、
フロントからの処理の流れ と、その中で使われる Observable について深掘りしていきます。

また、現在うちの現場では Observableは不要だという話題もあがっていたので、その真意についても考察しています。


フロントからの入力値を受け取る際には何が起きているのか

まずは 超ざっくりした流れ です。

  1. フロントエンドが API を呼ぶ
  2. BFF がリクエストを受け取る
  3. BFF がバックエンド API を呼ぶ
  4. バックエンドのレスポンスを加工する
  5. フロントへ返す

BFF の役割は、

  • フロントに最適な形に変換する
  • バックエンドの複雑さを隠蔽する

ことです。

ここまでは Java の Controller + Service とかなり似ています。

問題は 「レスポンスをどう扱っているか」

TS の BFF では、

  • Promise
  • Observable(RxJS)

という仕組みがよく登場します。


TSの基礎:Promise を理解する

まずは Promise からです。

Promise は一言でいうと、

「将来、値が返ってくることを約束するオブジェクト」

です。

// fetchUser関数はPromiseを返す
// Promise = 「将来値が返ってくることを約束するオブジェクト」
function fetchUser(): Promise<string> {
  return new Promise((resolve, reject) => {
    // 1秒後にresolveで値を返す
    // この瞬間にPromiseは完了状態(fulfilled)になる
    // もしここで reject(error) だった場合は完了状態(rejected)になる
    setTimeout(() => {
      resolve("user-data"); // ←Promiseの完了を決定する箇所
    }, 1000);
  });
}

// fetchUserを呼び出す
const promise = fetchUser();

// thenで値を受け取る
// ここ以外で定義したものに加工はできない
// Promiseは一度完了すると再度新しい値を流すことはできない
promise
  .then((data) => {
    // data = "user-data"
    // ここで初めて値を受け取り、加工や次の処理が可能
    return data.toUpperCase();
  })
  .then((result) => {
    // result = "USER-DATA"
    console.log(result);
  });

// 別のthenでも同じ結果が使えるが、新しい値は自動では流れない
promise.then((data) => console.log("別のthenでも同じ値:", data));

説明

  • resolve() / reject() が呼ばれた時点で Promise は完了状態になる
  • .then() は「完了した結果を受け取るコールバック」であり、ここで初めて値を加工可能
  • Promise は一度完了したら、新しい値を自動的には流さない
  • そのため「値を受け取った処理はここ以外では加工できない」という制約がある

まとめ

  • Promise = 1回きりの結果
  • 値を受け取るのは .then() の中だけ
  • それ以外では加工・再利用はできない

Observableとは何か(感覚的な理解)

Observable は、

「データが流れてくる蛇口」

だと考えると分かりやすいです。

  • 水道:蛇口をひねるまで水は出ない
  • Observable:subscribe するまでデータは流れない

また、水がいつ出てくるか分からないように、Observable から値が流れてくるタイミングも 非同期 です。

emit(エミット)とは?

emit とは、

Observable が新しい値を下流に流すこと

です。Observable の中で next() が呼ばれるたびに、新しい値が emit されます。


Observableを定義して pipe で流してみる

import { Observable, map, filter } from 'rxjs';

// Observable = 「データが流れてくる蛇口」
const source$ = new Observable<number>((observer) => {
  observer.next(1); // emit: 値を下流に流す
  observer.next(2); // emit
  observer.next(3); // emit
  observer.complete(); // ストリームの終了
});

// pipeで加工1: 値を10倍にする
const multiplied$ = source$.pipe(
  map((value) => value * 10) // mapで加工
);

// pipeで加工2: 偶数だけ残す
// ※ ここが Promise ではできない操作(途中で加工や分岐を追加できない)
const even$ = multiplied$.pipe(
  filter((value) => value % 20 === 0) // filterで加工
);

// subscribeで値を受け取る
even$.subscribe((result) => {
  console.log(result); // 20, 40
});

ポイント:

  • Observable は 定義しただけでは何も起きない
  • subscribe した瞬間に処理が始まる
  • pipe は「加工ライン」

pipe は Observable を加工する場所

流れを図にすると次のようになります。

[入力ストリーム] → pipe(map, catchError) → [出力ストリーム]

  • 入力ストリーム:元の Observable
  • pipe:データ加工レーン
  • 出力ストリーム:加工後の新しい Observable

pipe の中でやっているのは、
値そのものを加工しているのではなく、
Observable(ストリーム)を別の Observable に変換している という点です。

map / catchError の役割

  • map: 流れてきた値を別の形に変えて下流に流す(フィルターや加工)
  • catchError: ストリームの途中でエラーが起きた場合に別の流れに切り替える

なぜ自分たちの現場では Observable が不要となったのか

いつかの会議で

「あれ、うちの現場ってこのメリット、ほとんど使ってないのでは?」

という疑問が浮かびました。

Observable の代表的なユースケース:

  • 複数 API を並列で呼ぶ
  • 非同期処理を合成する
  • ストリーム的に加工する

しかし、私たちの現場では、

  • 複数 API の集約・加工は バックエンド側で完結
  • BFF は 1つの API を呼んで返すだけ

という構成でした。

つまり、

BFF で複雑な非同期処理を組み立てる必要がなかった

のです。

この構成であれば、

  • 非同期処理は1回
  • レスポンスも1回

なので、Promise で十分ということなのではないかと思いました。


それでも Observable が使われていた理由(考察)

では、

「Observable は完全に不要なのか?」

という疑問が浮かびます。

私たちのプロジェクトでは、datarest(自動生成コード)が Observable を返す設計になっていました。

なぜこの設計が選ばれたのかは、正直なところ 断定できません

ただ、コードや構成を眺める中で、次のような意図があったのではないかと考えました。

  • プロジェクト全体で RxJS を使う前提を揃えたかった
  • 将来 BFF の責務が増える可能性を見据えていた
  • エラーハンドリングやレスポンス加工の書き方を統一したかった

あくまで推測ですが、「今は不要でも、設計として Observable を選ぶ」という判断だったのかもしれません。

現在はこの設計をした方は現場にいませんが、
なぜ当時 Observable を選んだのか、いつか直接聞いてみたいテーマです。


まとめ

  • BFF はフロントとバックエンドの通訳
  • Promise は「1回きりの結果」を扱うもの
  • Observable + pipe は「流れてくるデータを、どう加工して渡すか」を扱う仕組み
  • Observable が不要に見えたのは、設計上その強みが活きなかっただけ

最初は本当に意味不明でしたが、

Observable はデータの川

と捉えたことで、コードが読めるようになりました。

この記事が、

  • TS が読めなくてつらい人
  • Observable が怖い人

の助けになれば嬉しいです。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?