Ivan Rossouw

コンポーネントは共有アセンブリ内でコンパイルできても、それをレンダリングするアプリケーションの1つで構築できないことがあります。

ブラウザと.NET MAUIホストが同じBlazorページを再利用する場合、これは見逃しやすいポイントです。機能は一度だけ存在するように見えますが、各ホストは依然として依存性注入コンテナ、スタートアップパス、ライフタイムモデルを所有しています。

最近コミットされた修正でこの問題が具体的に明らかになりました。共有ページに必須のワークフローの依存関係が追加されました。ネイティブホストは共通のブートストラップパスでこれを登録しました。ブラウザホストは別のコンポジションルートを通じて同じページをレンダリングしましたが、そこで依存関係は未知でした。ページはレンダリングされる前に失敗しました。

永続的な教訓は「もう1つの登録を忘れないこと」ではありませんでした。共有UIはそれをレンダリングできるすべてのホストに対する契約を作成するということでした。

アセンブリの共有はコンテナの共有を意味しない

コンパイル時の再利用は、複数のプロジェクトがコンポーネントを参照できるかどうかを問いかけます。ランタイムのコンポジションは、各ホストがそれを構築し、必要な動作を満たすことができるかどうかを問いかけます。

ネイティブホストはラッパーブートストラップを呼び出す一方、ブラウザホストはWeb指向のスタートアップパスを呼び出す場合があります。どちらも同じRazorコンポーネントをロードしますが、同じサービスグラフを構築するわけではありません。

これにより、有用なレビュー規則が生まれます。

共有コンポーネントに必要な注入は、それをレンダリングできるすべてのコンポジションルートに対する必要な契約です。

ホストのインベントリが重要です。「すべてのネイティブアプリはこのブートストラップを使用する」というだけでは、ブラウザ、デスクトップ、テスト、またはプレビューホストが別のルートでページをレンダリングする場合に十分ではありません。

不足しているホスト機能をオプショナリティで修復しない

1つのホストが依存関係を提供できない場合、それをnullableにすることは実用的だと感じるかもしれません。これは触覚フィードバックのようなオプショナルな機能強化には合理的です。しかし、依存関係がワークフローの不変条件を強制する場合には危険です。

レビューされた変更では、ワークフローは操作が進行中にランタイムコンテキストが同じままであるかどうかを知る必要がありました。ネイティブアプリケーションはこのコンテキストをランタイムに変更できました。ブラウザホストはこれを行うことができず、そのコンテキストはアプリケーションのライフタイムを通じて固定されていました。

オプショナリティは「このホストは安定性を異なる方法で表現する」ということを「このホストは安全チェックをスキップする可能性がある」ということに変えてしまいます。これらは同等ではありません。

より良い質問は、ワークフローが本当に必要とする最小限の機能は何だったのかということでした。

必要なのは3つの事実だけでした。

  • 現在のコンテキストを識別するリビジョン
  • コンテキストが作業の準備ができているかどうか
  • コンテキストが変更されたことを示すシグナル

ネイティブホストの完全なナビゲーション、ストレージ、またはUIサービスは必要ありませんでした。小さな契約が明示的になると、両方のホストが真実を述べることができました。

固定およびライブアダプタは1つの不変条件を満たすことができる

ブラウザの実装は固定可能でした。

  • そのリビジョンは決して変更されない
  • アプリケーションの起動時に準備完了
  • その変更シグナルは決して発火しない

ネイティブの実装はライブ可能でした。

  • そのリビジョンはランタイムコンテキストから取得される
  • 準備状態は選択が存在するかどうかを反映する
  • そのシグナルは実際のコンテキスト変更に従う

それらは異なる動作をしますが、同じ約束を保持します。1つのコンテキストで開始された作業は、そのコンテキストが変更された後にサイレントにコミットしてはならないということです。この抽象化は、プラットフォームの違いを消し去るのではなく、すべてのプラットフォームが尊重できる最小限の不変条件に名前を付けます。

ホストが実際にエントリする場所で契約を登録する

元の登録はネイティブアプリケーションが共有するブートストラップに存在していました。複数のホストがこれを呼び出すため中央に見えましたが、ページをレンダリングするすべてのホストにとって中央ではありませんでした。

修正により、共有ワークフローの契約は2つの実際のコンポジションパスに移動されました。

  • ブラウザパスは固定アダプタを登録
  • ネイティブパスはライブアダプタを登録
  • 両方とも必要な共有ワークフローを登録
  • ネイティブのみのラッパーは偶然の権限ではなくなった

重複した登録は実際の分割を文書化しています。同じワークフロー、異なるホストの真実です。共通の登録は同一のサービスにはまだ適しています。ホスト固有のアダプタは、ホストがそれらを選択する場所で可視性を保つべきです。

動作とコンポジションをテストする

この失敗はコンポーネントの動作が開始する前に発生するため、登録テストは有用です。

焦点を絞ったマトリックスでカバーできるもの。

  1. ブラウザのコンポジションルートには共有ワークフローと固定アダプタが含まれる
  2. ネイティブのコンポジションルートには共有ワークフローとライブアダプタが含まれる
  3. 古いプラットフォームのみのブートストラップは隠れた第3の権限ではない
  4. ワークフローはホストが真に固定されたコンテキストを持つ場合に成功する
  5. ワークフローはライブコンテキストが操作中に変更された場合に完了を拒否または修復する

記述子チェックは欠落した登録を迅速に検出します。動作テストはアダプタが不変条件を保持することを証明します。どちらも完全な本番ホストをアクティブ化しません。高価値のページの場合、各ホストの実際のプロバイダを構築し、ページ境界を構築するスモークテストを追加してください。これは記述子チェックが見逃すライフタイムまたは置換のミスを露呈する可能性があります。

トレードオフ

狭い契約アプローチは、インターフェース、2つのアダプタ、明示的な登録、およびより広いテストマトリックスを必要とします。

代替案はローカルでのみ安価です。

  • 広範なネイティブサービスはプラットフォームの懸念を共有ワークフローコードに漏洩させる
  • オプショナルな依存関係は安全不変条件をサイレントに無効化できる
  • 1つの大きすぎるブートストラップはどのホストがどの動作を所有しているかを隠す
  • アドホックチェックは名前付き契約なしで漂流する

私はワークフロー内の暗黙的な動作の違いよりも、コンポジション境界での小さく意図的な重複を好みます。

実用的なレビュー用チェックリスト

共有Blazorコンポーネントに必要な依存関係が追加された場合、以下を問いかけてください。

  • どのブラウザ、ネイティブ、デスクトップ、テスト、およびプレビューホストがこれをレンダリングできるか?
  • 各ホストはどのコンポジションルートを通じてエントリするか?
  • 依存関係はワークフローが必要とする機能か、それとも偶然受け取った大規模なプラットフォームサービスか?
  • すべてのホストが真実で非オプショナルな実装を提供できるか?
  • ホストの違いはnullブランチではなくアダプタで可視か?
  • テストはすべてのルートに対して登録と動作を実行するか?
  • 完全なプロバイダスモークテストはライフタイムまたは置換エラーを検出できるか?

共有UIは共有ランタイムトポロジの証拠ではありません。すべての必要な依存関係をクロスホスト設計決定として扱い、コンポジションルートをコンポーネントの実際のAPIの一部にしてください。

あなたのシステムで、静かに1つのホストしか持っていないと仮定している共有ページはどれですか?