Laravel na Prática: Actions, Events e Strategy Pattern em um Fluxo Profissional

Autor(a):

Neste tutorial vamos construir uma arquitetura profissional usando:

  • Laravel + Docker
  • Actions
  • Events
  • Listeners
  • Strategy Pattern
  • Boas práticas de organização
  • Separação de responsabilidades

A ideia será criar um sistema simples de “teste de produto”, onde cada produto pode ser validado de maneiras diferentes usando estratégias independentes.

Como usar Strategy Pattern no Laravel com Actions e Events para organizar regras de negócio

Exemplo:

  • Produto digital → valida licença
  • Produto físico → valida estoque
  • Produto premium → valida assinatura

Tudo desacoplado usando:

  • Strategy Pattern para trocar regras
  • Actions para centralizar casos de uso
  • Events para desacoplar efeitos colaterais

O que vamos aprender

  • Como subir Laravel com Docker
  • Como estruturar Actions no Laravel
  • Como usar Events e Listeners
  • Como aplicar Strategy Pattern corretamente
  • Como evitar Controllers gigantes
  • Como organizar um projeto Laravel de forma profissional

Criando o projeto Laravel com Docker

Estrutura que iremos criar

							
							
					app/
├── Actions/
├── Events/
├── Listeners/
├── Strategies/
│   └── ProductValidation/
├── Contracts/
├── DTOs/
└── Services/				
			

O problema da regra de negócio no Controller

Muita gente faz isso:

							
							
					public function store(Request $request)
{
    if ($request->type === 'digital') {
        // valida digital
    }

    if ($request->type === 'physical') {
        // valida físico
    }

    if ($request->type === 'premium') {
        // valida premium
    }
}				
			

O problema:

  • difícil manutenção
  • alto acoplamento
  • crescimento caótico
  • impossível testar direito

Vamos resolver isso usando Strategy + Actions + Events.

Criando nossa interface Strategy

Contrato das estratégias

mkdir -p app/Contracts
ProductValidationStrategyInterface.php
							
							
					<?php

namespace App\Contracts;

interface ProductValidationStrategyInterface
{
    public function validate(array $product): bool;
}				
			

Criando as Strategies

Produto Digital

							
							
					<?php

namespace App\Strategies\ProductValidation;

use App\Contracts\ProductValidationStrategyInterface;

class DigitalProductStrategy implements ProductValidationStrategyInterface
{
    public function validate(array $product): bool
    {
        return isset($product['license']);
    }
}				
			

Produto Físico

							
							
					<?php

namespace App\Strategies\ProductValidation;

use App\Contracts\ProductValidationStrategyInterface;

class PhysicalProductStrategy implements ProductValidationStrategyInterface
{
    public function validate(array $product): bool
    {
        return $product['stock'] > 0;
    }
}				
			

Produto Premium

							
							
					<?php

namespace App\Strategies\ProductValidation;

use App\Contracts\ProductValidationStrategyInterface;

class PremiumProductStrategy implements ProductValidationStrategyInterface
{
    public function validate(array $product): bool
    {
        return $product['subscription_active'] === true;
    }
}				
			

Criando a Action

Actions ajudam a encapsular regras de negócio.

Estrutura

mkdir app/Actions

ValidateProductAction.php

							
							
					<?php

namespace App\Actions;

use App\Events\ProductValidated;
use App\Strategies\ProductValidation\DigitalProductStrategy;
use App\Strategies\ProductValidation\PhysicalProductStrategy;
use App\Strategies\ProductValidation\PremiumProductStrategy;

class ValidateProductAction
{
    public function execute(array $product): bool
    {
        $strategy = match ($product['type']) {
            'digital' => new DigitalProductStrategy(),
            'physical' => new PhysicalProductStrategy(),
            'premium' => new PremiumProductStrategy(),
            default => throw new \Exception('Tipo inválido')
        };

        $validated = $strategy->validate($product);

        event(new ProductValidated($product, $validated));

        return $validated;
    }
}				
			

Por que isso ficou melhor?

Agora:

  • cada regra fica isolada
  • adicionar novos tipos é fácil
  • código testável
  • baixo acoplamento
  • mais legível

Criando Event

Gerando event

sail artisan make:event ProductValidated

ProductValidated.php

							
							
					<?php

namespace App\Events;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class ProductValidated
{
    use Dispatchable, SerializesModels;

    public function __construct(
        public array $product,
        public bool $validated
    ) {}
}				
			

Criando Listener

Gerando listener

sail artisan make:listener LogValidatedProductListener

LogValidatedProductListener.php

							
							
					<?php

namespace App\Listeners;

use App\Events\ProductValidated;
use Illuminate\Support\Facades\Log;

class LogValidatedProductListener
{
    public function handle(ProductValidated $event): void
    {
        Log::info('Produto validado', [
            'product' => $event->product,
            'validated' => $event->validated
        ]);
    }
}				
			

Registrando Event e Listener

AppServiceProvider

							
							
					<?php

namespace App\Providers;

use App\Events\ProductValidated;
use App\Listeners\LogValidatedProductListener;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(
            ProductValidated::class,
            LogValidatedProductListener::class
        );
    }
}				
			

Criando Controller

Gerando

sail artisan make:controller ProductController

ProductController.php

							
							
					<?php

namespace App\Http\Controllers;

use App\Actions\ValidateProductAction;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    public function validateProduct(
        Request $request,
        ValidateProductAction $action
    ) {
        $validated = $action->execute($request->all());

        return response()->json([
            'validated' => $validated
        ]);
    }
}				
			

Criando rota

routes/api.php

							
							
					use App\Http\Controllers\ProductController;

Route::post('/products/validate', [
    ProductController::class,
    'validateProduct'
]);				
			

Testando a API

Produto físico

							
							
					{
    "type": "physical",
    "stock": 10
}				
			

Produto digital

							
							
					{
    "type": "digital",
    "license": "ABC-123"
}				
			

Produto premium

							
							
					{
    "type": "premium",
    "subscription_active": true
}				
			

Melhorando ainda mais com Factory

O próximo passo profissional seria remover o match.

Exemplo:

$strategy = ProductStrategyFactory::make($product['type']);

Isso evita:

  • crescimento infinito de condicionais
  • dependências diretas
  • violação do Open/Closed Principle

Onde essa arquitetura brilha?

Esse padrão funciona MUITO bem para:

  • gateways de pagamento
  • cálculo de frete
  • notificações
  • importadores
  • ERPs
  • antifraude
  • regras fiscais
  • workflows

Benefícios reais dessa arquitetura

Actions

  • centralizam casos de uso
  • deixam controller limpo

Events

  • desacoplam processos
  • facilitam filas e escalabilidade

Strategy

  • elimina if/switch gigantes
  • facilita manutenção

Estrutura final do projeto

							
							
					app/
├── Actions/
│   └── ValidateProductAction.php
│
├── Contracts/
│   └── ProductValidationStrategyInterface.php
│
├── Events/
│   └── ProductValidated.php
│
├── Listeners/
│   └── LogValidatedProductListener.php
│
├── Strategies/
│   └── ProductValidation/
│       ├── DigitalProductStrategy.php
│       ├── PhysicalProductStrategy.php
│       └── PremiumProductStrategy.php
│
└── Http/
    └── Controllers/
        └── ProductController.php				
			

Conclusão

Usar Laravel apenas com Controllers e Services resolve problemas pequenos.

Mas quando o sistema cresce:

  • regras explodem
  • manutenção vira pesadelo
  • testes ficam difíceis

Com:

  • Actions
  • Events
  • Strategy Pattern

você cria uma arquitetura:

  • escalável
  • desacoplada
  • profissional
  • fácil de testar
  • preparada para crescimento

Essa é uma abordagem muito usada em sistemas grandes e times maduros de Laravel.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *