Laravel의 새로운 컨텍스트 라이브러리와 혼동하지 마세요. 이 패키지는 다중 컨텍스트 다중 테넌트 애플리케이션을 구축하는 데 사용할 수 있습니다. 대부분의 다중 테넌트 라이브러리에는 기본적으로 단일 '테넌트' 컨텍스트가 있으므로 여러 컨텍스트가 필요한 경우 상황이 약간 까다로워질 수 있습니다. 이 새로운 패키지는 그 문제를 해결합니다.
예제를 살펴볼까요?
예제 애플리케이션의 경우 팀으로 구성된 글로벌 사용자 기반이 있으며 각 팀에는 여러 프로젝트가 있습니다. 이는 많은 SaaS(Software as a Service) 애플리케이션에서 상당히 일반적인 구조입니다.
다중 테넌트 애플리케이션에서 각 사용자 기반이 테넌트 컨텍스트 내에 존재하는 것은 드문 일이 아니지만, 예제 애플리케이션에서는 사용자가 여러 팀에 참여할 수 있기를 원하므로 글로벌 사용자 기반입니다.
글로벌 사용자 기반과 테넌트 사용자 기반 다이어그램
SaaS에서는 팀이 청구 가능 개체(예: 좌석)가 되고 특정 팀 구성원에게 팀을 관리할 수 있는 권한이 부여될 가능성이 높습니다. 이 예에서는 이러한 구현 세부 사항을 자세히 다루지는 않지만 추가 컨텍스트를 제공할 수 있기를 바랍니다.
이 게시물을 간결하게 유지하기 위해 Laravel 프로젝트를 시작하는 방법에 대해서는 설명하지 않겠습니다. 공식 문서뿐만 아니라 이미 이를 위한 더 나은 리소스가 많이 있습니다. 사용자, 팀 및 프로젝트 모델이 포함된 Laravel 프로젝트가 이미 있고 컨텍스트 패키지 구현을 시작할 준비가 되었다고 가정해 보겠습니다.
설치는 간단한 작곡가 추천입니다:
composer install honeystone/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와 프로젝트 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 저장소에서 확인할 수 있습니다. 우리 패키지가 유용하다고 생각하시면 별표를 남겨주세요.
다음 시간까지..
이 글은 원래 허니스톤 블로그에 게시된 글입니다. 우리 기사가 마음에 드신다면 거기에 있는 더 많은 콘텐츠를 확인해 보세요.
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3