[PROMOÇÃO] Assine com + 30% de desconto ANUAL MENSAL (últimas horas)
Adriel
Criador Adriel 22/05/2023

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.   

Manager Carlos Ferreira 22/05/2023

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

Carlos Ferreira
Criador Adriel 22/05/2023

OK, Professor!
Muito obrigado pelas dicas. 

Adriel
Criador Adriel 22/05/2023

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?

Adriel
Manager Carlos Ferreira 22/05/2023

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?

Carlos Ferreira
Criador Adriel 22/05/2023

Boa note, professor!

Minha aplicação não é uma API, mas compreendi perfeitamente a sua dica.
Irei implementar aqui, conforme sugerido.
Obrigado!

Adriel
Criador Adriel 22/05/2023

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.

 

Adriel
Manager Carlos Ferreira 22/05/2023

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.

Carlos Ferreira
Criador Adriel 22/05/2023

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/

Adriel
Manager Carlos Ferreira 22/05/2023

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.

Carlos Ferreira
Criador Adriel 22/05/2023

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, .



Adriel
Criador Adriel 22/05/2023

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/

Adriel
Manager Carlos Ferreira 22/05/2023

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.

Carlos Ferreira
Sabe a Solução? Ajude a resolver!

Precisa estar logado para conseguir responder a este ticket!

Clique Aqui Para Entrar!