Construire une application multi-tenant avec Honeystone/Context

PHPz
Libérer: 2024-08-12 15:11:25
original
738 Les gens l'ont consulté

À ne pas confondre avec la nouvelle bibliothèque de contexte de Laravel, ce package peut être utilisé pour créer des applications multi-contextes multi-locataires. La plupart des bibliothèques multi-locataires ont essentiellement un seul contexte « locataire », donc si vous avez besoin de plusieurs contextes, les choses peuvent devenir un peu délicates. Ce nouveau package résout ce problème.

Regardons un exemple, d'accord ?

Exemple de projet

Pour notre exemple d'application, nous aurons une base d'utilisateurs globale organisée en équipes et chaque équipe aura plusieurs projets. Il s'agit d'une structure assez courante dans de nombreuses applications Software as a Service.

Il n'est pas rare que les applications multi-locataires aient chaque base d'utilisateurs existant dans un contexte de locataire, mais pour notre exemple d'application, nous voulons que les utilisateurs puissent rejoindre plusieurs équipes, il s'agit donc d'une base d'utilisateurs globale.
Diagramme de la base d'utilisateurs globale et de la base d'utilisateurs locataire

Building a multi-tenant application with honeystone/context

En tant que SaaS, il est probable que l'équipe soit l'entité facturable (c'est-à-dire le siège) et que certains membres de l'équipe soient autorisés à gérer l'équipe. Je n’entrerai cependant pas dans les détails de la mise en œuvre dans cet exemple, mais j’espère que cela fournira un contexte supplémentaire.

Installation

Pour garder cet article concis, je n'expliquerai pas comment démarrer votre projet Laravel. Il existe déjà de nombreuses meilleures ressources disponibles pour cela, notamment la documentation officielle. supposons simplement que vous avez déjà un projet Laravel, avec des modèles d'utilisateur, d'équipe et de projet, et que vous êtes prêt à commencer à implémenter notre package de contexte.

L'installation est une simple recommandation du compositeur :

composer install honeystone/context
Copier après la connexion

Cette bibliothèque a une fonction pratique, context(), qui, à partir de Laravel 11, entre en conflit avec la propre fonction contextuelle de Laravel. Ce n'est pas vraiment un problème. Vous pouvez soit importer notre fonction :

use function Honestone\Context\context;
Copier après la connexion

Ou utilisez simplement le conteneur d’injection de dépendances de Laravel. Tout au long de cet article, je supposerai que vous avez importé la fonction et que vous l'utilisez en conséquence.

Les modèles

Commençons par configurer notre modèle d'équipe :

belongsToMany(User::class); } public function projects(): HasMany { return $this->hasMany(Project::class); } }
Copier après la connexion

Une équipe a un nom, des membres et des projets. Au sein de notre application, seuls les membres d'une équipe pourront accéder à l'équipe ou à ses projets.

D'accord, regardons donc notre projet :

belongsTo(Team::class); } }
Copier après la connexion

Un projet a un nom et appartient à une équipe.

Déterminer le contexte

Lorsque quelqu'un accède à notre application, nous devons déterminer dans quelle équipe et dans quel projet il travaille. Pour simplifier les choses, traitons cela avec les paramètres d'itinéraire. Nous supposerons également que seuls les utilisateurs authentifiés peuvent accéder à l'application.

Ni contexte d'équipe ni de projet :app.mysaas.dev
Contexte d'équipe uniquement :app.mysaas.dev/my-team
Contexte de l'équipe et du projet :app.mysaas.dev/my-team/my-project

Nos itinéraires ressembleront à ceci :

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); }); });
Copier après la connexion

Il s'agit d'une approche très rigide, étant donné le potentiel de conflits d'espaces de noms, mais elle permet de garder l'exemple concis. Dans une application réelle, vous souhaiterez gérer cela un peu différemment, peut-être anothersaas.dev/teams/my-team/projects/my-project ou my-team.anothersas.dev/projects/my-project.

Nous devrions d'abord examiner notre AppContextMiddleware. Ce middleware initialise le contexte de l'équipe et, s'il est défini, le contexte du projet :

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)); } }
Copier après la connexion

Pour commencer, nous récupérons l'identifiant de l'équipe de l'itinéraire, puis oublions le paramètre d'itinéraire. Nous n’avons pas besoin que le paramètre atteigne nos contrôleurs une fois qu’il est dans le contexte. Si un identifiant de projet est défini, nous le extrayons également. Nous initialisons ensuite le contexte à l'aide de notre AppResolver en passant notre identifiant d'équipe et notre identifiant de projet (ou null) :

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']); } }
Copier après la connexion

Il se passe un peu plus ici.

La méthode definition() est chargée de définir le contexte à résoudre. L'équipe est obligatoire et doit être un modèle d'équipe, et le projet est accepté (c'est-à-dire facultatif) et doit être un modèle de projet (ou nul).

resolveTeam() sera appelé en interne lors de l'initialisation. Il renvoie l'équipe ou null. En cas de réponse nulle, l'exception CouldNotResolveRequiredContextException sera levée par ContextInitializer.

resolveProject() sera également appelé en interne lors de l'initialisation. Il renvoie le projet ou null. Dans ce cas, une réponse nulle n'entraînera pas d'exception car le projet n'est pas requis par la définition.

Après avoir résolu l'équipe et le projet, ContextInitializer appellera les méthodes facultatives checkTeam() et checkProject(). Ces méthodes effectuent des contrôles d'intégrité. Pour checkTeam() nous nous assurons que l'utilisateur authentifié est membre de l'équipe, et pour checkProject() nous vérifions que le projet appartient à l'équipe.

Finally, every resolver needs a deserialization() method. This method is used to reinstate a serialised context. Most notably this happens when the context is used in a queued job.

Now that our application context is set, we should use it.

Accessing the context

As usual, we’ll keep it simple, if a little contrived. When viewing the team we want to see a list of projects. We could build our TeamController to handle this requirements like this:

projects; return view('team', compact('projects')); } }
Copier après la connexion

Easy enough. The projects belonging to the current team context are passed to our view. Imagine we now need to query projects for a more specialised view. We could do this:

id) ->where('name', 'like', "%$query%") ->get(); return view('queried-projects', compact('projects')); } }
Copier après la connexion

It’s getting a little fiddly now, and it’s far too easy to accidentally forget to ‘scope’ the query by team. We can solve this using the BelongsToContext trait on our Project model:

belongsTo(Team::class); } }
Copier après la connexion

All project queries will now be scooped by the team context and the current Team model will be automatically injected into new Project models.

Let’s simplify that controller:

get(); return view('queried-projects', compact('projects')); } }
Copier après la connexion

That’s all folks

From here onwards, you’re just building your application. The context is easily at hand, your queries are scoped and queued jobs will automagically have access to the same context from which they were dispatched.

Not all context related problems are solved though. You’ll probably want to create some validation macros to give your validation rules a little context, and don’t forget manual queries will not have the context automatically applied.

If you’re planning to use this package in your next project, we’d love to hear from you. Feedback and contribution is always welcome.

You can checkout the GitHub repository for additional documentation. If you find our package useful, please drop a star.

Until next time..


This article was originally posted to the Honeystone Blog. If you like our articles, consider checking our more of our content over there.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!