”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 honeystone/context 构建多租户应用程序

使用 honeystone/context 构建多租户应用程序

发布于2024-08-18
浏览:219

不要与 Laravel 的新上下文库混淆,该包可用于构建多上下文多租户应用程序。大多数多租户库本质上都有一个“租户”上下文,因此如果您需要多个上下文,事情可能会变得有点麻烦。这个新包解决了这个问题。

让我们看一个例子好吗?

示例项目

对于我们的示例应用程序,我们将拥有一个组织成团队的全球用户群,每个团队将有多个项目。这是许多软件即服务应用程序中相当常见的结构。

对于多租户应用程序来说,每个用户群存在于租户上下文中并不罕见,但对于我们的示例应用程序,我们希望用户能够加入多个团队,因此它是全局用户群。
全球用户群与租户用户群图

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,我们也会提取它。然后,我们使用 AppResolver 传递团队 id 和项目 id(或 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']);
    }
}

这里还有更多内容。

define() 方法负责定义正在解析的上下文。团队是必需的并且必须是团队模型,并且项目已接受(即可选)并且必须是项目模型(或为空)。

resolveTeam() 将在初始化时在内部调用。它返回 Team 或 null。如果出现空响应,ContextInitializer 将抛出 CouldNotResolveRequiredContextException。

resolveProject() 也将在初始化时在内部调用。它返回项目或 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 存储库以获取其他文档。如果您觉得我们的套餐有用,请给个星星。

下次再见..


本文最初发布于 Honeystone 博客。如果您喜欢我们的文章,请考虑在那里查看我们的更多内容。

版本声明 本文转载于:https://dev.to/piranhageorge/building-a-multi-tenant-application-with-honeystonecontext-3eob?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何有效地执行JavaScript中的回调函数?
    如何有效地执行JavaScript中的回调函数?
    理解 JavaScript 中回调函数的本质在 JavaScript 中,回调函数提供了一种方便的机制,可以在另一个函数完成后执行一个函数它的执行。虽然概念很简单,但回调的最佳实现有时可能不清楚。让我们探讨一个简化的示例:var myCallBackExample = { myFirstFu...
    编程 发布于2024-11-06
  • Vue 框架简介
    Vue 框架简介
    What is Vue? from the Vue website Vue is a "progressive" JavaScript framework for building user interfaces. It works by build...
    编程 发布于2024-11-06
  • 逃离戏剧:为什么 HydePHP 是您的 WordPress 替代品
    逃离戏剧:为什么 HydePHP 是您的 WordPress 替代品
    WordPress 戏剧 随着 WordPress 生态系统面临前所未有的混乱,许多开发人员和网站所有者正在重新考虑他们的平台选择。最近 WordPress 联合创始人 Matt Mullenweg 和 WP Engine 之间的冲突凸显了 WordPress 社区内的控制、贡献和...
    编程 发布于2024-11-06
  • Go 中的并发模式;工作池和扇出/扇入
    Go 中的并发模式;工作池和扇出/扇入
    Go 以其卓越的并发模型而闻名,但许多开发人员只关注 goroutine 和通道。然而,工作池和扇出/扇入等并发模式提供了真正的效率。 本文将介绍这些高级概念,帮助您最大限度地提高 Go 应用程序的吞吐量。 为什么并发很重要 并发允许程序高效地执行任务,特别是在处理 I/O 操作、...
    编程 发布于2024-11-06
  • 如何在 C++ 中将单个字符转换为 std::string?
    如何在 C++ 中将单个字符转换为 std::string?
    从单个字符创建字符串人们可能会遇到需要将表示为 char 数据类型的单个字符转换为std::string。从字符串中获取字符很简单,只需在所需位置索引字符串即可。然而,相反的过程需要不同的方法。要从单个字符创建 std::string,可以使用多种方法:使用 std::string参数计数为 1:c...
    编程 发布于2024-11-06
  • JavaScript 变量名称中美元符号的含义是什么?
    JavaScript 变量名称中美元符号的含义是什么?
    JavaScript 变量名称中美元符号的意义在编程领域,命名约定的使用对于增强代码至关重要可读性并遵循最佳实践。在 JavaScript 中,美元符号 ($) 通常作为变量名称的前缀出现,特别是引用 jQuery 对象的变量名称。美元符号的用途是什么?与流行的看法相反,JavaScript 变量名...
    编程 发布于2024-11-06
  • 如何重新排列 CSS 网格布局中的列以实现移动响应?
    如何重新排列 CSS 网格布局中的列以实现移动响应?
    在 CSS 网格布局中重新排序列在 CSS 网格布局中,有多种技术可以修改列的顺序以实现具体布局。本问题探讨了重新排列移动布局列的可能性,例如将列移动到底部,同时在桌面布局上保持所需的列顺序。解决方案选项:grid-template-areas: 此属性允许您在网格内定义命名区域,然后将网格项分配给...
    编程 发布于2024-11-06
  • Hacktoberfest 周在线拍卖系统
    Hacktoberfest 周在线拍卖系统
    概述 在 Hacktoberfest 的第三周,我决定为一个较小但有前途的项目做出贡献:在线拍卖系统。尽管该项目仍处于早期阶段,但它已经显示出增长潜力,而且我看到了帮助改进其代码库的机会。我的任务是通过减少冗余代码和改进整体结构来重构项目,使其更具可维护性和可扩展性。 ...
    编程 发布于2024-11-06
  • 如何使用“exception_ptr”在 C++ 线程之间传播异常?
    如何使用“exception_ptr”在 C++ 线程之间传播异常?
    在 C 中的线程之间传播异常 当从主线程调用的函数生成多个线程时,就会出现在 C 中的线程之间传播异常的任务用于 CPU 密集型工作的工作线程。挑战在于处理工作线程上可能发生的异常并将其传播回主线程​​以进行正确处理。传统方法一种常见方法是手动捕获工作线程上的各种异常,记录它们的详细信息,然后在主线...
    编程 发布于2024-11-06
  • 如何使用 3D CSS 变换修复 Firefox 中的锯齿状边缘?
    如何使用 3D CSS 变换修复 Firefox 中的锯齿状边缘?
    使用 3D CSS 变换时 Firefox 中的锯齿状边缘与 Chrome 中使用 CSS 变换时的锯齿状边缘问题类似,Firefox 在 3D 变换中也出现了这个问题。背面可见性作为 Chrome 中的潜在解决方案,在 Firefox 中被证明无效。解决方法:要在 Firefox 中缓解此问题,您...
    编程 发布于2024-11-06
  • 为什么 PHP 的 mail() 函数给电子邮件发送带来挑战?
    为什么 PHP 的 mail() 函数给电子邮件发送带来挑战?
    为什么 PHP 的 mail() 函数达不到要求:限制和陷阱虽然 PHP 提供了 mail() 函数用于发送电子邮件,但它却失败了与专用库或扩展相比较短。以下是与使用 mail() 相关的缺点和限制的全面检查:格式问题:mail() 可能会遇到以下问题:标题和内容格式,尤其是操作系统之间的换行符差异...
    编程 发布于2024-11-06
  • 使用 npyConverter 简化 NumPy 文件转换
    使用 npyConverter 简化 NumPy 文件转换
    如果您使用 NumPy 的 .npy 文件并需要将其转换为 .mat (MATLAB) 或 .csv 格式,npyConverter 就是适合您的工具!这个简单的基于 GUI 的工具通过干净且用户友好的界面提供 .npy 文件的批量转换。 主要特点 批量转换:将目录下所有.npy文件...
    编程 发布于2024-11-06
  • 如何禁用特定线路的 Eslint 规则?
    如何禁用特定线路的 Eslint 规则?
    禁用特定行的 Eslint 规则在 JSHint 中,可以使用语法禁用特定行的 linting 规则: /* jshint ignore:start */ $scope.someVar = ConstructorFunction(); /* jshint ignore:end */对于 eslint...
    编程 发布于2024-11-06
  • 如何在没有错误的情况下将列表插入 Pandas DataFrame 单元格?
    如何在没有错误的情况下将列表插入 Pandas DataFrame 单元格?
    将列表插入 Pandas 单元格问题在 Python 中,尝试将列表插入 Pandas DataFrame 的单元格可能会导致错误或意想不到的结果。例如,当尝试将列表插入 DataFrame df 的单元格 1B 时:df = pd.DataFrame({'A': [12, 23], 'B': [n...
    编程 发布于2024-11-06
  • Matplotlib 中的“plt.plot”、“ax.plot”和“figure.add_subplot”之间的主要区别是什么?
    Matplotlib 中的“plt.plot”、“ax.plot”和“figure.add_subplot”之间的主要区别是什么?
    Matplotlib 中绘图、轴和图形之间的差异Matplotlib 是一个用于创建可视化的面向对象的 Python 库。它使用三个主要对象:图形、轴和绘图。图形图形表示将在其中显示可视化的整个画布或窗口。它定义画布的整体大小和布局,包括边距、背景颜色和任何其他全局属性。轴轴表示图中绘制数据的特定区...
    编程 发布于2024-11-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3