🔧 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
- Violação de SRP (Single Responsibility Principle)
- Controller mistura validação HTTP + lógica de negócio + queries
- Baixa Testabilidade
- Difícil testar lógica de negócio isoladamente
- Manutenção Difícil
- 1.515 linhas em um único arquivo
- 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)
| Risco | Probabilidade | Impacto | Mitigação |
|---|---|---|---|
| Quebra de endpoints existentes | ALTA | CRÍTICO | Testes de regressão antes de cada merge |
| Perda de validações | MÉDIA | ALTO | Manter FormRequests intactos |
| Quebra de relacionamentos Eloquent | MÉDIA | ALTO | Mover queries complexas gradualmente |
| Perda de transações DB | BAIXA | CRÍTICO | Envolver Service em DB::transaction |
| Alteração de comportamento | MÉDIA | ALTO | Testes E2E antes e depois |
Riscos Médios (Podem Causar Bugs)
| Risco | Probabilidade | Impacto | Mitigação |
|---|---|---|---|
| Cache não invalidado | MÉDIA | MÉDIO | Documentar pontos de cache |
| Logs perdidos | BAIXA | BAIXO | Manter logs no Service |
| Notificações não enviadas | BAIXA | MÉDIO | Testar fluxos de notificação |
✅ ESTRATÉGIA DE MIGRAÇÃO (SEM QUEBRAR)
Princípios da Migração
- ✅ Incremental: Refatorar 1 método por vez
- ✅ Testável: Testar antes e depois de cada mudança
- ✅ Reversível: Manter código antigo comentado por 1 sprint
- ✅ Isolado: Trabalhar em branch separada
- ✅ 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.phpvazio - 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:
show()- Exibir um orderitems()- Listar itens do orderhistory()- 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:
update()- Atualizar orderdestroy()- Deletar order (soft delete)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:
store()- Criar ordersaveDraft()- Salvar rascunho (lógica de wizard)publish()- Publicar order (validações complexas)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:
cancel()- Cancelar order + notificar fornecedoresrevoke()- Revogar order + cancelar propostasreopen()- 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:
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
| Fase | Duração | Risco | Prioridade |
|---|---|---|---|
| FASE 0: Preparação | 1-2 dias | BAIXO | CRÍTICA |
| FASE 1: Métodos Simples | 2-3 dias | BAIXO | ALTA |
| FASE 2: Escrita Simples | 3-4 dias | MÉDIO | ALTA |
| FASE 3: Métodos Complexos | 5-7 dias | ALTO | MÉDIA |
| FASE 4: Side Effects | 3-4 dias | ALTO | MÉDIA |
| FASE 5: Método index() | 4-5 dias | MÉDIO | BAIXA |
| TOTAL | 18-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
- NÃO apressar o processo - Melhor devagar e seguro do que rápido e quebrado
- Testar exaustivamente cada fase antes de prosseguir
- Manter código antigo comentado por 1 sprint antes de deletar
- Documentar decisões em cada commit
- 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:
- Rollback imediato (ver seção acima)
- Notificar equipe via canal de emergência
- Documentar o problema para análise posterior
- 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