Dúvidas sobre o Curso Laravel Multi-Tenancy Multi Database
Olá, Professor Carlos!
Gostaria de sua oriantação sobre duas questões:
1. Preciso criar um sistema multi-tenancy multi database em laravel 10, o qual identificará o enquilino pelo CNPJ, a partir de uma rota específica, tipo "meudominio/tenancy=CNPJ". Sei que o curso aqui da academy faz a identificação a partir do domínio, mas gostaria de saber se, seguindo as aulas do curso, é possível adapatar a lógica para o modelo que eu preciso?
2. Como faço para ter mais de um banco de dados mysql no mesmo ambiente utilizando o docker? Adiciono mais uma configuração de environment no docker-composer?
Desde já, agradeço os esclarecimentos.
Olá, Adriel!
Tudo bem?
1) É super possível, como tem o tenant via query param, você consegue pegar o CNPJ no middleware facilmente:
$cnpj = request()->get('tenant');
2) Dentro do mesmo banco de dados, você pode ter N databases, sem problema algum.
Uso e recomendo este setup aqui, vai te ajudar demais: https://github.com/especializati/setup-docker-laravel
OK, Professor!
Muito obrigado pelas dicas.
Bom dia, Carlos!
Iniciei o curso de multi-tenant multi-database, estou nas primeiras aulas e implementando conforma a minha lógica de negácio - conforme explicado em mensagem anterior. Surgio então a seguinte dúvida: estando no escopo global de arrays de middleware do arquivo Kernel.php, o middlewire Tenant sempre irá interceptar todas as requisições, correto? Como a minha regra de negácio está no uso do CNPJ ao invés do domínio como chave para encontrar a Company, terei eu sempre que passar o CNPJ como parâmetro em todas as Urls do meu sistema?
Tipo:
meudominio/tenancy=CNPJ // index
meudominio/users/tenancy=CNPJ // lista de usários
e assim sucessivamente?
Sempre vai precisar o CNPJ como query param ?tenancy=CNPJ
--
Sua aplicação é uma API?
Se for, pode sempre obrigar a enviar e tenant como query param, ou no header, tanto faz.
Se não for uma API, você pode optar por armazenar em sessão o cnpj. Pegou a ideia amigo?
Boa note, professor!
Minha aplicação não é uma API, mas compreendi perfeitamente a sua dica.
Irei implementar aqui, conforme sugerido.
Obrigado!
Olá, professor!
Fiz a implementação utilizando sessão, mas está ocorrendo um erro estranho, o qual não consegui resolver.
Estarei disponibilizando o meu código no bitbucket também, caso ajude:
https://bitbucket.org/lumen-public/multi-tenancy-multi-database/src/master/
Importante:
a) Estou usando docker-compose, livewire e jetstream.
b) A rota principal para o sistema é http://localhost:8989/companies-login?companyKey=ddbea206-1a7c-4686-af94-f1878cdaf68a, passando como key um uuid, para localizar a company no banco principal.
Descrição dos passos que segui:
1. Criei um helper para testar se existe uma company na sessão.
2. No TenantMiddlewire, verifico se não existe uma key 'company' na sessão, caso não exista, realizo o passo a passo para buscar os dados da company, faço as verificações de rota, etc. Estando tudo ok, crio uma key 'company' na sessão com a company encontrada no banco principal de minha aplicação.
Caso já exista na sessão uma key company, apenas instâcio o ManagerTenant com os dados dessa company e segue o fluxo.
Segue um trecho do código:
public function handle(Request $request, Closure $next): Response
{
try {
if (!checkIfCompanyTenantItsOnSession()) {
$companyKey = $request->get('companyKey');
$company = $this->getCompany($companyKey);
if ((!$company || $company->company_status === StatusEnum::DESATIVADO->value)
&& $request->url() !== route('company.404-notFound')) {
return redirect()->route('company.404-notFound');
}
if ($request->url() !== route('company.404-notFound')) {
Session::put('company', $company);
Session::save();
app(ManagerTenant::class)->setConnection(
Session::get('company')->toArray()
);
}
} else {
app(ManagerTenant::class)->setConnection(
Session::get('company')->toArray()
);
}
} catch (Exception $exception) {
Log::error( __CLASS__ . ' - ' . $exception->getMessage());
}
return $next($request);
}
3. O ManagerTenant fiz algumas alterações, para pegar os dados na sessão.
public function setConnection(array $company_session = null): void
{
try {
DB::purge('mysql_tenant');
if (!is_null($company_session)) {
config()->set('database.connections.mysql_tenant.host', $company_session['db_hostname']);
config()->set('database.connections.mysql_tenant.database', $company_session['db_database']);
config()->set('database.connections.mysql_tenant.port', '3306');
config()->set('database.connections.mysql_tenant.username', $company_session['db_username']);
config()->set('database.connections.mysql_tenant.password', $company_session['db_password']);
$connection = DB::reconnect('mysql_tenant');
}
...
}
4. Aqui é que está a confusão:
- Se falo um dd() tenho os dados da conexão e até consigo realizar a consulta no banco de dados do tenant corretamente, com excessão do Schema::connection('mysql_tenant')->getConnection()->reconnect() que sempre retorna NULL.
Se tiro o dd(), o fluxo segue e tenho o seguinte erro: Call to a member function prepare() on null
Verifiquei que esse erro vem da conexão com banco, mas não consigo entender onde isso realmente acontece.
Obs.: Os bancos foram criados corretamente, inclusive com o recurso de command, conforme as aulas orientam.
Desde já, agradeço a sua ajuda.
Ajudei um outro aluno com um erro semelhante, no caso da aplicação dele, precisei remover o DB::purge('mysql_tenant');
Apenas removendo isso já deu certo, zerou esse erro "Call to a member function prepare() on null"
Faz isso e me diga o resultado por favor.
Olá, Carlos!
Ao comentar o DB::purge(), resolveu o erro "Call to a member function prepare() on null".
Porém, agora, me deparo com outro problema :)
Criei um component Livewire para fazer o login (estou usando Jetstream), e até aqui tudo ok, pois o sistema já está conectando no banco de dados secundário.
Quando executo o método de login, o sistema faz a autenticação, mas ao invés de ser redirecionar para a página dashboard, sou levado para a página de erro 404, pois, pelo que percebi, mesmo autenticando, aparentemente, o Auth fica null e perco os dados da sessão.
Tens alguma dica de como resolver mais essa bronca?
https://bitbucket.org/lumen-public/multi-tenancy-multi-database/src/master/
Boa, um problema à menos.
--
A tabela de users, está em qual base?
Se estiver na base principal, está aqui o problema, porque sempre que faz o refresh, tenta buscar o usuário autenticado, no caso, vai tentar buscar na base do tenant conectado. Me dê mais detalhes que penso em uma forma de resolver o seu case.
Então, meu projeto está estruturado da seguinte maneira (lembrando que estou usando jetstream e livewire):
- databse -> migrations -> tables da base principal (users, password_reset_tokens, add_two_factor_columns_to_users, failed_jobs, personal_access_tokens, sessions e companies).
- databse -> migrations -> tenants_migrations -> tables das bases secundárias (instituicoes, users, password_reset_tokens, add_two_factor_columns_to_users, failed_jobs, personal_access_tokens e sessions).
Segue minha lógica:
1. Para escolher a company desejada, acesso a rota principal do sistema, passando como key um uuid , para localizar a company no banco principal é http://localhost:8989/companies-login?companyKey=ddbea206-1a7c-4686-af94-f1878cdaf68a, .
Então, meu projeto está estruturado da seguinte maneira (lembrando que estou usando jetstream e livewire):
- databse -> migrations -> tables da base principal (users, password_reset_tokens, add_two_factor_columns_to_users, failed_jobs, personal_access_tokens, sessions e companies).
- databse -> migrations -> tenants_migrations -> tables das bases secundárias (instituicoes, users, password_reset_tokens, add_two_factor_columns_to_users, failed_jobs, personal_access_tokens e sessions).
Segue minha lógica:
1. Para escolher a company desejada, acesso a rota principal do sistema, passando a key uuid da company: http://localhost:8989/companies-login?companyKey=ddbea206-1a7c-4686-af94-f1878cdaf68a.
2. O sistema encontra a company, salva na sessão e me leva para a tela de login-company, que eu implementei em livewire.
3. Preencho o formulário com email e senha e submeto-o: no backend, caso o usuário que está tentando acessar o sistema exista no banco da company (que por sinal está conectado corretamente até aqui), faz o login e redireciona para uma página dashboard, criada por mim.
Erro:
no passo três o sistema valida o usuário corretamente (já testei usando dd()), porém, quando realizado o redirect(), ele perde os dados da company na sessão, e também não salva em sessão o Auth. Logo, me redireciona para a página de erro 404.
Segue meu código no Login:
public function login()
{
sleep(2);
$this->validate();
try {
$user = User::with('instituicao')
->where('email', $this->email)
->where('status_user', StatusEnum::ATIVO)
->first();
if (!$user || ! Hash::check($this->senha, $user->password)) {
session()->flash('message', "As credênciais informadas não conferem. <br> Por favor, tente novamente.");
}
$credentials = [
'email' => $this->email,
'password' => $this->senha
];
if (Auth::attempt($credentials)) {
return redirect()->route('dashboard.companies');
}
} catch (\Exception $exception) {
Log::error($exception->getMessage());
session()->flash('message', "As credênciais informadas não conferem. <br> Por favor, tente novamente.");
}
}
Código no Bitbucket: https://bitbucket.org/lumen-public/multi-tenancy-multi-database/src/master/
Acredito que o seu problema é por conta do guard (middleware) auth.
Em algum momento chega a recuperar o usuário?
Porque isso faz buscar o usuário no banco, e como ele está apenas no banco principal, não encontra e surge os erros.
Dá uma olhada nesses pontos, se não conseguir resolver, vou precisar tirar um tempinho maior para fazer um check-up geral no seu projeto e te ajudar a identificar o problema.
Precisa estar logado para conseguir responder a este ticket!
Clique Aqui Para Entrar!