Erstellen einer mandantenfähigen Anwendung mit Honeystone/Kontext

PHPz
Freigeben: 2024-08-12 15:11:25
Original
737 Leute haben es durchsucht

Nicht zu verwechseln mit der neuen Kontextbibliothek von Laravel, dieses Paket kann zum Erstellen von Multi-Kontext-Multi-Tenant-Anwendungen verwendet werden. Die meisten mandantenfähigen Bibliotheken verfügen im Wesentlichen über einen einzigen „Mandanten“-Kontext. Wenn Sie also mehrere Kontexte benötigen, kann es etwas umständlich werden. Dieses neue Paket löst dieses Problem.

Schauen wir uns ein Beispiel an, oder?

Beispielprojekt

Für unsere Beispielanwendung verfügen wir über eine globale Benutzerbasis, die in Teams organisiert ist und jedes Team mehrere Projekte hat. Dies ist eine recht häufige Struktur in vielen Software-as-a-Service-Anwendungen.

Es ist nicht ungewöhnlich, dass bei mandantenfähigen Anwendungen jede Benutzerbasis in einem Mandantenkontext vorhanden ist, aber für unsere Beispielanwendung möchten wir, dass Benutzer mehreren Teams beitreten können, also eine globale Benutzerbasis.
Diagramm „Globale Benutzerbasis vs. Mandantenbenutzerbasis“

Building a multi-tenant application with honeystone/context

Als SaaS ist es wahrscheinlich, dass das Team die abrechenbare Einheit (d. h. der Sitz) ist und bestimmten Teammitgliedern die Erlaubnis erteilt wird, das Team zu verwalten. Ich werde in diesem Beispiel zwar nicht näher auf diese Implementierungsdetails eingehen, aber es bietet hoffentlich etwas zusätzlichen Kontext.

Installation

Um diesen Beitrag prägnant zu halten, werde ich nicht erklären, wie Sie Ihr Laravel-Projekt starten. Dafür stehen bereits viele bessere Ressourcen zur Verfügung, nicht zuletzt die offizielle Dokumentation. Nehmen wir einfach an, Sie haben bereits ein Laravel-Projekt mit Benutzer-, Team- und Projektmodellen und sind bereit, mit der Implementierung unseres Kontextpakets zu beginnen.

Die Installation ist ein einfaches Composer-Kommentar:

composer install honeystone/context
Nach dem Login kopieren

Diese Bibliothek verfügt über eine praktische Funktion, context(), die ab Laravel 11 mit der eigenen Kontextfunktion von Laravel kollidiert. Das ist eigentlich kein Problem. Sie können entweder unsere Funktion importieren:

use function Honestone\Context\context;
Nach dem Login kopieren

Oder verwenden Sie einfach den Dependency-Injection-Container von Laravel. In diesem Beitrag gehe ich davon aus, dass Sie die Funktion importiert haben und sie entsprechend verwenden.

Die Modelle

Beginnen wir mit der Konfiguration unseres Teammodells:

belongsToMany(User::class); } public function projects(): HasMany { return $this->hasMany(Project::class); } }
Nach dem Login kopieren

Ein Team hat einen Namen, Mitglieder und Projekte. Innerhalb unserer Anwendung können nur Mitglieder eines Teams auf das Team oder seine Projekte zugreifen.

Okay, schauen wir uns also unser Projekt an:

belongsTo(Team::class); } }
Nach dem Login kopieren

Ein Projekt hat einen Namen und gehört zu einem Team.

Den Kontext bestimmen

Wenn jemand auf unsere Anwendung zugreift, müssen wir feststellen, in welchem Team und Projekt er arbeitet. Der Einfachheit halber behandeln wir dies mit Routenparametern. Wir gehen außerdem davon aus, dass nur authentifizierte Benutzer auf die Anwendung zugreifen können.

Weder Team noch Projektkontext:app.mysaas.dev
Nur Teamkontext:app.mysaas.dev/my-team
Team- und Projektkontext:app.mysaas.dev/my-team/my-project

Unsere Routen werden in etwa so aussehen:

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); }); });
Nach dem Login kopieren

Angesichts der Möglichkeit von Namespace-Konflikten ist dies ein sehr unflexibler Ansatz, aber er hält das Beispiel prägnant. In einer realen Anwendung möchten Sie dies etwas anders handhaben, vielleicht anothersaas.dev/teams/my-team/projects/my-project oder my-team.anothersas.dev/projects/my-project.

Wir sollten uns zuerst unsere AppContextMiddleware ansehen. Diese Middleware initialisiert den Teamkontext und, falls festgelegt, den Projektkontext:

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)); } }
Nach dem Login kopieren

Zunächst holen wir uns die Team-ID aus der Route und vergessen dann den Routenparameter. Wir brauchen nicht, dass der Parameter unsere Controller erreicht, sobald er im Kontext ist. Wenn eine Projekt-ID festgelegt ist, ziehen wir diese ebenfalls. Anschließend initialisieren wir den Kontext mithilfe unseres AppResolvers und übergeben dabei unsere Team-ID und unsere Projekt-ID (oder 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']); } }
Nach dem Login kopieren

Hier ist noch ein bisschen mehr los.

Die Methode define() ist für die Definition des aufzulösenden Kontexts verantwortlich. Das Team ist erforderlich und muss ein Teammodell sein, und das Projekt wird akzeptiert (d. h. optional) und muss ein Projektmodell (oder null) sein.

resolveTeam() wird bei der Initialisierung intern aufgerufen. Es gibt das Team oder null zurück. Im Falle einer Nullantwort wird die CouldNotResolveRequiredContextException vom ContextInitializer ausgelöst.

resolveProject() wird bei der Initialisierung auch intern aufgerufen. Es gibt das Projekt oder null zurück. In diesem Fall führt eine Nullantwort nicht zu einer Ausnahme, da das Projekt gemäß der Definition nicht erforderlich ist.

Nachdem das Team und das Projekt aufgelöst wurden, ruft der ContextInitializer die optionalen Methoden checkTeam() und checkProject() auf. Diese Methoden führen Integritätsprüfungen durch. Bei checkTeam() stellen wir sicher, dass der authentifizierte Benutzer Mitglied des Teams ist, und bei checkProject() prüfen wir, ob das Projekt zum Team gehört.

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')); } }
Nach dem Login kopieren

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')); } }
Nach dem Login kopieren

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); } }
Nach dem Login kopieren

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')); } }
Nach dem Login kopieren

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.

Das obige ist der detaillierte Inhalt vonErstellen einer mandantenfähigen Anwendung mit Honeystone/Kontext. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!