ORM: Data Mapper vs Active Record

Dec 06 2022
Który jest lepszy i dlaczego?
ORM oznacza Object-Relational Mapping, czyli technikę, która przekształca obiekty z OOP w wiersze w bazie danych i odwrotnie. Istnieją dwa wzorce: Rekord aktywny Jak już wspomniałem, samą jednostkę można tworzyć, aktualizować lub usuwać.

ORM oznacza Object-Relational Mapping , czyli technikę, która przekształca obiekty z OOP w wiersze w bazie danych i odwrotnie.

Istnieją dwa wzory:

  • Rekord aktywny : Sam rekord (jednostka) może aktywnie zmieniać bazę danych
  • Data Mapper : Istnieje obiekt zewnętrzny, który utrwala jednostki

Aktywny rekord

Jak wcześniej wspomniałem, sam podmiot można tworzyć, aktualizować lub usuwać. Na przykład w PHP Eloquent to Active Record ORM opracowany przez społeczność Laravel.

final class UserCreator
{
    public function create(UserInformation $userInfo): void
    {
        $address = Address::createFrom($userInfo->address());
        $address->save() # Address is stored in the database

        $user = User::createFrom($userInfo->user());
        $user->address = $address; // Set relationship
        $user->save(); # User is stored in the database
    }
}

W mapowaniu danych potrzebujemy zewnętrznej klasy, która zaktualizuje bazę danych. Na przykład w PHP Doctrine jest de facto Data Mapper ORM dla Symfony.

final class UserCreator
{
    public function __construct(
        private EntityManager $entityManager,
    ) {}

    public function create(UserInformation $userInfo): void
    {
        $address = Address::createFrom($userInfo->address());
        $user = User::createFrom($userInfo->user());
        $user->address = $address; # Set relationship

        # We persist all objects we want to update
        $this->entityManager->persist($address);
        $this->entityManager->persist($user);

        # Finally, flushing the entity manager will execute the SQLs
        $this->entityManager->flush();
    }
}

Chociaż, jeśli spojrzysz na oba fragmenty, możesz już zobaczyć, że Data Mapper używa zewnętrznej zależności ( Entity Managerobiektu), która jest dziwna i wyraźnie, jest trudniej, jeśli zdecydujesz się wprowadzić kilka testów do klasy UserCreator, tymczasem w Active Recordklasie, encje same się ratują, więc na pierwszy rzut oka nie mamy żadnej zależności, ale to nie do końca prawda, encja sama ma wiedzę o DB i ciężko będzie napisać jakieś testy, jeśli użyjemy encji bezpośrednio User.

Wzór repozytorium

Wzorzec repozytorium to wzorzec projektowy, który ukrywa logikę bazy danych w nowej klasie, ujawniając tylko metody, które chcemy udostępnić użytkownikowi.

Aby to zrobić, musimy stworzyć interfejs i klasę, która zaimplementuje interfejs, w którym usuniemy całą logikę.

# App/User/Domain
interface UserRepository
{
    public function save(UserInformation $userInfo): void;
}

~~~~

# App/User/Infrastructure
final class ActiveRecordUserRepository implements UserRepository
{
    public function save(UserInformation $userInfo): void
    {
        $address = Address::createFrom($userInfo->address());
        $address->save()

        $user = User::createFrom($userInfo->user());
        $user->address = $address;
        $user->save();
    }
}

final class DataMapperUserRepository implements UserRepository
{
    public function __construct(
        private EntityManager $entityManager,
    ) {}

    public function create(UserInformation $userInfo): void
    {
        $address = Address::createFrom($userInfo->address());
        $user = User::createFrom($userInfo->user());
        $user->address = $address;

        $this->entityManager->persist($address);
        $this->entityManager->persist($user);
        $this->entityManager->flush();
    }
}

final class UserCreator
{
    public function __construct(
        private UserRepository $userRepository,
    ) {}

    public function create(UserInformation $userInfo): void
    {
        $this->userRepository->save($userInfo);
    }
}