BT

Windows Workflow における手動アクティビティの実装

| 作者 Boris Lublinsky フォローする 1 人のフォロワー , 翻訳者 伊藤 幸博 フォローする 0 人のフォロワー 投稿日 2009年3月8日. 推定読書時間: 25 分 |

Windows Workflow はビジネスプロセスを実装するための優れたフレームワークです。1つ欠けているのはヒューマンアクティビティ(人的な作業を伴う処理)の直接的なサポートです。この問題を解決するいくつかのアプローチが Microsoft の刊行物 [1 2] に見受けられますが 、一般的に使用できるほど包括的なものではありません。本稿では WF における完全に包括的なヒューマンアクティビティの実装へのアプローチの1つを定義したいと思います。

人間の相互作用をサポートすることの複雑性は複合的な課題を顕在化させます。そのうちのいくつかは以下のようなものです。

  • ユーザの応答の持続(ユーザアクティビティの実行時間)が予測不可能。
  • リクエストが発生した時点でユーザがシステムに接続されていない場合があるため、リクエストを保存しておいてユーザがログインした時点で提示する必要がある。
  • 異なるマシン上にホストされ同時に実行される複数のワークフローが存在する場合があるが、概してユーザは自分のタスクを全てまとめて閲覧したい。

これらの課題は業界においては認識されてきており、これらの問題の解決を目指した2つの主要な仕様がもたらされました [3 4]。私たちはその仕様の文書ではなく精神を引き継いだ実装の構築を試みたいと思います。

ソリューションのコンポーネント概観

ソリューション全体の主なコンポーネントを図1に示します。

図1 ソリューションのコンポーネント

ソリューションの中心に作業キューマネージャ (Work Queue Manager) があります。これはシステムの全ユーザの全てのタスクを把握する中央集権型サービスです。人の手による処理を必要とするいずれのワークフロー (Workflow) も(あるいはワークフローを含むサービス/アプリケーションも)、作業キューマネージャにそれらを永続化してシステムの他のコンポーネントが連動できるようにリクエストを送出するカスタムワークフローアクティビティ [5] を呼び出します。これによって作業キューマネージャはワークフローエンジンとヒューマンアクティビティの実行との間を分離するレイヤとなり、ワークフローの実行中にユーザがシステム内に存在しないというケースをサポートします。また作業キューマネージャが中央集権型のサービスであることにより、プロセスがどこから起動されたかによらず全てのタスクを所定のユーザと結びつける事が可能になります。異なるユーザタスクは異なる入力情報を必要としまた異なる出力を行う場合があるため、どのようなリクエストおよびレスポンスも包括的に処理可能とするためワークフローとヒューマンアクティビティの間の通信は XML による入出力となっています。XML を使用することは実装を複雑にする要因になるように思われるかもしれませんが、.Net で実装されている XML シリアル化の優れたサポート [6] によって XML とオブジェクトをマッピングするのは非常に簡単です。作業キュービューア (Work Queue Viewer) はユーザが入力可能な全てのタスクを閲覧することができる GUI アプリケーションです。これは標準的で、名前、種別、優先度、作成情報等のタスクの基本的な要素のみを表示するアプリケーションです。ユーザはこの情報に基づいてキュー内のどのタスクを処理するか決定できます。実際のタスクの処理は指定されたタスクに特有の機能をサポートするタスクアプリケーション (Task Application) によって行われます。作業キューメンテナンス (Work Queue Maintenance) アプリケーションは作業キューマネージャの管理をサポートする UI を提供します。これは既存のヒューマンタスクの表示と修正、履歴の表示などが可能です。最後にヒューマンアクティビティ (Human Activity) は、作業キューマネージャとの通信を実装し、ワークフロー開発者にヒューマンタスク実行のための非常にシンプルなプログラミングモデルを提供するカスタムアクティビティ(補足の "Windows Workflow Foundation コンポーネントモデル" を参照)です。この点から見れば、ユーザの相互作用は通常のサービス呼び出しと違いがありません。

Windows Workflow Foundation コンポーネントモデル

[11] で説明されているように、Windows Workflow Foundation (WWF) の実装は今日のワークフロー実装で優勢となっている実行可能なワークフロー言語(ドメイン言語)とはかなり異なっています。WWF では "プロセスグラフ内のアクティビティは、汎用のプログラミング言語内でそのアクティビティの実行時の振る舞いを実装するコンポーネントとリンクされます。処理言語内の各アクティビティ種別はそれぞれ1つの実装コンポーネントに対応します。例えばウェブサービス呼び出しアクティビティ、ヒューマンタスクアクティビティ、Eメールアクティビティは全て1つの実装コンポーネントに対応します。

結果として、特定のケース(我々のケースではヒューマンタスクアクティビティ)で要求される実行時の振る舞いを実装する新しいアクティビティ種別を導入して WWF を拡張するのは極めて容易になります。このアプローチにより新しいアクティビティの実装による新規構築や既存の処理言語の拡張がかなり簡単になっています。

各コンポーネントの相互作用の全体像をシーケンス図(図2)に示します。

図2 シーケンス図

上記からもわかるように、ソリューション全体には以下の2種類のコンポーネントが含まれます。

  • 汎用の、ヒューマンアクティビティ、作業キューマネージャおよび作業キュービューア。これらのコンポーネントはヒューマンタスクの "標準の" 属性に作用し、タスクの入出力を汎用の XML として取り扱います。
  • 特定の、ワークフローそのもの、および特定のビジネスオブジェクトの XML シリアル化/デシリアル化を実装し自身の動作のためにそれらのオブジェクトを使用するタスク処理ビジネスアプリケーション。

本稿ではソリューションの汎用コンポーネントについてのみ説明したいと思います。

作業キューマネージャ

サービスインターフェースと機能

先に示したように、作業キューマネージャは中央集権型のサービスです。その機能はデータコントラクトをベースにしており、図3に示す通りになっています。

図3 作業キューマネージャのデータコントラクト

このデータモデルの主な構成要素は以下の通りです。

  • Human task - このデータ型は([3] に従って)以下のようなヒューマンタスクの主要な要素を定義します。
    • TaskID - タスクの一意な識別子。
    • Task Name - 人間が読める形式のタスクの識別子(一意ではない)。
    • Task status - タスクの状態。取り得るタスクの状態は TaskStatusEnum データ要素で定義され、待機 (Ready)、予約 (Reserved)、完了 (Completed) のいずれかになります。簡略化されたタスクの状態遷移図を図4に示します。

      図4 タスクの状態遷移図

      タスクが作業キューマネージャに送出された時点では待機状態になります。アプリケーションから実行をリクエストされると予約状態に遷移します。アプリケーションがタスク処理を完了するとタスクは完了状態に移行し、作業キューマネージャにヒューマンアクティビティへタスクの完了を知らせる応答を返すようにシグナルを送ります。あるいはもしタスク処理がキャンセルされるかタイムアウトになった場合は、タスクは待機状態に戻ります。
       
    • Task priority はタスク作成時に指定しそのタスクの重要度を表すために使用されます。作業キュービューアは、タスクの優先度に基づいて、より高い優先度のタスクをキューから上位に配置して表示します。
    • CreatedOn と CreatedBy 属性はタスク作成時のタイムスタンプとそのタスクを作成したユーザを示します。それらはキュー内のタスクの処理順序を決めるために使用できます。
    • Task type はタスクの処理に適したビジネスアプリケーションを選択するために使用されるタスクの種別です。現時点ではワークフロー名称とタスク名称の組み合わせ(TaskType データ要素)を採用しています。タスク種別を相当するデータ型に分離することによってタスク種別の定義方法の変更(もし必要になった場合)が容易になっています。
    • ReservedBy および ReservedOn は特定のタスクにロックをかけて同時に1人のユーザだけが作業を行うことができることを保証するために使用されます。
    • CompletedBy および CompletedOn 要素はタスク処理に影響を与えるものではなく、保守アプリケーション用の記帳サービスおよび/あるいはレポート用のものです。
    • Potential Owners はこのタスクを表示したり作業することを許可されているユーザのリストを指定します。現在は潜在ユーザ(タスクの所有者になる可能性のあるユーザ)は特定のユーザもしくはユーザのグループ(User データ型)として定義可能です。
    • Escalations はタスクに適用可能なエスカレーションのリストを識別します。いずれのエスカレーション (Escalation データ型)も通知 (Notification データ型)あるいは再割り当て(Reassignment データ型)として定義することができます。両エスカレーション型ともエスカレーション適用後のタイムアウト(タスク作成からのタイムアウト)とEメールアドレス(通知のため)と潜在ユーザの新しいリスト(再割り当てのため)を含みます。現時点の実装ではエスカレーションはサポートしていません。
    • Escalated フラグはタスクのエスカレーションが行われたかどうかを識別します。
    • Task request および TaskReply フィールドはタスクのリクエストおよび応答が文字列化されたものを格納します。
  • CallbackInfo は、作業キューマネージャがタスクの実行が完了したことを通知するために必要となる情報を指定するためにヒューマンアクティビティによって使用されるデータ型です。この型には作業キューマネージャプロセスのコンシューマ種別[10] に加え、サービスのカテゴリ、名称およびバージョンが含まれます。 この情報を実際の終点アドレス、バインディングおよびバインディングパラメータへ解決する実装はサービスレジストリに依存します。
  • WorkflowParameters データ型は適切なワークフローインスタンスの適切な場所への返答の経路を決定するため使用されます。これにはワークフロー ID(ワークフローのインスタンスを識別する)およびコリレーション (ワークフローのステップ/アクティビティを識別する)が含まれます(カスタムヒューマンアクティビティの実装はワークフローキューに基づいているため、現時点ではワークフローキュー名称をコリレーションとして使用しています)。
  • HumanTaskView は、HumanTask データ型のサブセットを表し、作業キュービューアアプリケーションにタスクに関する情報を返却するために使用されるデータ型です。
  • WorkItemQuery データ型は作業キュービューアアプリケーションによって現在のタスクに関する情報をリクエストするために使用されます。
  • MaintenanceQuery データ型は保守アプリケーションによってヒューマンタスクを表示するためのクエリを指定するために使用されます。

これらのデータ型に加えて、データモデルは図5に示すような例外データ型を定義します。

図5 例外データ型

作業キューマネージャには3つのサービスのグループ - ワークフローサービス、ユーザサービスおよびメンテナンスサービスがあります。

ワークフローサービス(図6)はヒューマンタスクアクティビティによってタスクの実行を提出する際に使用されるサービスと、作業キューマネージャが実行完了のシグナルを送るために使用する、ワークフローサービスによって提供されるべきインターフェースを定義しています。これら2つのサービスはワークフロー内で実行されるヒューマンアクティビティと作業キューマネージャとの間を統合します。

図6 ワークフローサービス

作業キューマネージャによって実装される SubmitForExcecution サービスは、多数の異なるワークフローの一部となり得るヒューマンアクティビティからのリクエストを受信します。リクエストは一度受信されると更なる処理のためにデータベースに保存されます。

CompleteExecutionContract はここで定義されてはいますが、ワークフローによって実装されます。このサービスはワークフローに特定のタスクの実行が完了して処理の続行が可能であるというシグナルを送るために使用されます。

ユーザサービス(図7)はユーザと作業キューマネージャとの相互作用のサポートのために実装されます。WorkWithItems サービスは以下の3つのオペレーションをサポートします。

  • GetItemsList メソッドは作業キュービューアによって使用され、指定されたユーザの '待機' 状態にある(任意の種別の)タスクのリストを返却します。
  • WorkWithItem メソッドはビジネスアプリケーションが処理しようとするタスクに関する完全な情報を取得するために使用されます。その際ユーザの資格情報はこのタスクの潜在ユーザのリストと比較され、もしユーザがこのグループに属していない場合は NotAuthorized 例外が返却されます。さらにこのリクエストはタスクの状態変更を引き起こします -  一旦ビジネスアプリケーションがタスクの処理を開始すると状態が Reserved へ変更され、ReservedOn および ReservedBy フィールドにはしかるべき情報が書き込まれます。このシンプルな予約(ロック)機構により、ある時点においてただ1人のユーザのみがタスクを処理していることを保証します。もしビジネスアプリケーションが予約されたタスクにアクセスしようとしている場合は、タスクが他のユーザによって処理中であることを示す例外がオペレーションによって返却されます(例外はユーザおよび彼/彼女がタスクの処理を開始した時刻に関する情報を提供します)。タスクの無限ロックを回避するため、サービスはロックを失効させます。もし30分以上 Reserved の状態にあったタスクに対してタスクの一覧取得あるいはタスク処理のリクエストを受け取った場合、このタスクの状態は Ready に変更されます。
  • CompleteExecution メソッドはビジネスアプリケーションによって作業キューマネージャにタスク処理が完了したというシグナルを送るために使用されます。ビジネスアプリケーションは完了あるいは中断のどちらかを示す CompletionStatus を返却します。もし返却された状態が完了の場合、タスクの実行状態は Completed に変更され、ヒューマンアクティビティタスクの完了シグナルを送るためのサービスが呼び出されます。返却された状態が中断だった場合は、タスクの状態は Ready に変更されます。

図7 ユーザサービス

メンテナンスサービス(図8)は作業キューマネージャの保守アプリケーションのサポートのために実装されます。

図8 メンテナンスサービス

ViewTasks メソッドは MaintenanceQuery データ型で定義されるフィルタを使用して既存のタスクのリストを返却します。タスクは閲覧後に修正が可能で、UpdateTask メソッドを用いて作業キューマネージャに変更内容を返却することができます。

データベースの基本構成とデータアクセス

サービスのデータベース設計(図9)にはヒューマンタスクに関連する全ての情報を永続化するために必要なテーブルが含まれます。メインのテーブル - HumanTask テーブルはタスク名称、ID(サービスによって割り当てられる)、優先度、そして誰がいつこれらのイベントを実行したかという情報で補完されるタスクのライフサイクルイベントなどの基本的なヒューマンタスクの情報が全て格納されます。

図9 作業キューマネージャデータベース

他の付加的テーブルには以下のような付加的なタスクの情報が格納されます。

  • Callback および TaskCallback テーブル。Callback テーブルにはワークフロープロセスによって実装されヒューマンタスクマネージャからのリクエストを待ち受けているサービスに関する情報が収められます。設計はレジストリの使用に依存しているため、サービスの終点アドレスの解決と情報のバインドを行うのに十分なサービスの名前とバージョンを使用しています。複数のヒューマンタスクが同じコールバックを利用できるようにするため、別個のテーブルとして実装しています。TaskCallback テーブルは HumanTask テーブルと Callback テーブルを結合する交差テーブルです。
  • TaskType および TaskTaskType テーブル。TaskType テーブルにはタスクの種別に関する情報が収められます。同じ種別の多数のヒューマンタスクが存在し得るので、この情報は別個のテーブルに分けています。TaskTaskType テーブルは HumanTask テーブルと TaskType テーブル間の交差テーブルです。
  • WorkflowReference テーブルにはヒューマンタスクとワークフローの特定のインスタンス/アクティビティとの結びつけを可能にするための情報が収められます。ワークフローのインスタンス ID と、インスタンスによって使用されるワークフローキュー(後述)の名称が含まれます。この構造は将来的に変更される可能性があるため、この情報は別個のテーブルに分けています。HumanTask と WorkflowReference の間のリレーションシップは常に1対1なので、このケースでは交差テーブルを使用せず、外部キーを使用して WorkflowReference テーブルと HumanTask テーブルを結合しています。
  • PotentialUser および TaskPotentialUser。どのタスクも潜在ユーザのリストを持つ事ができ、またシステム内のどのユーザも処理権限を与えられたタスクのリストを持つ事ができます。従って、PotentialUser テーブルの情報はユーザの把握に使用し、TaskPotentialUser はユーザとタスクを結合する交差テーブルとなります。
  • Notification、Reassignment、TaskNotification、TaskReassignment および ReassignmentPotentialUser テーブル。これらのテーブルは将来エスカレーションが実装された際にそれをサポートします。

データアクセスを最適化するため、ストアドプロシージャとビューもいくつか実装しています。ストアドプロシージャは以下のようなものです。

  • AddTaskCallback はコールバック情報を追加して(後述の InsertTaskCallback を使用して)指定されたヒューマンタスクと関連付けるストアドプロシージャです。最初に与えられたコールバックが既に存在するかどうかチェックし、もし存在する場合は既存のコールバックレコードを使用し、無い場合は Callback テーブルに新しいレコードを作成します。その後リンクを作成するため InsertTaskCallback ストアドプロシージャを呼び出します。
  • InsertTaskCallbackTaskCallback テーブルにデータを投入するストアドプロシージャで、与えられたコールバックとヒューマンタスクの間のリレーションシップを作成します。
  • AddTaskType はタスク種別情報を追加し(後述の InsertTaskTypeTask を使用して)指定されたヒューマンタスクと関連付けるストアドプロシージャです。最初に与えられたタスク種別が既に存在するかどうかチェックし、もし存在する場合は既存のタスク種別レコードを使用し、無い場合は TaskType テーブルに新しいレコードを作成します。その後リンクを作成するため InsertTaskTypeTask ストアドプロシージャを呼び出します。
  • InsertTaskTypeTask は TaskTaskType テーブルにデータを投入するストアドプロシージャで、与えられたタスク種別とヒューマンタスクの間のリレーションシップを作成します。
  • AddTaskPotentialOwner は潜在ユーザの情報を追加し(後述の InsertTaskPotentialUser を使用して)指定されたヒューマンタスクと関連付けるストアドプロシージャです。最初に与えられた潜在ユーザが既に存在するかどうかチェックし、もし存在する場合は既存の潜在ユーザのレコードを使用し、無い場合は PotentialUser テーブルに新しいレコードを作成します。その後リンクを作成するため InsertTaskPotentialUser ストアドプロシージャを呼び出します。
  • InsertTaskPotentialUserTaskPotentialUser テーブルにデータを投入するストアドプロシージャで、与えられた潜在ユーザとヒューマンタスクの間のリレーションシップを作成します。

これらのストアドプロシージャを利用することで、重複の排除によってデータベースのデータ量を最小化しつつ、データベースのアクセス数はおよそ半分に減少します。

データベースのビュー(図10)によってデータベースの読み込みアクセスが簡略化されます。指定されたユーザに関する全てのタスク情報(タスク種別を含む)が単一のビューから取得することができます。

図10 完全なタスクのビュー

リポジトリパターンを実装する永続化レイヤのクラス図を図11に示します。

図11 永続化クラス図

サービス実装

サービスの実装は全般的にとてもわかりやすいものになっています。一旦特定のメソッドが呼び出されると、そのメソッドは必要なデータベースアクセスを全て実装するリポジトリクラス(図11)に実行を委譲します。

ヒューマンアクティビティ

ヒューマンアクティビティは人間の相互作用を実装するカスタムワークフローアクティビティです。このアクティビティはワークフローデザイナから作業キューマネージャとの通信を隠蔽し、ユーザの相互作用を通常のサービス呼び出しと同様に扱う事ことを可能にします。

このアクティビティの実装はアクティビティと外界との間の非同期通信を提供するワークフローキュー [7] に基づいており、アクティビティはキューにメッセージの受け取りを登録し、サービスはキューにメッセージを送信します。カスタムアクティビティは外部イベントの処理および非同期アクティビティの実行完了の伝達のためにこのモデルを使用することができます。これによりアクティビティはある程度までの実行の後、実行再開の刺激が与えられるまで待機することができます。これらの実装に関する全体的な相互作用を図12に示します。

図12 ワークフローキューを用いたアクティビティの実装

アクティビティは一旦開始すると可能な限り実行を継続します。外部実行が必要な時点に達すると、アクティビティはワークフローキューに登録します。その後ワークフローインスタンスは待機状態に入り不動態化(永続化)が可能になります。外部実行が完了するとキューに情報が追加されます。この時点でランタイムは実行を継続するワークフローインスタンスを再活性化します。

実際のヒューマンアクティビティの実装は2つの部分 - アクティビティ自身とコールバックサービスで構成されます。

ヒューマンアクティビティはヒューマンタスクマネージャに新しいヒューマンタスク処理を開始するようにメッセージを送信します。このサービスの呼び出しが成功した場合、アクティビティは自身をワークフローキューに登録し待機状態になります。もしサービスの実行で何らかのエラーが発生した場合、アクティビティはワークフローが処理可能なワークフロー例外をスローします。

サービスは返答を受け取るとアクティビティの作業キューへその返答を追加します。キューメッセージは返答をキューから取り除くアクティビティを活性化し、実行を終了します。

コールバックサービスは CompleteExecutionContract(図6)を実装し、ワークフローを実装するサービス/アプリケーションによって開始されなければなりません。このサービスを正しく動作させるためには、WorkflowRuntime が設定されている必要があります。これには executionComplete クラスの公開静的変数を使用することができます。

ヒューマンアクティビティの実行のためには幾つかのパラメータ - タスク種別、優先度などが必要になります。これらのパラメータは DependencyProperty [8] として定義されます。DependencyProperty はワークフローデザイナ [9] によって設定されるよう公開が可能なため、プログラミングを行うことなく視覚的にアクティビティパラメータを設定することができます(図13)。

図13 アクティビティパラメータの設定

 

作業キュービューア

作業キュービューアはユーザコントロールとして実装されており(図14)、どのようなフォーム上にも配置することができます。このコントロールはデータビューコントロールを実装し、パラメータとしてデリゲートを受け取ります。与えられた行のいずれかのセルがクリックされるとタスク ID とタスク種別と共にデリゲートが呼び出されます。図14はメッセージボックスをポップアップするだけのシンプルなデリゲートを表示しています。

図14 作業キュービューア

まとめ

ビジネスプロセスの完全な自動化の推進にもかかわらず、ヒューマンアクティビティはビジネスプロセスの実装において未だ重要な役割を果たしており、またこれからも果たし続けるでしょう。本稿の始めに示したように、ユーザの相互作用を導入することによって、ワークフローの実装とは直接的には関係ない多数の追加的な懸案事項が表面化します。本稿で説明したように実装を完全に切り離すことにより、ワークフロー開発とユーザ相互作用の開発との間で関心を分離することができます。加えて、ヒューマンタスクマネージャによるヒューマンタスクのサポートの中央集権化によって、特定ユーザのタスク管理の集約が簡単になります。

謝辞

本稿の執筆を共に手助けしてくれた Paul Rovkah氏と Rob Sheldon氏に心より感謝の意を表します。

参考文献

1 Jeremy Boyd. Integrating Windows Workflow Foundation and Windows Communication Foundation MSDN January 2007. 

 [日本語版]
    Windows Workflow Foundation と Windows Communication Foundation の統合
    (http://msdn.microsoft.com/ja-jp/library/bb266709.aspx)
2 Windows Workflow Foundation Web Workflow Approvals Starter Kit?Microsoft Downloads.
3 Web Services Human Task (WS_HumanTask).
4 WS BPEL extensions for People
5 Matt Milner. Build Custom Activities To Extend The Reach Of Your Workflows MSDN Magazine, December 2006,
6 Dare Obasanjo XML Serialization in the .NET Framework January 2003,

  [日本語版]
    .NET Framework での XML シリアル化
    (http://msdn.microsoft.com/ja-jp/library/ms950721.aspx)
7 Serge Luca. Using the Windows Workflow Foundation Queuing system
8 Glenn Block Attached Properties and the Workflow Designer
9 Dennis Pilarinos. Getting DependencyProperty RegisterAttached properties to appear in the Property Browser redux
10 B.Lublinsky, Implementing a Service Registry for .NET Web Services (参考資料). January 2008, InfoQ,

  [翻訳版]
    .NET Webサービス向けのサービスレジストリの実装
    (http://www.infoq.com/jp/articles/net-service-registry)
11 Tom Baeyens. Process Component Models: The Next Generation In Workflow? (参考資料) February 2008, InfoQ.

  [翻訳版]
    プロセスコンポーネントモデルは次世代のワークフローか?
    (http://www.infoq.com/jp/articles/process-component-models)

 

原文はこちらです:http://www.infoq.com/articles/lublinksy-workqueue-mgr
(このArticleは2008年5月5日に原文が掲載されました)

この記事に星をつける

おすすめ度
スタイル

こんにちは

コメントするには InfoQアカウントの登録 または が必要です。InfoQ に登録するとさまざまなことができます。

アカウント登録をしてInfoQをお楽しみください。

あなたの意見をお聞かせください。

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする
コミュニティコメント

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

HTML: a,b,br,blockquote,i,li,pre,u,ul,p

このスレッドのメッセージについてEmailでリプライする

ディスカッション

InfoQにログインし新機能を利用する


パスワードを忘れた方はこちらへ

Follow

お気に入りのトピックや著者をフォローする

業界やサイト内で一番重要な見出しを閲覧する

Like

より多いシグナル、より少ないノイズ

お気に入りのトピックと著者を選択して自分のフィードを作る

Notifications

最新情報をすぐ手に入れるようにしよう

通知設定をして、お気に入りコンテンツを見逃さないようにしよう!

BT