0Pricing
PHP Academy · Aula

Casos de uso e serviços de aplicação

Expresse ações de negócio como casos de uso independentes de frameworks.

Casos de uso e serviços de aplicação é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.

O que um caso de uso realmente é

Um caso de uso (também chamado de serviço de aplicação ou interator) representa exatamente uma operação específica da aplicação: Registrar usuário, Fazer pedido, Cancelar assinatura. Ele coordena entidades e portas para cumprir uma única intenção. O mais importante é que ele é independente de frameworks: não há Request, nem Response, nem auxiliares globais — apenas PHP simples que pode ser chamado de qualquer lugar.

DTOs de comando e resultado

Um caso de uso recebe um DTO de comando imutável como entrada e retorna um DTO de resultado. DTOs são simples portadores de dados — não têm comportamento nem lógica de validação além da estrutura. Propriedades readonly (PHP 8.1 ou superior) impedem adulterações.

<?php
final class RegisterUserCommand
{
    public function __construct(
        public readonly string $email,
        public readonly string $plainPassword,
    ) {}
}

final class RegisterUserResult
{
    public function __construct(public readonly string $userId) {}
}

O corpo do serviço de aplicação

O serviço traduz o comando em operações de domínio. Ele faz a orquestração no nível da aplicação — verificações de unicidade, persistência e retorno de identificadores — enquanto delega as regras às entidades.

<?php
final class RegisterUser
{
    public function __construct(
        private Users $users,
        private PasswordHasher $hasher,
    ) {}

    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        if ($this->users->existsByEmail($c->email)) {
            throw new EmailAlreadyRegistered($c->email);
        }
        $user = User::register(
            UserId::generate(),
            new Email($c->email),
            $this->hasher->hash($c->plainPassword),
        );
        $this->users->add($user);
        return new RegisterUserResult((string) $user->id());
    }
}

Mantenha a lógica nas entidades

Cuidado com o modelo de domínio anêmico: entidades reduzidas a métodos de leitura e atribuição, enquanto toda a lógica fica nos serviços. As invariantes pertencem à entidade. O caso de uso deve parecer um pequeno roteiro de intenções, não um bloco enorme de regras de negócio.

<?php
final class User
{
    private function __construct(
        private UserId $id,
        private Email $email,
        private string $passwordHash,
        private bool $active = false,
    ) {}

    public static function register(UserId $id, Email $e, string $hash): self {
        return new self($id, $e, $hash); // invariants enforced here
    }
    public function activate(): void {
        if ($this->active) throw new AlreadyActive();
        $this->active = true;
    }
    public function id(): UserId { return $this->id; }
}

Limites de transação

Um caso de uso é o limite de transação natural: um caso de uso = uma unidade de trabalho consistente. Em vez de espalhar beginTransaction() pelos serviços, envolva-os com um decorador transacional para manter o núcleo independente da persistência.

<?php
interface TransactionManager {
    public function transactional(callable $work): mixed;
}

final class TransactionalRegisterUser
{
    public function __construct(
        private RegisterUser $inner,
        private TransactionManager $tx,
    ) {}

    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        return $this->tx->transactional(fn() => ($this->inner)($c));
    }
}

Validação: onde ela pertence

Divida a validação em dois níveis:

  • Validação de entrada (formato e campos obrigatórios) ocorre no adaptador de entrada ou em um validador dedicado, antes da execução do caso de uso.
  • Validação de domínio (invariantes e regras de negócio) fica nos objetos de valor e nas entidades, que lançam exceções de domínio.

O caso de uso pressupõe uma entrada bem-formada e impõe seu significado.

<?php
final class Email
{
    public function __construct(public readonly string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: $value");
        }
    }
}

try { new Email('nope'); } catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; }
echo (new Email('a@b.com'))->value, PHP_EOL;

Retornando a saída sem HTTP

Há dois padrões para retornar dados sem depender de frameworks:

  • Retornar um DTO de resultado (simples e síncrono).
  • Porta de saída / apresentador — o caso de uso envia o resultado para um limite de saída injetado, permitindo que o adaptador decida a formatação (JSON, HTML, CLI). Assim, até mesmo o formato da resposta permanece fora do núcleo.
<?php
interface RegisterUserOutput {
    public function present(RegisterUserResult $r): void;
}

final class RegisterUserWithPresenter {
    public function __construct(private Users $users, private PasswordHasher $h) {}
    public function __invoke(RegisterUserCommand $c, RegisterUserOutput $out): void {
        $user = User::register(UserId::generate(), new Email($c->email), $this->h->hash($c->plainPassword));
        $this->users->add($user);
        $out->present(new RegisterUserResult((string) $user->id()));
    }
}

Eventos de domínio a partir de casos de uso

Os casos de uso frequentemente registram eventos de domínio gerados pelas entidades e depois os despacham quando a transação é confirmada. Isso desacopla efeitos colaterais (enviar um e-mail de boas-vindas, atualizar um modelo de leitura) do fluxo de trabalho do núcleo.

<?php
trait RecordsEvents {
    private array $events = [];
    protected function record(object $e): void { $this->events[] = $e; }
    public function releaseEvents(): array {
        $e = $this->events; $this->events = []; return $e;
    }
}

final class UserRegistered {
    public function __construct(public readonly string $userId) {}
}

// Use case calls $user->releaseEvents() and hands them to a dispatcher
echo 'event recorded pattern', PHP_EOL;

Uma classe por caso de uso

Prefira uma classe de ação única (um método público, frequentemente __invoke) a um serviço extenso com dez métodos. Benefícios:

  • Responsabilidade única clara e nomes precisos (CancelSubscription, não SubscriptionService::cancel).
  • O construtor injeta apenas o que esta operação precisa.
  • É fácil envolvê-la com decoradores (transação, registro, autorização).

Configuração na raiz de composição

O caso de uso nunca cria diretamente suas dependências; a raiz de composição faz isso. Esta é uma configuração manual que poderia ser colocada na definição de um contêiner de DI.

<?php
$pdo      = new PDO('sqlite::memory:');
$users    = new PdoUsers($pdo);
$hasher   = new BcryptHasher();
$register = new RegisterUser($users, $hasher);

// Decorate with a transaction boundary
$register = new TransactionalRegisterUser($register, new PdoTransactionManager($pdo));

// Driving adapter calls it
$result = $register(new RegisterUserCommand('dev@coddykit.com', 's3cret!'));
echo $result->userId, PHP_EOL;

Preocupações transversais por meio de decoradores

Registro, métricas e autorização são preocupações transversais — mantenha-as fora do corpo do caso de uso. Envolva o serviço em decoradores que compartilham sua interface, para que o núcleo permaneça concentrado no fluxo de trabalho enquanto as preocupações de infraestrutura são compostas ao redor dele.

<?php
interface RegisterUserHandler {
    public function __invoke(RegisterUserCommand $c): RegisterUserResult;
}

final class LoggingRegisterUser implements RegisterUserHandler {
    public function __construct(
        private RegisterUserHandler $inner,
        private LoggerInterface $log,
    ) {}
    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        $this->log->info('register.start', ['email' => $c->email]);
        $r = ($this->inner)($c);
        $this->log->info('register.ok', ['id' => $r->userId]);
        return $r;
    }
}

Verificação rápida

Onde deve ficar a regra "um e-mail deve ser exclusivo e bem-formado"?

Recapitulação

Casos de uso independentes de frameworks proporcionam uma camada de aplicação limpa:

  • Uma classe de ação única por operação, recebendo um DTO de comando e retornando um DTO de resultado (ou enviando o resultado para uma porta de saída).
  • Entidades e objetos de valor são responsáveis pelas invariantes; o serviço apenas orquestra — evite modelos anêmicos.
  • Os casos de uso são limites de transação, envolvidos por decoradores em vez de conterem beginTransaction diretamente.
  • Separe a validação de entrada (adaptador/objeto de valor) da validação de domínio (entidades).
  • Eventos de domínio desacoplam efeitos colaterais; a raiz de composição conecta as dependências.

Perguntas Frequentes

A aula “Casos de uso e serviços de aplicação” é grátis?

Sim — o texto completo de “Casos de uso e serviços de aplicação” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.

O que vou aprender em “Casos de uso e serviços de aplicação”?

Expresse ações de negócio como casos de uso independentes de frameworks. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar PHP Academy?

Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Casos de uso e serviços de aplicação”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de PHP Academy?

Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Da arquitetura em camadas à arquitetura limpa
  2. Portas e adaptadores explicados
  3. Casos de uso e serviços de aplicação
  4. Inversão de dependências na prática
← Voltar para PHP Academy