Zrozumieć Value Object cover image

Photo by Glen Carrie on Unsplash

Zrozumieć Value Object

Dariusz Ciesielski • 07/06/2020

Value Object jest to jeden z podstawowych budulców Domain Driven Design, aczkolwiek można go zastosować z każdym projekcie w celu poprawy jakości swojego kodu.

Skoro więc wiemy, że od razu możemy wdrożyć Value Object do naszego projektu, pytanie jakie możesz sobie właśnie zadać brzmi "W jakich okolicznościach przyda mi się VO, jak wygląda i do czego dokładnie służy?".

Zakładam, że typy proste z języka programowania nie są Ci obce. Mamy m.in. integer, string, boolean. Możesz sobie wyobrazić że Value Object jest takim typem prostym, tylko tworzonym przez nas - ja bym to nazwał Customowym Typem, niekoniecznie prostym ;)

Jak wiemy typ integer będzie miał swój zakres, który jest poprawny. Dla systemu 32 bitowego będzie to -2,147,483,647 - 2,147,483,647, jeżeli przekroczymy ten zakres PHP zwróci błąd. W podobny sposób działa Value Object - jest to jakiś byt/obiekt, który posiada pewne zasady działania.

Dla przykładu załóżmy, że posiadamy w systemie klasę Client, która posiada pole age. W standardowy sposób przesłalibyśmy zmienną $age o typie prostym int,

class Client {
    public function setAge(int $age) {
        ...
    }
}

Jak wiemy wiek nie może być dowolną liczbą. Nie ma ludzi na świecie którzy posiadają wiek mniejszy niż 0, tak samo jak nie ma ludzi którzy żyją po 300 lat. Niestety int nie daje nam możliwości powyższej walidacji i tu z pomocą przychodzi nam Value Object.

Stwórzmy więc nasz Customowy Typ - Value Object i dodajmy powyższą walidację.

final class Age {
    private $age;

    public function __construct(int $age) {
        if($age < 0) {
            throw new Exception('Invalid age');
        }

        // najstarszy żyjący człowiek miał 122 lata, więc załóżmy, 
        // że w przyszłości ktoś pożyje trochę dłużej
        if($age > 130) { 
            throw new Exception('Invalid age');
        }

        $this->age = $age;
    }
}

Zmieńmy więc typ prosty w klasie Client na nasz Customowy Typ - Value Object

class Client {
    public function setAge(Age $age) {
        ...
    }
}

Tym samym jeżeli poda się niewłaściwe dane mamy pewność, że walidacja nie przejdzie

$client = new Client();
$client->setAge(new Age(25)); // ok
$client->setAge(new Age(-20)); // wyjątek
$client->setAge(new Age(156)); // wyjątek

Oczywiście możesz pomyśleć "przecież mogę to samo uzyskać tworząc walidację w kontrolerze" i oczywiście masz rację. Nie ma problemu, abyś stworzył walidację, czy to w kontrolerze, czy w serwisie, czy w Form Request (o ile korzystasz z frameworka Laravel), tylko zauważ, że w tym przypadku mamy możliwość walidacji tego pola w dowolnym miejscu w systemie, a nie tylko podczas przesyłu requestu. Co jeżeli w przyszłości Twój system będzie wywoływał tworzenie klienta nie tylko za pomocą żądania http, ale też za pomocą cli. Musiałbyś wtedy powielić walidację, a tak spokojnie możesz wykorzystać Value Object w dowolnym miejscu początkowej walidacji (żądania http, cli, itd.), czy też sprawdzenia poprawności danych z bazy danych.

Mam nadzieję, że pomogłem Ci w zrozumieniu podstaw działania oraz wykorzystania Value Object, ale żeby VO był VO potrzeba czegoś więcej niż tylko dodanie walidacji w konstruktorze.

Value Objects muszą pozostać:

  • niezmienne (immutable)
  • spójne z całością
  • porównywalne
  • pozbawione efektów ubocznych (Side-Effect-Free Behaviour)

ale o tym w kolejnym artykule.