Minha arquitetura API no Laravel

Faz um tempo que não escrevo mais aqui, mas vamos lá, hoje vamos conversar sobre a arquitetura que eu uso no Laravel. A estrutura de pastas e arquivos que eu desenvolvi ao longo do tempo me ajudou a ganhar agilidade desenvolvendo no dia a dia. Estrutura de pasta projeto |--app | |--Infra | |--Models | |--Modules |--config | |--develop | |--prod Fora do padrão que temos do Laravel ao dar o start, tenho essa estrutura. Em Infra, deixo tudo que se aplica a escopo global da aplicação, como por exemplo, controllers a serem estendidos, validadores de requisições, enums, abstrações uteis (email, sentry), middle wares e outras coisas mais... Em Models, fica os models do Laravel, organizados por módulo, então se eu tenho um model de usuário e outro de endereço do usuário, ambos ficam na pasta User. Em módules fica todos os módulos separados por pasta, de forma clara a qual módulo estamos trabalhando. Dentro de cada módulo tenho as pastas referente a controllers, enum, use cases e demais itens específicos para esse módulo. As pastas develop e prod, eu deixo os docker files, docker compose, configurações do opcache, etc... Controllers Eu gosto de trabalhar com controllers basic para fazer todo o trabalho repetitivo. Dentro de Infra/Controllers tenho um controller basic para cada ação do CRUD. Exemplo de controller basic: abstract class BaseCreateController extends Controller { abstract protected function getUseCase(): ICreateUseCase; abstract protected function getRules(): array; abstract protected function getModelName(): string; public function __invoke(Request $request): JsonResponse { ForbiddenException::validatePolicy(GatesAbilityEnum::Create, $this->getModelName()); Validator::validateRequest($request, $this->getRules()); $result = $this->getUseCase()->execute($request->json()->all()); if ($result) { return ResponseApi::renderCreated($result); } return ResponseApi::renderInternalServerError('Erro ao inserir item.'); } } Por esse controller, já temos todas as tratativas para exceptions de request inválida e falta de acesso ao endpoint, tratados no middle ware do Laravel. O controller de um módulo fica assim: class ClientCreateController extends BaseCreateController { public function __construct(protected ClientCreateUseCase $useCase) { } protected function getUseCase(): ICreateUseCase { return $this->useCase; } protected function getRules(): array { return [ // regras do json de request ]; } protected function getModelName(): string { return Client::class; } } Use Cases Todo use case implementa uma interface, para garantir a comunicação correta com o controller. O use case do controller acima, ficaria assim: class ClientCreateUseCase implements ICreateUseCase { public function execute(array $data): array { $client = Client::create([ // campos ]); return $client->toArray(); } } Dessa forma, com poucos arquivos, temos uma estrutura robusta e fácil de se trabalhar, já que não precisarei me preocupar com padrão de response, validar request e acesso do usuário. Isso tudo já fica abstraído no controller base. O máximo que terei que fazer é criar e registrar policy para o módulo. Por hoje é só pessoal. Até a próxima.

Mar 25, 2025 - 14:13
 0
Minha arquitetura API no Laravel

Faz um tempo que não escrevo mais aqui, mas vamos lá, hoje vamos conversar sobre a arquitetura que eu uso no Laravel.

A estrutura de pastas e arquivos que eu desenvolvi ao longo do tempo me ajudou a ganhar agilidade desenvolvendo no dia a dia.

Estrutura de pasta

projeto
|--app
| |--Infra
| |--Models
| |--Modules
|--config
| |--develop
| |--prod

Fora do padrão que temos do Laravel ao dar o start, tenho essa estrutura.

Em Infra, deixo tudo que se aplica a escopo global da aplicação, como por exemplo, controllers a serem estendidos, validadores de requisições, enums, abstrações uteis (email, sentry), middle wares e outras coisas mais...

Em Models, fica os models do Laravel, organizados por módulo, então se eu tenho um model de usuário e outro de endereço do usuário, ambos ficam na pasta User.

Em módules fica todos os módulos separados por pasta, de forma clara a qual módulo estamos trabalhando. Dentro de cada módulo tenho as pastas referente a controllers, enum, use cases e demais itens específicos para esse módulo.

As pastas develop e prod, eu deixo os docker files, docker compose, configurações do opcache, etc...

Controllers

Eu gosto de trabalhar com controllers basic para fazer todo o trabalho repetitivo. Dentro de Infra/Controllers tenho um controller basic para cada ação do CRUD.

Exemplo de controller basic:

abstract class BaseCreateController extends Controller
{
    abstract protected function getUseCase(): ICreateUseCase;
    abstract protected function getRules(): array;
    abstract protected function getModelName(): string;

    public function __invoke(Request $request): JsonResponse
    {
        ForbiddenException::validatePolicy(GatesAbilityEnum::Create, $this->getModelName());
        Validator::validateRequest($request, $this->getRules());
        $result = $this->getUseCase()->execute($request->json()->all());
        if ($result) {
            return ResponseApi::renderCreated($result);
        }
        return ResponseApi::renderInternalServerError('Erro ao inserir item.');
    }
}

Por esse controller, já temos todas as tratativas para exceptions de request inválida e falta de acesso ao endpoint, tratados no middle ware do Laravel.

O controller de um módulo fica assim:

class ClientCreateController extends BaseCreateController
{
    public function __construct(protected ClientCreateUseCase $useCase)
    {
    }

    protected function getUseCase(): ICreateUseCase
    {
        return $this->useCase;
    }

    protected function getRules(): array
    {
        return [
            // regras do json de request
        ];
    }

    protected function getModelName(): string
    {
        return Client::class;
    }
}

Use Cases

Todo use case implementa uma interface, para garantir a comunicação correta com o controller.

O use case do controller acima, ficaria assim:

class ClientCreateUseCase implements ICreateUseCase
{
    public function execute(array $data): array
    {
        $client = Client::create([
            // campos
        ]);
        return $client->toArray();
    }
}

Dessa forma, com poucos arquivos, temos uma estrutura robusta e fácil de se trabalhar, já que não precisarei me preocupar com padrão de response, validar request e acesso do usuário. Isso tudo já fica abstraído no controller base.

O máximo que terei que fazer é criar e registrar policy para o módulo.

Por hoje é só pessoal. Até a próxima.