Skip to content

Instantly share code, notes, and snippets.

@brendoLoR
Created June 7, 2025 23:31
Show Gist options
  • Save brendoLoR/a24ae6212c7c56b81565c1397d936e48 to your computer and use it in GitHub Desktop.
Save brendoLoR/a24ae6212c7c56b81565c1397d936e48 to your computer and use it in GitHub Desktop.

Considerações acerca do projeto https://github.com/vexpenses/backend-test

Nesse documento irei abordar aspectos técnicos e arquiteturais da aplicação. Será utilizada uma abordagem analítica, demonstrando os principais problemas encontrados e propondo possíveis soluções para eles.

A analise está dividida em 3 grupos:

  1. Considerações iniciais
  2. Code-review
  3. Problemas globais

Apesar de longo, não é uma leitura pesada ou massiva, grande parte do volume é composto por snippets de código inseridos ao longo do texto.

Então vamos iniciar!


Considerações iniciais

Testes:

Os testes do sistema cobrem os principais endpoints, e certificam do cumprimento de algumas regras de negócio, como qual perfil de usuário pode executar determinada ação no sistema.

Porém, senti falta de testes dedicados a camada de Domínio. Por se tratar de um centralizador de regras de negócio, se torna um ponto crucial do sistema, que deveria ser testada mais a fundo. Ou, ao menos, que os testes end-to-end fossem guiados para certificar o funcionamento correto das classes dessa camada. Por exemplo: existem no sistema, na camada de domínio, o Domain de Card, responsáveis por garantir que o catão é único no sistema, que o usuário é valido. O teste tests/Feature/Card/RegisterTest.php valida apenas se o cartão pode ser criado uma única vez, sem testar caminhos alternativos ao happy-path

Essas validações não são certificadas nos testes existêntes

    protected function findAccountId(): void
    {
        $account = (new FindByUser($this->userId))->handle();

        if (is_null($account)) {
            throw new InternalErrorException(
                'ACCOUNT_NOT_FOUND',
                161001001
            );
        }

        $this->accountId = $account['id'];
    }
    protected function checkExternalId()
    {
        if (!(new CanUseExternalId($this->cardId))->handle()) {
            throw new InternalErrorException(
                'Não é possível vincular esse cartão',
                0
            );
        }
    }

Além da ausência de testes significativos, ao rodar os testes uma série de erros de constraint de valor foram disparadas.

Migrations e base de dados

Em algumas migrations é usada a constraint ENUM para definir possíveis valores para as colunas, isso é sim uma boa prática para a performance e garantia da consistência dos dados, mas quando não amarrada a uma definição em código, como Enum/const, etc. Pode tornar a constrant fragil, permitindo que erros de digitação a quebrem.

Claro, possuir um Objeto que defina esses valores não resolve esse problema, mas tende a padronizar o estado da String dentro da codebase e a facilitar a leitura dos seus estados. Uma vez que ler AccountStatus::BLOCK é muito mais inteligível que apenas a string "BLOCK".

Por exemplo, na criação da tabela de accounts é feito dessa forma:

public function up()
    {
        Schema::create('accounts', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->uuid('user_id');
            $table->uuid('external_id')->nullable();
            $table->enum('status', ['BLOCK', 'ACTIVE']);
            $table->timestamps();
            $table->softDeletes();

            $table->foreign('user_id')
                ->references('id')
                ->on('users');
        });
    }

Isso poderia ser reescrito para:

public function up()
    {
        Schema::create('accounts', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->uuid('user_id');
            $table->uuid('external_id')->nullable();
            $table->enum('status', [AccountStatus::BLOCK, AccountStatut::ACTIVE]); // Ou array_column(AccountStatus::cases(), 'value')
            $table->timestamps();
            $table->softDeletes();

            $table->foreign('user_id')
                ->references('id')
                ->on('users');
        });
    }

Parte dos testes do projeto estão quebrando justamente por violarem as constraints definidas nessas colunas

Coisas que podem quebrar em situações específicas

A migration de User esconde um bug na funcionalidade de soft_deletes

Ao deletar um record dessa tabela deve-se tratar o estado de "deletado" das colunas unique: email e document_number A base de dados não conhece o que é a técnica de soft_deletes, e isso deve ser tratado ou via constraint combinada ou via rotina no código.

/**
 * Migration para criação da tabla de `users`
 */
public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->string('name');
            $table->enum('type', ['USER', 'VIRTUAL', 'MANAGER']);
            $table->string('document_number')->unique();
            $table->string('email')->unique();
            $table->uuid('company_id');
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
            $table->softDeletes();

            $table->foreign('company_id')
                ->references('id')
                ->on('companies');
        });
    }

Alternativamente ela poderia ser escrita da seguinte forma:

 public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->uuid('id')->primary();
            $table->string('name');
            $table->enum('type', ['USER', 'VIRTUAL', 'MANAGER']);
            $table->string('document_number');
            $table->string('email');
            $table->uuid('company_id');
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
            $table->softDeletes();
            /*
             * Acrescenta Constraints combinadas para evitar que ao recriar um elemento na base de dados o dado novo esbarre
             * no deletado
             */
            $table->unique(['document_number', 'deleted_at']);
            $table->unique(['email', 'deleted_at']);

            $table->foreign('company_id')
                ->references('id')
                ->on('companies');
        });
    }

Code-review

Abaixo serão relacionados tópicos de code review mais detalhados e problemas identificados no projeto.

Aspectos gerais

Em todo projeto foram encontrados código como estes, a seguir:

  • Funções sem tipagem no retorno
  • Códigos de erro escritos hard-coded
// app/Domains/Card/Register.php
 /**
  * Cartão não pode já estar vinculado
  */
 protected function checkExternalId()  // Incluir tipagem de retorno
 {
     if (!(new CanUseExternalId($this->cardId))->handle()) {
         throw new InternalErrorException(
             'Não é possível vincular esse cartão',
             0 // Usar constântes legíveis para os códigos de erro
         );
     }
 }
  • Uso de strings para representar estados/tipos dentro de objetos do sistema
protected function checkType(): void
{
   if (is_null($this->type)) {
       return;
   }
   /*
    * Utilizar o modo estrito do `in_array`, passando `true` no último parâmetro
    * Avaliar possíbilidade de substituir as strings hard-coded por constântes ou Enums
    */
   if (!in_array($this->type, ['USER', 'VIRTUAL', 'MANAGER'])) {
       throw new InternalErrorException(
           'Não é possível adicionar o tipo informado',
           0
       );
   }
}
  • Definição de classes e Traits que não são de fato necessárias.
app/Traits/Instancer.php

Não encontrei no projeto uso da função Instancer::instance()

  • Inconsistência das documentações de classes e funções

É importânte documentar os parâmetros de entradas com PHPDocs. A documentação correta dos parâmetros de entrada das funções, além de garantir legibilidade e externar suas funcionalidades, permite o uso de ferramentas auxiliares para geração de documentação de API como swagger, por exemplo.

Factories

As factories do projeto realmente são eficazes no seu proposito. Mas pecam apenas no uso de strings hard-coded para representar estados de objeto, como, status e níveis de permissão.

O uso de enums aqui tornaria esse código muito mais escalável e inteligível.

Exemplo:

class AccountFactory extends Factory
{
    public function definition()
    {
        return [
            'external_id' => null,
            'user_id'     => User::factory(),
            'status'      => fake()->randomElement(AccountStatus::cases()) // Antes: fake()->randomElement(['BLOCK', 'ACTIVE']),
        ];
    }
}

Claro que isso exigiria uma refatoração do casting dessa coluna a nível da model

Controllers

Em CardController::register() POST api/users/{id}/card não é utilizado um FormRequest dedicado para a rota, e nem é feita a validação e sanitização dos dados de entrada, o que permite que qualquer tipo de dado entre para o controller e possa provocar qualquer tipo de erro inesperado.

Existe código escrito em pt_BR, quebrando o padrão, aparentemente, adotado na aplicação:

class CompanyController extends Controller
{
    public function update(UpdateRequest $request): JsonResponse
    {
        $dominio = (new UpdateDomain(
            Auth::user()->company_id,
            $request->name,
        ))->handle();
        (new CompanyUpdate($dominio))->handle();

        $resposta = Company::find(Auth::user()->company_id)->first()->toArray();

        return $this->response(
            new DefaultResponse(
                new UpdateResource($resposta)
            )
        );
    }
}

Validações HTTP

Quase todos os POST Requests do site possuem classes de validação devidamente configuradas, porem não fazem uso de alguns recursos, por exemplo

<?php

namespace App\Http\Requests\User;

use Illuminate\Foundation\Http\FormRequest;

class CreateRequest extends FormRequest
{
    public function rules(): array
    {
    /*
     * Todas as colunas deveriam ter a definição que qual o limite de caracteres aceitos no campo
     */
        return [
            /*
             * Esse documento pode ser qualquer coisa que dê match com essa regex? 
             * Digo, ele possui alguma regra a mais como o CPF?
             * Se a resposta for sim, necessitamos de uma validação mais precisa desse dado, pois ele não é apenas uma string
             * 
             * A sugestão é criar uma classe
             * Validation/User/DocumentNumber e nela realizar a validação correta dos dados
             */
            'document_number' => 'required|regex:/[0-9]{11}/i',
            'name'            => 'required',
            /*
             * Existe uma constraint para garantir que esse campo é unique, 
             * Para evitar estouro em runtime, essa checagem poderia ser feita aqui
             * 
             * Afim de garantir controle sobre a origem dos dados, a sugestão é criar uma classe
             * Validation/User/EmailUnique e nela realizar a consulta através do repositório devidamente
             * injetado.
             * 
             * > Essa validação deve ser considerada também em app/Http/Requests/User/UpdateRequest.php
             * > Tratando o caso de estar atualizando para o mesmo email que ja existe
             */
            'email'           => 'required|email',
            'password'        => 'required',
            
            /*
             * Novamente um reforço para o uso de Enums na representação de constântes
             */
            'type'            => 'required|in:USER,VIRTUAL,MANAGER'
        ];
    }
}

Essa Trait é mesmo necessária?

App/Http/Requests/Traits/SanitizesInput::class

Não encontrei seu uso na codebase, e me parece um código importado de algum outro sistema, pois é acoplada a uma classe Sanitizer que não existe

Não encontrei nas validações os módulos de Account e Card. Eles possuem algumas regras de negócio na camada de domain e seus dados estão sendo usados puros nos controllers. Essas classes deveriam existir para garantir a segurança da entrada dos dados

Responses

A classe DefaultResponse acumula muita funcionalidade no seu construtor. Além de não possuir tipagem no parâmetro $parameters

/**
* Original
 */
    public function __construct(
        $data = null,
        bool $success = true,
        array $errors = [],
        int $code = 200
    ) {
        $this->parameters = [
            'success' => $success,
            'request' => request()->fullUrl(),
            'method' => strtoupper(request()->method()),
            'code' => $code,
        ];

        $this->parameters['data'] = (empty($data))
            ? null
            : $data;

        // Formatando dados paginados que serão retornados
        if ($data instanceof ResourceCollection && $data->resource instanceof LengthAwarePaginator) {
            $this->formatPaginatedData($data);
        }

        // Formatando os erros que serão retornados
        if (count($errors)) {
            $this->parameters['errors'] = array_map(function ($error) {
                if (!$error instanceof InternalError) {
                    throw new Exception('Error inserido não é do tipo InternalError');
                }

                return $error->toArray();
            }, $errors);
        }
    }

/**
 * Refatorado
 */
class DefaultResponse
{
    /**
     * @var array
     */
    private array $parameters;

    /**
     * @param mixed                $data    Dados de retorno
     * @param bool                 $success Processado com sucesso
     * @param array<InternalError> $errors  Lista de erros internos acontecidos
     * @param int                  $code    HTTP Code response
     */
    public function __construct(
        $data = null,
        bool $success = true,
        array $errors = [],
        int $code = 200
    ) {
        $this->initParameters($success, $code);
        $this->setData($data);
        $this->handlePaginationData($data);
        $this->handleErrosFormating($errors);

    }

    /**
     * Formatando dados paginados que serão retornados
     * @param mixed $data
     * @return void
     */
    public function handlePaginationData(mixed $data): void
    {
        if ($data instanceof ResourceCollection && $data->resource instanceof LengthAwarePaginator) {
            $this->formatPaginatedData($data);
        }
    }

    /**
     * Seta o parâmetro data
     * @param mixed $data
     * @return void
     */
    public function setData(mixed $data): void
    {
        $this->parameters['data'] = (empty($data))
            ? null
            : $data;
    }

    /**
     * Inicializa os parâmetros da classe
     * 
     * @param bool $success
     * @param int $code
     * @return void
     */
    public function initParameters(bool $success, int $code): void
    {
        $this->parameters = [
            'success' => $success,
            'request' => request()->fullUrl(),
            'method' => strtoupper(request()->method()),
            'code' => $code,
        ];
    }

    /**
     * Formatando os erros que serão retornados
     * @param array $errors
     * @return void
     * @throws Exception
     */
    public function handleErrosFormating(array $errors): void
    {
        if (!count($errors)) {
            return;
        }
        
        $this->parameters['errors'] = array_map(function ($error) {
            if (!$error instanceof InternalError) {
                throw new Exception('Error inserido não é do tipo InternalError');
            }

            return $error->toArray();
        }, $errors);
    }
}

Essa simples refatoração não tira as responsabilidade do construtor, mas, pelo menos, o torna mais legível e organizado

Responses

Apenas sugiro mover essa URL para algum canto mais "configurável"

class InternalError
{
    // Essa URL deveria ser uma config do sistema
    const BASE_URL_MORE_INFO = 'https://developers.vexpenses.com/moreinfo/';
}

Paginators

Esse módulo me parece um pouco estranho estar desacoplado das responses, dado que ele representa uma collection paginada Mas ao mesmo tempo não está errado seu "isolamento", apenas um ponto de vista

O problema dele é em sua única classe, a Paginator, A documentação do método fromLengthAwarePaginator não representa o que realmente o método recebe e retorna, confundindo seriamente quem consome a classe. No lugar de ajudar uma documentação errada pode atrapalhar um pouco.

/**
     * Cria uma nova instancia de paginação a partir de um LengthAwarePaginator
     *
     * @param  LengthAwarePaginator $paginator
     * @param  string|null          $order
     *
     * @return void
     */
    public static function fromLengthAwarePaginator(LengthAwarePaginator $paginator)
    {
        $collection = $paginator->getCollection();

        return new self(
            $collection->values(),
            $paginator->total(),
            $paginator->perPage(),
            $paginator->currentPage(),
            $paginator->getOptions()
        );
    }

Policies

A classe BasePolicy possui alguns problemas de legibilidade e fere o S do SOLID no método deny(). Esse método não apenas nega o acesso, mas também faz log disso, possuindo mais de um motivo para ser alterada.

Uma solução para esse problema é a utilização do mecanismo de eventos do Laravel, disparando um evento, como ActionDenied, por exemplo. E então, um listener externo, em um contexto de logging, faria o registro da informação no log.

Os métodos de checagem de permissão possuem repetição de lógica, ifs encadeados e códigos de erro hard-coded.

Uma sugestão para refatorar essa classe:

class BasePolicy
{
    const POLICY_EXCEPTION_ERROR = 'POLICY_EXCEPTION_ERROR';
    const ERROR_CODE = 146001003;

    /**
     * Lançamento da exception de acesso negado
     *
     * @param string $message
     * @param int $code
     * @param string|null $entityId
     * @param string|null $entity
     *
     * @return void
     * @throws PolicyException
     */
    protected function deny(
        string $message,
        int    $code,
        string $entityId = null,
        string $entity = null
    ): void
    {
        $className = get_called_class();
        // Toda a lógica de log disso será feito em um listener
        ActionDenied::dispatch($message, self::POLICY_EXCEPTION_ERROR, $code, $entityId, $entity, $className);
        throw new PolicyException($message, $code);
    }
    
    /**
     * Verifica se o usuário dado é do usuário logado, ou se ele
     * é gestor
     *
     * @return void
     */
    protected function isManagerOrOwnerResource(string $ownerResourceId): void
    {
        if ($this->isUserOwner($ownerResourceId) || $this->isUserManager(Auth::user())) {
            return;
        }

        $this->deny(
            'UNAUTHORIZED',
            self::ERROR_CODE,
            Auth::id(),
            'USER'
        );
    }

    /**
     * Verifica se o id informado é o do usuário logado
     *
     * @param string $ownerResourceId
     *
     * @return void
     */
    protected function isOwnerResource(string $ownerResourceId): void
    {
        if ($this->isUserOwner($ownerResourceId)) {
            return;
        }

        $this->deny(
            'UNAUTHORIZED',
            self::ERROR_CODE,
            Auth::id(),
            'USER'
        );
    }

    /**
     * Verifica se o usuário logado é gestor de contas
     *
     * @return void
     */
    protected function isManagerAccountsUser(): void
    {
        $user = Auth::user();
        if ($this->isUserManager($user)) {
            return;
        }

        $this->deny(
            'UNAUTHORIZED',
            self::ERROR_CODE,
            Auth::id(),
            'USER'
        );
    }


    /**
     * @param string $ownerResourceId
     * @return bool
     */
    public function isUserOwner(string $ownerResourceId): bool
    {
        return $ownerResourceId === Auth::id();
    }

    /**
     * @param Authenticatable|null $user
     * @return bool
     */
    public function isUserManager(?Authenticatable $user): bool
    {
        return !is_null($user) && $user->type === 'MANAGER';
    }
}

Repositórios

No módulo Card existe um repositório cuja o código e nomenclatura remetem a uma validação...

O repositório CanUseExternalId está com a responsabilidade de saber qual a regra que permite o uso de um external_id no sistema. Isso é característica de uma classe de Domínio ou de alguma classe de validação.

O correto seria que esse repositório se chamasse FindByExternalId e o seu client que validasse essa lógica se pode ou não usar o dado

Outra opção é criar um repositório ExternalIdExists ou ExistsByExternalId e usar o método exists() da model para retornar existência

O mesmo se repete para os módulos de Company e User, onde são feitas validações de dados com repositórios.

Essa sugestão toma como base o sentido pelo qual o nome da classe externa. Se a compreencão correta do autor for que "esse dado pode ser usado na base de dados" o repositório está correto, mas ainda é necessário externar isso de alguma maneira no nome da classe.

Pelo aspecto dúbio que esse nome apresenta, eu ainda mantenho a sugestão de mudar o significado do repositório.

UserCase

A camada de UserCase é fortemente acoplada com outras classes do sistema, como repositórios e domínios. Uma sujestão para isso é a utilização de Dependency Injection para essas classes. Levando a responsabilidade de instânciar essas classas para quem consome os dados. Se unida à utilização de Interfaces, conseguimos garantir mais flexibilidade para a aplicação.

Aparentemente esse acoplamento faz parte da decisão arquitetural do sistema. Cabe avaliar a necessidade de desacoplamento dessas classes e uso melhor de injeção de dependências

No módulo de Account o UserCase Register possui uma checagem de validação que não caberia dentro do contexto de UserCase

class Register extends BaseUseCase
{
    protected function findUser(): void
    {
        $user = (new Find($this->userId, $this->companyId))->handle();
        /*
         * Essa validação deveria acontecer dentro da camada de domínio
         */
        if (is_null($user)) {
            throw new InternalErrorException(
                'USER_NOT_FOUND',
                146001001
            );
        }

        $this->user = $user;
    }
}

No modulo de Account, o UserCase Register utiliza elementos dentro de array sem checagem de existência (no caso sem nenhum domain, objectValue, etc para checar) Isso pode quebrar facilmente.

class Register extends BaseUseCase
{

    protected array $user;

    protected function store(): void
    {
        /*
         * Como não tem um objeto guardando esses dados, essa informação fica dependendo da origem garantir
         * que sempre existirá um data.id dentro do retorno. Aqui estamos confiando em algo que não temos controle 
         * sobre a existência e tipagem
         */
        (new Create($this->userId, $this->account['data']['id']))->handle();
    }
}

No módulo de User, o UserCase Login fere o CS do projeto, criando um alias para uma classe usando snake_case no lugar de de PascalCase

use App\Repositories\Token\Create as create_token;

No mesmo módulo, no UserCase Show a padronização do código é totalmente ferida;

  • Uso de variáveis sem nomes compreendíveis, como $a ou $b
  • arquivo nomeado como show.php no lugar de Show.php

Integration

A camada de integração com o BaaS possui alguns problemas com o S do SOLID

A classe Gateway possui acoplamento direto com o sistema de logging, isso é um problema pois cria duas responsabilidades para a classe. A recomendação aqui é a mesma da classe BasePolicy: utilizar o sistema de eventos do Laravel para coordenar os logs desse módulo

Ao utilizar eventos e listeners desacoplamos a responsabilidade de registro de logs da requisição a API de integração.

Além disso, ela possui responsabilidade de mais dentro do contexto de Integration. sendo responsável por:

  • Tratar a autenticação com a API - OK (Mas ainda da pra separar isso em uma outra classe que trata a autenticação)
  • Tratar das chamadas HTTP com a API - OK
  • Formatar saída dos dados para uso dentro do sistema - X

A formatação dos dados de saída deveria ser de responsabilidade de uma classe especialista nisso, uma ResponseService por exemplo. Ela seria responsável pelo tratamento dos dados e retorno do DTO de response.

Por fim, por se tratar de um serviço externo, o acoplamento desse módulo com o restante do sistema deveria acontecer por meio de interfaces. Todas as classes que consomem da integração acessam diretamente as implementações, isso fere o I do SOLID.

O Correto seria que as classes de serviço desse módulo, as Register, Create, Find e UpdateStatus fossem acessadas apenas por meio de interfaces.

Para que isso seja possível será necessário refatorar os UserCase que a consomem para trabalharem orientados as interfaces e desacoplem das classes de serviço


Problemas globais

Nessa sessão trataremos dos problemas que atingem diretamente o client das APIs do sistema e mais problemas no código

Aplicação do padrão REST

Quando o sistema retorna um erro de domínio o status da requisição é HTTP 200 OK. Isso causa sérios problemas para o client que for consumir a API

No exemplo testado tentei registrar o mesmo usuário mais de uma vez, o status code esperado nessa situação é na faixa 400, de request inválido. Porem a requisição retorna com status 200 no Header e o parâmetro code 200 da mesma forma.

Por mais que o parâmetro success seja false, a necessidade e presença desses atributos no response da API revela uma inconsistência no uso do padrão REST.

POST http://localhost:8000/api/users/register

HTTP/1.1 200 OK
Host: localhost:8000
Connection: close
X-Powered-By: PHP/8.2.28
Cache-Control: no-cache, private
Date: Sat, 07 Jun 2025 15:01:42 GMT
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 60
Access-Control-Allow-Origin: *

{
  "success": false,
  "request": "http:\/\/localhost:8000\/api\/users\/register",
  "method": "POST",
  "code": 200,
  "data": null,
  "errors": [
    {
      "code": 0,
      "log": null,
      "message": "Não é possível adicionar o CNPJ informado",
      "moreInfo": "https:\/\/developers.vexpenses.com\/moreinfo\/0"
    }
  ]
}

Outro comportamento que não faz sentido para quem consome o a api, é que ao passar um dado inválido, como um email errado, a API redireciona para a rota /

POST http://localhost:8000/api/users/register
Content-Type: application/json

{
  "user_document_number": "26115676541",
  "user_name": "Petra Luettgen",
  "company_document_number": "93106534000188",
  "company_name": "Mueller, O'Reilly and Zemlak",
  "email": "@email.com",
  "password": "neque"
}

Redirections:
-> http://localhost:8000/

HTTP/1.1 404 Not Found
Host: localhost:8000
Connection: close
X-Powered-By: PHP/8.2.28
Cache-Control: no-cache, private
Date: Sat, 07 Jun 2025 15:09:54 GMT
Content-Type: application/json

{
  "success": false,
  "request": "http:\/\/localhost:8000",
  "method": "GET",
  "code": 404,
  "data": null
}

Problemas de integridade de estado

Como citado anteriormente nesse documento, o projeto peca ao tentar fazer gerenciamento de estado dos objetos com strings puras

Essa pratica fragiliza toda a code-base. Pois um único caractere errado quebrará o sistema. E isso de fato está acontecendo. No UseCase responsável por bloquear uma Account é utilizado o termo block para representar o estado de bloqueado, mas na base de dados existe uma constraint que permite apenas os termos BLOCK e ACTIVE. Como se pode notar, o termo utilizado está em minusculo, e na base está maiúsculo.

    protected function updateDatabase(): void
    {
       /*
        * Aqui deveríamos estar usando algo mais concreto para representar o estado dessa string
        */
        (new RepositoryUpdateStatus($this->userId, 'block'))->handle();
    }

Esse tipo de falha externa uma má decisão técnica tomada ao criar estados baseados em strings no lugar de usar objetos concretos.

Performance

O maior problema de performance encontrado nesse sistema é a integração com o serviço de BaaS. Essa integração é feita durante o processamento das requisições. Isso empilha na requisição, pelo menos, duas requests a serviços terceiros, uma para login (quando necessário), e outra para o processamento/busca dos dados. Essa pratica é extremamente prejudicial para o desempenho de um sistema, caso ele precise atender dezenas de requisições por segundo.

Nesse caso, a recomendação é mover, quando possível os trabalhos de processamento para workers assíncronos do laravel, por meio de jobs e schedulers.

Uma forma simples de remover ao menos a request do login do tempo de requisição é programar um Scheduler que mantenha o token de autenticação sempre valido no cache. Dessa forma não será necessário revalidar esse dado durante a requisição.

Para as requisições que realizam criação de dados, caso não seja necessário retornar essa informação na request, a recomendação é mover essas informações para uma queue de jobs para processamento assíncrono.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment