Objetos de Valores e sua importância

Desenvolver uma aplicação e ter a certeza do recebimento do tipo de dado, garante uma qualidade melhor no seu software, teste de unidades mais sólidos e concisos. Objetos de Valores os "VO", como usar e qual sua importância na fila do pão.

Objeto de Valor

Um objeto de valor é um objeto que agrupa um ou mais Objetos de Valores que garante todos os dados que estejam la façam sentido para sua aplicação, que gere valor, que não tenha strings vazias, que os dados sejam validos, e lançando exceções quando estes não fazem sentido.

Objetos de Valores não são DTO`s

DTO`s Data Transfer Object são Objetos usados única e exclusivamente para transportar dados de uma camada para outra, simplesmente isso e nada mais. Um DTO é usado em dois momentos, o momento um é na entrada de Dados, seja em um controller ou command, o segundo momento na saída do dado, seja ele vindo de uma Model, Repository ou na Saída do Controller, não possuem logicas de programação são ReadOnly, encare um DTO como uma maleta, no qual você coloca seus pertences e transporta-os para um lado e para o outro, quando você vai viajar (Saída) ou quando vai voltar (Entrada), vice-versa. :)

Prática

Um exemplo comum para um Objeto de Valor do tipo email, é que o mesmo seja um email válido, no entanto, podemos nos deparar com uma validação a mais que é o domínio do email tem que ser exclusivamente de um tipo, no nosso caso o email só poderá ser do domínio jsjunior.dev

Mostrando passo a passo do registro do email sendo validado pelo domínio no nosso exemplo básico só será permitido cadastrar email do meu site.

//routes/api.php <?php use App\Http\Controllers\Api\NewsLetters\Subscribe; use Illuminate\Support\Facades\Route; Route::post( '/newsletters/subscribe', Subscribe::class, )->name('newsletters.subscribe');

Meu controller, nota que não fiz um service, para de fato cadastrar o email estamos somente simulando o uso. Outra observação eu usei um Data Transfer Object, neste poderia ou não usá-lo neste exemplo. No entanto, percebemos a funcionalidade separada de cada um.

//controllers/Api/NewsLetters/Subscribe.php <?php declare(strict_types=1); namespace App\Http\Controllers\Api\NewsLetters; use App\Http\Controllers\Controller; use App\Http\DataTransferObjects\SubscribeDTO; use App\Http\Requests\SubscribeRequest; use App\ValueObjects\Email; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; final class Subscribe extends Controller { public function __invoke(SubscribeRequest $request): JsonResponse { $validated = $request->validated(); $subscribe = new SubscribeDTO( name: $validated['name'], email: new Email($validated['email']), ); //todo: Service return response()->json([ 'email' => $subscribe->email->getEmail() ], Response::HTTP_CREATED); } }

Também criei o SubscribeRequest.php para validação utilizando os recursos do Framework Laravel, o mais importante é quando usamos VO desacoplamos também validações que seria importante para o nosso negócio.

//SubscribeRequest.php //... public function rules(): array { return [ 'name' => 'required', 'email' => 'required|email', ]; } //...

Segue meu DTO, outro detalhe, estou usando o PHP8.2 e já utilizando o recurso de readonly das classes.

Observe que no meu DTO eu especifiquei que o meu email é do Tipo Email, que é a nossa classe de Value Objects.

//SubscribeDTO.php <?php namespace App\Http\DataTransferObjects; use App\ValueObjects\Email; final readonly class SubscribeDTO { public function __construct( public string $name, public Email $email, ){ } }

Agora a tão esperada classe de Value Objects, com validação de email, e com validação do domínio do email

//Email.php <?php declare(strict_types=1); namespace App\ValueObjects; use App\Exceptions\InvalidDomainEmailException; use App\Exceptions\InvalidEmailException; /** * @object-type ValueObject */ final readonly class Email { public function __construct( private string $email, ) { $this->validate($this->email); } /** * @return string */ public function getEmail(): string { return $this->email; } /** * @throws InvalidEmailException * @throws InvalidDomainEmailException */ private function validate(string $email): string { $this->isValidEmail($email); $this->isValidDomain($email); return $this->email; } /** * @throws InvalidEmailException */ private function isValidEmail(string $email): void { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new InvalidEmailException(); } } /** * @throws InvalidDomainEmailException */ private function isValidDomain(string $email): void { $domain = "@jsjunior.dev"; if(!preg_match("~(?<=\w)($domain)~",$email)){ throw new InvalidDomainEmailException(); } } }

As boas práticas de programação sugere não usarmos EmailVO ou EmailValueObjects na criação do nome da classe, nisso aconselha-se a criar um tipo na documentação como no exemplo.

// @object-type ValueObject

Também criei classes exclusivas de Exception

//InvalidEmailException.php <?php namespace App\Exceptions; use Exception; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; class InvalidEmailException extends Exception { public function render(): JsonResponse { return response()->json([ 'message' => 'The email field must be a valid email address.', ], Response::HTTP_UNPROCESSABLE_ENTITY); } } //InvalidEmailException.php <?php namespace App\Exceptions; use Exception; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; class InvalidDomainEmailException extends Exception { public function render(): JsonResponse { return response()->json([ 'message' => 'Invalid domain for email', ], Response::HTTP_UNPROCESSABLE_ENTITY); } }

Pronto agora eu garanto, que o parâmetro email que estou recebendo é um email e é do Tipo Email que eu definir. Isso te deixa mais seguro na criação de aplicações, e pode usar Value Objects para ter seus objetos validos e da forma que você espera, pode ser um endereço (que vai conter, rua, número, complemento, cidade, estado) ou um Value Objeto do Tipo Moeda (que vai ter valores positivos, com sigla da moeda e casas decimais). Assim você deixa seu sistema menos acoplado e mais profissional e garante os tipos de dados de entrada, que você espera.

Espero que tenham gostado e até a próxima.

Dúvidas ou sugestões me mandem um Direct no twitter ou um email.

Por: J.S.Júnior