「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > ハニーストーン/コンテキストを使用したマルチテナント アプリケーションの構築

ハニーストーン/コンテキストを使用したマルチテナント アプリケーションの構築

2024 年 8 月 18 日に公開
ブラウズ:877

Laravel の新しいコンテキスト ライブラリと混同しないでください。このパッケージは、マルチコンテキスト、マルチテナント アプリケーションの構築に使用できます。ほとんどのマルチテナント ライブラリには基本的に単一の「テナント」コンテキストがあるため、複数のコンテキストが必要な場合は少し面倒になる可能性があります。この新しいパッケージはその問題を解決します。

例を見てみましょう?

プロジェクト例

このサンプル アプリケーションでは、チームに編成されたグローバル ユーザー ベースがあり、各チームには複数のプロジェクトがあります。これは、多くの Software as a Service アプリケーションで非常に一般的な構造です。

マルチテナント アプリケーションでは、テナント コンテキスト内に各ユーザー ベースが存在することは珍しくありませんが、このサンプル アプリケーションでは、ユーザーが複数のチームに参加できるようにしたいため、グローバル ユーザー ベースとなります。
グローバル ユーザー ベースとテナント ユーザー ベースの図

Building a multi-tenant application with honeystone/context

SaaS として、チームが請求対象エンティティ (つまりシート) となり、特定のチーム メンバーにチームを管理する権限が付与される可能性があります。ただし、この例では実装の詳細には立ち入りませんが、追加のコンテキストが提供されることを願っています。

インストール

この投稿を簡潔にするために、Laravel プロジェクトの開始方法については説明しません。これについては、公式ドキュメントをはじめ、より優れたリソースがすでにたくさんあります。ユーザー、チーム、プロジェクトモデルを備えた Laravel プロジェクトがすでにあり、コンテキスト パッケージの実装を開始する準備ができていると仮定しましょう。

インストールは簡単なコンポーザのコマンドです:

composer install honeystone/context

このライブラリには便利な関数 context() があり、Laravel 11 の時点では Laravel 独自の context 関数と衝突します。これは実際には問題ではありません。関数をインポートすることもできます:

use function Honestone\Context\context;

または、Laravel の依存関係注入コンテナーを使用します。この投稿では、関数をインポートし、それに応じて使用していることを前提とします。

モデル

チーム モデルを構成することから始めましょう:

belongsToMany(User::class);
    }

    public function projects(): HasMany
    {
        return $this->hasMany(Project::class);
    }
}

チームには名前、メンバー、プロジェクトがあります。アプリケーション内では、チームのメンバーのみがチームまたはそのプロジェクトにアクセスできます。

それでは、プロジェクトを見てみましょう:

belongsTo(Team::class);
    }
}

プロジェクトには名前があり、チームに属します。

コンテキストの決定

誰かが私たちのアプリケーションにアクセスすると、その人がどのチームやプロジェクトで作業しているのかを判断する必要があります。物事を簡単にするために、ルート パラメーターを使用してこれを処理しましょう。また、認証されたユーザーのみがアプリケーションにアクセスできると仮定します。

チームでもプロジェクトのコンテキストでもない: app.mysaas.dev
チームコンテキストのみ: app.mysaas.dev/my-team
チームとプロジェクトのコンテキスト: app.mysaas.dev/my-team/my-project

ルートは次のようになります:

Route::middleware('auth')->group(function () {

    Route::get('/', DashboardController::class);

    Route::middleware(AppContextMiddleware::Class)->group(function () {

        Route::get('/{team}', TeamController::class);
        Route::get('/{team}/{project}', ProjectController::class);
    });
});

これは、名前空間が衝突する可能性があるため、非常に柔軟性に欠けるアプローチですが、例は簡潔に保たれています。実際のアプリケーションでは、これを少し異なる方法で処理する必要があるでしょう。おそらく、anothersaas.dev/teams/my-team/projects/my-project または my-team.anothersas.dev/projects/my-project.

のようになります。

まず AppContextMiddleware を見てみましょう。このミドルウェアはチーム コンテキストを初期化し、設定されている場合はプロジェクト コンテキストを初期化します:

route('team');
        $request->route()->forgetParameter('team');

        $projectId = null;

        //if there's a project, pull that too
        if ($request->route()->hasParamater('project')) {

            $projectId = $request->route('project');
            $request->route()->forgetParameter('project');
        }

        //initialise the context
        context()->initialize(new AppResolver($teamId, $projectId));
    }
}

まず、ルートからチーム ID を取得し、ルート パラメーターを忘れます。パラメーターがコンテキスト内に入ったら、コントローラーにパラメーターを渡す必要はありません。プロジェクト ID が設定されている場合は、それもプルします。次に、チーム ID とプロジェクト ID (または null) を渡す AppResolver を使用してコンテキストを初期化します:

require('team', Team::class)
            ->accept('project', Project::class);
    }

    public function resolveTeam(): ?Team
    {
        return Team::with('members')->find($this->teamId);
    }

    public function resolveProject(): ?Project
    {
        return $this->projectId ?: Project::with('team')->find($this->projectId);
    }

    public function checkTeam(DefinesContext $definition, Team $team): bool
    {
        return $team->members->find(context()->auth()->getUser()) !== null;
    }

    public function checkProject(DefinesContext $definition, ?Project $project): bool
    {
        return $project === null || $project->team->id === $this->teamId;
    }

    public function deserialize(array $data): self
    {
        return new static($data['team'], $data['project']);
    }
}

ここでもう少し続きます。

define() メソッドは、解決されるコンテキストを定義します。チームは必須であり、チーム モデルである必要があります。また、プロジェクトは受け入れられ (つまり、オプション)、プロジェクト モデル (または null) である必要があります。

resolveTeam() は初期化時に内部的に呼び出されます。チームまたは null を返します。 null 応答の場合は、ContextInitializer によって CouldNotResolveRequiredContextException がスローされます。

resolveProject() も初期化時に内部的に呼び出されます。プロジェクトまたは null を返します。この場合、プロジェクトは定義で必要とされていないため、null 応答でも例外は発生しません。

チームとプロジェクトを解決した後、ContextInitializer はオプションの checkTeam() メソッドと checkProject() メソッドを呼び出します。これらのメソッドは整合性チェックを実行します。 checkTeam() では、認証されたユーザーがチームのメンバーであることを確認し、checkProject() では、プロジェクトがチームに属していることを確認します。

最後に、すべてのリゾルバーには deserialization() メソッドが必要です。このメソッドは、シリアル化されたコンテキストを復元するために使用されます。これは最も顕著なのは、コンテキストがキューに入れられたジョブで使用される場合に発生します。

アプリケーション コンテキストが設定されたので、それを使用する必要があります。

コンテキストへのアクセス

いつものように、少し不自然ではありますが、シンプルにしていきます。チームを表示するときは、プロジェクトのリストを確認する必要があります。この要件を処理するために TeamController を次のように構築できます:

projects;

        return view('team', compact('projects'));
    }
}

十分簡単です。現在のチーム コンテキストに属するプロジェクトがビューに渡されます。より専門的なビューを得るためにプロジェクトをクエリする必要があると想像してください。これを行うことができます:

id)
            ->where('name', 'like', "%$query%")
            ->get();

        return view('queried-projects', compact('projects'));
    }
}

少し面倒になってきており、チームごとにクエリの「スコープ」を設定することをうっかり忘れてしまいがちです。これは、プロジェクト モデルの BelongsToContext 特性を使用して解決できます:

belongsTo(Team::class);
    }
}

すべてのプロジェクト クエリがチーム コンテキストによってスクープされ、現在のチーム モデルが新しいプロジェクト モデルに自動的に挿入されます。

コントローラーを単純化しましょう:

get();

        return view('queried-projects', compact('projects'));
    }
}

以上です、皆さん

ここからは、アプリケーションを構築するだけです。コンテキストは簡単に入手でき、クエリのスコープが設定され、キューに入れられたジョブは、ディスパッチされたのと同じコンテキストに自動的にアクセスできるようになります。

ただし、コンテキスト関連の問題がすべて解決されたわけではありません。おそらく、検証ルールに少しコンテキストを与えるために、いくつかの検証マクロを作成する必要があるでしょう。また、手動クエリにはコンテキストが自動的に適用されないことを忘れないでください。

次のプロジェクトでこのパッケージを使用する予定がある場合は、ぜひご連絡ください。フィードバックと貢献はいつでも大歓迎です。

追加のドキュメントについては、GitHub リポジトリをチェックアウトできます。私たちのパッケージが役に立ったと思われる場合は、星を付けてください。

次回まで...


この記事はもともとハニーストーン ブログに投稿されたものです。私たちの記事が気に入ったら、さらに多くのコンテンツをチェックすることを検討してください。

リリースステートメント この記事は次の場所に転載されています: https://dev.to/piranhageorge/building-a-multi-tenant-application-with-honeystonecontext-3eob?1 侵害がある場合は、[email protected] に連絡して削除してください。
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3