Arquitetura

🔧 PLANO DE REFATORAÇÃO: OrderController → OrderService

🔧 PLANO DE REFATORAÇÃO: OrderController → OrderService

⚠️ CONTEXTO E RISCOS

Situação Atual

  • Arquivo: app/Http/Controllers/Api/OrderController.php
  • Tamanho: 1.515 linhas de código
  • Métodos públicos: 15 endpoints
  • Dependências: OrderStatusService, Models (Order, Customer, OrderItem, etc.)
  • Complexidade: ALTA - Controller contém toda a lógica de negócio

❌ Problemas Identificados

  1. Violação de SRP (Single Responsibility Principle)
    • Controller mistura validação HTTP + lógica de negócio + queries
  2. Baixa Testabilidade
    • Difícil testar lógica de negócio isoladamente
  3. Manutenção Difícil
    • 1.515 linhas em um único arquivo
  4. Acoplamento Alto
    • Controller fortemente acoplado aos Models

🎯 Objetivo da Refatoração

Criar OrderService com toda a lógica de negócio, mantendo Controller apenas como:

  • Receber Request
  • Validar Input (via FormRequest)
  • Chamar Service
  • Retornar Response

🚨 ANÁLISE DE RISCOS

Riscos Críticos (Podem Quebrar o Sistema)

RiscoProbabilidadeImpactoMitigação
Quebra de endpoints existentesALTACRÍTICOTestes de regressão antes de cada merge
Perda de validaçõesMÉDIAALTOManter FormRequests intactos
Quebra de relacionamentos EloquentMÉDIAALTOMover queries complexas gradualmente
Perda de transações DBBAIXACRÍTICOEnvolver Service em DB::transaction
Alteração de comportamentoMÉDIAALTOTestes E2E antes e depois

Riscos Médios (Podem Causar Bugs)

RiscoProbabilidadeImpactoMitigação
Cache não invalidadoMÉDIAMÉDIODocumentar pontos de cache
Logs perdidosBAIXABAIXOManter logs no Service
Notificações não enviadasBAIXAMÉDIOTestar fluxos de notificação

✅ ESTRATÉGIA DE MIGRAÇÃO (SEM QUEBRAR)

Princípios da Migração

  1. Incremental: Refatorar 1 método por vez
  2. Testável: Testar antes e depois de cada mudança
  3. Reversível: Manter código antigo comentado por 1 sprint
  4. Isolado: Trabalhar em branch separada
  5. Validado: Testes de regressão em cada fase

Abordagem: Adapter Pattern + Gradual Migration

// FASE 1: Criar Service vazio (sem quebrar nada)
class OrderService
{
    // Métodos vazios, Controller continua funcionando
}

// FASE 2: Mover lógica 1 método por vez
class OrderService
{
    public function createOrder(array $data): Order
    {
        // Lógica movida do Controller
    }
}

// FASE 3: Controller vira "thin wrapper"
class OrderController
{
    public function store(Request $request): JsonResponse
    {
        // Apenas delega para Service
        $order = $this->orderService->createOrder($request->validated());
        return response()->json(new OrderResource($order));
    }
}

📋 FASES DA REFATORAÇÃO

FASE 0: Preparação (1-2 dias)

Objetivo: Preparar ambiente e testes

Tarefas:

  • Criar branch refactor/order-service
  • Executar todos os testes existentes e garantir que passam
  • Criar testes E2E para os 15 endpoints (se não existem)
  • Documentar comportamento atual de cada endpoint
  • Criar arquivo OrderService.php vazio
  • Registrar Service no ServiceProvider

Checklist de Segurança:

  • Todos os testes existentes passando
  • Backup do banco de desenvolvimento criado
  • Documentação dos 15 endpoints completa

Critério de Sucesso:

✅ Testes passando + Service vazio criado + Branch criada


FASE 1: Métodos Simples (2-3 dias)

Objetivo: Refatorar métodos de leitura (GET) sem lógica complexa

Métodos Alvo:

  1. show() - Exibir um order
  2. items() - Listar itens do order
  3. history() - Histórico do order

Passo a Passo (EXEMPLO: show):

1. Criar método no Service:

// app/Services/OrderService.php
class OrderService
{
    public function getOrder(int $orderId, int $customerId): Order
    {
        $order = Order::where('id', $orderId)
            ->where('customer_id', $customerId)
            ->with(['orderItems.product', 'proposals'])
            ->firstOrFail();

        return $order;
    }
}

2. Atualizar Controller para usar Service:

// app/Http/Controllers/Api/OrderController.php
public function show(Order $order): JsonResponse
{
    try {
        $user = Auth::user();
        $customer = Customer::where('user_id', $user->id)->firstOrFail();

        // ANTES (comentar, não deletar):
        // $order = Order::where('id', $order->id)
        //     ->where('customer_id', $customer->id)
        //     ->with(['orderItems.product', 'proposals'])
        //     ->firstOrFail();

        // DEPOIS (usar Service):
        $order = $this->orderService->getOrder($order->id, $customer->id);

        return response()->json(new OrderResource($order));

    } catch (\Exception $e) {
        return response()->json(['message' => 'Erro ao buscar order'], 500);
    }
}

3. Testar:

# Rodar testes específicos
php artisan test --filter=ShowOrderTest

# Testar endpoint manualmente
curl -X GET http://localhost:8000/api/orders/{uuid} \
  -H "Authorization: Bearer {token}"

4. Se tudo OK, deletar código comentado e commitar

Critério de Sucesso de FASE 1:

  • 3 métodos refatorados (show, items, history)
  • Todos os testes passando
  • Endpoints funcionando identicamente
  • Código comentado removido
  • 3 commits separados (1 por método)

FASE 2: Métodos de Escrita Simples (3-4 dias)

Objetivo: Refatorar métodos POST/PUT sem lógica complexa

Métodos Alvo:

  1. update() - Atualizar order
  2. destroy() - Deletar order (soft delete)
  3. updateStatus() - Atualizar status

Passo a Passo (EXEMPLO: update):

1. Criar método no Service:

// app/Services/OrderService.php
public function updateOrder(Order $order, array $data, int $customerId): Order
{
    // Validar ownership
    if ($order->customer_id !== $customerId) {
        throw new \Exception('Unauthorized');
    }

    // Validar se pode atualizar (ex: não pode atualizar se published)
    if ($order->status === OrderStatus::PUBLISHED) {
        throw new \Exception('Cannot update published order');
    }

    // Atualizar usando transação
    DB::beginTransaction();
    try {
        $order->update($data);

        // Se tiver lógica de log/auditoria, adicionar aqui

        DB::commit();
        return $order->fresh();

    } catch (\Exception $e) {
        DB::rollback();
        throw $e;
    }
}

2. Atualizar Controller:

public function update(UpdateOrderRequest $request, Order $order): JsonResponse
{
    try {
        $user = Auth::user();
        $customer = Customer::where('user_id', $user->id)->firstOrFail();

        // Usar Service
        $updatedOrder = $this->orderService->updateOrder(
            $order,
            $request->validated(),
            $customer->id
        );

        return response()->json([
            'success' => true,
            'data' => new OrderResource($updatedOrder)
        ]);

    } catch (\Exception $e) {
        return response()->json([
            'success' => false,
            'message' => $e->getMessage()
        ], 400);
    }
}

3. Testar exaustivamente:

# Testes unitários
php artisan test --filter=UpdateOrderTest

# Testes manuais:
# - Atualizar order válido
# - Tentar atualizar order de outro customer (deve falhar)
# - Tentar atualizar order published (deve falhar)
# - Verificar auditoria/logs

Critério de Sucesso de FASE 2:

  • 3 métodos refatorados (update, destroy, updateStatus)
  • Transações DB funcionando
  • Validações mantidas
  • Todos os testes passando
  • 3 commits separados

FASE 3: Métodos Complexos (5-7 dias)

Objetivo: Refatorar métodos com lógica de negócio complexa

Métodos Alvo:

  1. store() - Criar order
  2. saveDraft() - Salvar rascunho (lógica de wizard)
  3. publish() - Publicar order (validações complexas)
  4. duplicate() - Duplicar order com itens

⚠️ ATENÇÃO ESPECIAL - saveDraft()

Este método tem ~350 linhas com lógica de wizard incremental. MUITO CUIDADO!

Estratégia para saveDraft:

// Quebrar em sub-métodos no Service:
class OrderService
{
    public function saveDraft(array $data, int $customerId): Order
    {
        return DB::transaction(function () use ($data, $customerId) {
            $order = $this->createOrUpdateDraft($data, $customerId);

            if (isset($data['items'])) {
                $this->syncDraftItems($order, $data['items']);
            }

            if (isset($data['step'])) {
                $this->updateDraftProgress($order, $data['step']);
            }

            return $order->fresh();
        });
    }

    private function createOrUpdateDraft(array $data, int $customerId): Order
    {
        // Lógica de criação ou atualização
    }

    private function syncDraftItems(Order $order, array $items): void
    {
        // Lógica de sincronização de itens
    }

    private function updateDraftProgress(Order $order, int $step): void
    {
        // Lógica de progresso do wizard
    }
}

Testes Críticos para saveDraft:

  • Criar draft do zero (step 1)
  • Atualizar draft existente (step 2, 3, etc)
  • Adicionar/remover itens incrementalmente
  • Validar progresso do wizard
  • Testar rollback em caso de erro

Critério de Sucesso de FASE 3:

  • 4 métodos complexos refatorados
  • Sub-métodos criados para lógica complexa
  • Transações e rollbacks funcionando
  • Testes de regressão passando
  • 4 commits separados

FASE 4: Métodos com Side Effects (3-4 dias)

Objetivo: Refatorar métodos que disparam notificações/eventos

Métodos Alvo:

  1. cancel() - Cancelar order + notificar fornecedores
  2. revoke() - Revogar order + cancelar propostas
  3. reopen() - Reabrir order cancelada

⚠️ CUIDADO: Notificações e Eventos

Estes métodos podem disparar:

  • Emails
  • Notificações WhatsApp
  • Webhooks
  • Atualizações em cascata

Estratégia:

class OrderService
{
    public function cancelOrder(Order $order, string $reason, int $customerId): Order
    {
        return DB::transaction(function () use ($order, $reason, $customerId) {
            // 1. Validar ownership
            if ($order->customer_id !== $customerId) {
                throw new \Exception('Unauthorized');
            }

            // 2. Atualizar status
            $order->update([
                'status' => OrderStatus::CANCELLED,
                'cancellation_reason' => $reason,
                'cancelled_at' => now(),
            ]);

            // 3. Disparar eventos (IMPORTANTE: manter mesma ordem)
            event(new OrderCancelled($order));

            // 4. Notificar fornecedores (se tiver propostas)
            if ($order->proposals()->count() > 0) {
                $this->notifySuppliers($order, 'cancelled');
            }

            return $order->fresh();
        });
    }

    private function notifySuppliers(Order $order, string $action): void
    {
        // Lógica de notificação
    }
}

Testes Críticos:

  • Verificar se emails são enviados
  • Verificar se notificações WhatsApp funcionam
  • Verificar se propostas são canceladas
  • Testar com order SEM propostas
  • Testar com order COM propostas

Critério de Sucesso de FASE 4:

  • 3 métodos refatorados (cancel, revoke, reopen)
  • Notificações funcionando
  • Eventos disparados na ordem correta
  • Testes E2E passando
  • 3 commits separados

FASE 5: Método Gigante - index() (4-5 dias)

Objetivo: Refatorar listagem com filtros complexos

Método Alvo:

  1. index() - Listar orders com filtros, paginação, geo

⚠️ COMPLEXIDADE ESPECIAL

Este método tem:

  • Múltiplos filtros (status, bidding_type, product_type, dates, radius)
  • Ordenação dinâmica
  • Paginação
  • Query geo espacial (Haversine)
  • Relacionamentos eager loading

Estratégia:

class OrderService
{
    public function listOrders(int $customerId, array $filters = [], array $options = []): LengthAwarePaginator
    {
        $query = Order::where('customer_id', $customerId)
            ->with(['orderItems.product', 'proposals']);

        // Aplicar filtros via métodos privados
        $query = $this->applyFilters($query, $filters);

        // Aplicar ordenação
        $query = $this->applyOrdering($query, $options);

        // Paginar
        $perPage = $options['per_page'] ?? 15;
        return $query->paginate($perPage);
    }

    private function applyFilters($query, array $filters)
    {
        if (isset($filters['status'])) {
            $query = $this->filterByStatus($query, $filters['status']);
        }

        if (isset($filters['product_type_id'])) {
            $query = $this->filterByProductType($query, $filters['product_type_id']);
        }

        if (isset($filters['radius'])) {
            $query = $this->filterByRadius($query, $filters);
        }

        // ... outros filtros

        return $query;
    }

    private function filterByRadius($query, array $filters)
    {
        // Lógica de Haversine
    }

    private function applyOrdering($query, array $options)
    {
        $orderBy = $options['order_by'] ?? 'created_at';
        $direction = $options['order_direction'] ?? 'desc';

        // Validar e aplicar ordenação
    }
}

Testes Críticos:

  • Testar TODOS os filtros individualmente
  • Testar combinações de filtros
  • Testar ordenações diferentes
  • Testar paginação
  • Testar filtro de raio (geolocalização)
  • Testar performance (queries N+1)

Critério de Sucesso de FASE 5:

  • Método index() refatorado
  • Todos os filtros funcionando
  • Performance mantida ou melhorada
  • Testes de regressão passando
  • 1 commit com testes completos

🧪 TESTES DE REGRESSÃO

Antes de CADA Fase

# 1. Rodar TODOS os testes existentes
php artisan test

# 2. Verificar código com análise estática
./vendor/bin/phpstan analyse

# 3. Rodar linter
./vendor/bin/pint

# 4. Testar endpoints manualmente (Postman/Insomnia)

Checklist de Testes por Endpoint

Para CADA método refatorado, testar:

  • Happy Path: Caso de sucesso padrão
  • Validações: Dados inválidos retornam erro correto
  • Permissões: Usuário sem permissão é bloqueado
  • Edge Cases: Casos extremos (ordem vazia, etc)
  • Performance: Tempo de resposta aceitável
  • Database: Dados salvos corretamente
  • Notificações: Emails/WhatsApp enviados (se aplicável)

🔄 PLANO DE ROLLBACK

Se Algo Der Errado em Produção

Rollback Imediato (5 minutos)

# 1. Voltar para commit anterior
git revert {commit-hash}
git push

# 2. Deploy da versão anterior
./deploy.sh

# 3. Verificar se sistema voltou ao normal
curl -X GET https://api.agrsis.com/health

Rollback Granular (Por Fase)

Cada fase está em commit separado, então:

# Reverter apenas FASE 3, manter FASE 1 e 2
git revert {commit-fase-3}

Backup de Segurança

ANTES de iniciar qualquer fase:

# Backup do banco
pg_dump agrsis_db > backup_before_phase_{N}.sql

# Tag de segurança no Git
git tag -a safe-before-phase-{N} -m "Backup antes da Fase {N}"
git push --tags

📊 CRONOGRAMA ESTIMADO

FaseDuraçãoRiscoPrioridade
FASE 0: Preparação1-2 diasBAIXOCRÍTICA
FASE 1: Métodos Simples2-3 diasBAIXOALTA
FASE 2: Escrita Simples3-4 diasMÉDIOALTA
FASE 3: Métodos Complexos5-7 diasALTOMÉDIA
FASE 4: Side Effects3-4 diasALTOMÉDIA
FASE 5: Método index()4-5 diasMÉDIOBAIXA
TOTAL18-25 dias--

Timeline Sugerida (1 mês)

  • Semana 1: FASE 0 + FASE 1
  • Semana 2: FASE 2
  • Semana 3: FASE 3
  • Semana 4: FASE 4 + FASE 5

✅ CHECKLIST FINAL DE SEGURANÇA

Antes de Merge em Main

  • Todos os 15 endpoints testados manualmente
  • Todos os testes automatizados passando
  • Análise estática (PHPStan) sem erros
  • Code review realizado
  • Documentação atualizada
  • Changelog atualizado
  • Backup do banco criado
  • Tag de versão criada
  • Deploy em staging testado
  • Aprovação do usuário/cliente

Após Deploy em Produção

  • Monitorar logs por 24h
  • Verificar taxa de erros (Sentry/similar)
  • Verificar performance (APM)
  • Verificar notificações sendo enviadas
  • Feedback dos usuários coletado

🎯 BENEFÍCIOS ESPERADOS

Técnicos

  • ✅ Controller com ~200 linhas (ao invés de 1.515)
  • ✅ Service testável isoladamente
  • ✅ Lógica de negócio centralizada
  • ✅ Fácil manutenção e extensão
  • ✅ Código mais limpo (SOLID)

Negócio

  • ✅ Menos bugs (lógica isolada e testada)
  • ✅ Onboarding de devs mais rápido
  • ✅ Facilita novas funcionalidades
  • ✅ Redução de débito técnico

📝 NOTAS FINAIS

Recomendações

  1. NÃO apressar o processo - Melhor devagar e seguro do que rápido e quebrado
  2. Testar exaustivamente cada fase antes de prosseguir
  3. Manter código antigo comentado por 1 sprint antes de deletar
  4. Documentar decisões em cada commit
  5. Pedir code review de outro desenvolvedor

Quando NÃO Refatorar

  • ❌ Às vésperas de um deploy importante
  • ❌ Sem testes automatizados existentes
  • ❌ Sem backup do banco de dados
  • ❌ Sem aprovação/consenso da equipe

Quando Começar

  • ✅ Após criar todos os testes de regressão
  • ✅ Com tempo disponível (não sob pressão)
  • ✅ Com aprovação da equipe
  • ✅ Em período de baixo tráfego

🆘 CONTATOS DE EMERGÊNCIA

Em caso de problemas críticos:

  1. Rollback imediato (ver seção acima)
  2. Notificar equipe via canal de emergência
  3. Documentar o problema para análise posterior
  4. NÃO tentar corrigir sob pressão - rollback primeiro, corrigir depois

Última atualização: 2026-02-04 Versão do documento: 1.0 Status: Aguardando aprovação para início

Copyright © 2026