Repository i DAO, wzorce warstwy dostępu do danych

Repository i DAO to popularne wzorce projektowe, które stosuje się w warstwie dostępu do danych. Przyjrzyjmy się, jak działają, czym się różnią i kiedy ich używać.

Repository i DAO

Warstwa dostępu do danych jest zwykle odpowiedzialna za wiele typów logiki związanej z operacjami na bazie danych – od prostego tworzenia, czytania, aktualizowania i usuwania rekordów w podłączonej bazie danych, po niekiedy bardziej skomplikowane operacje. I teraz, w momencie kiedy kod związany z tymi działaniami jest rozsiany po całej aplikacji, szybko staje się trudny w utrzymaniu. Właśnie dlatego istnieją takie wzorce projektowe jak Repository i DAO (Data Access Object). Pozwalają one na jawne wydzielenie warstwy dostępu do danych, a tym samym na oddzielenie logiki aplikacji od mechanizmów zapisu i odczytu danych. Czym się od siebie różnią?

Czym jest DAO (Data Access Object)?

Data Access Object to wzorzec projektowy, który skupia się na enkapsulacji operacji związanych z dostępem do bazy danych. DAO wprowadza abstrakcję nad niskopoziomowym kodem, odpowiedzialnym za operacje na danych, takimi jak SQL lub inne operacje bazodanowe. DAO zarządza szczegółami technicznymi związanymi z nawiązywaniem połączeń i operacjami CRUD. Dodatkowo, definiuje metody, które są używane przez wyższe warstwy aplikacji, dzięki czemu programista nie musi wiedzieć, jak dokładnie działa połączenie z samą bazą danych.

Przykład DAO

Klasa DAO może bazować na zapytaniach SQL, bądź metodach ORM czy query builder-a, nie ma to większego znaczenia. Chodzi tutaj wyłącznie o bycie abstrakcją do operacji na bazie danych i możliwość reużycia zapytania.

Poniższy przykład Data Access Object, który używa funkcji ORM do generowania i uruchamiania zapytań do bazy może teraz z powodzeniem zostać wstrzyknięty w dowolne miejsce systemu i być dobrą abstrakcją na tego typu działania.

export class TagSqlDAO implements TagDAO {
  constructor(
    private readonly db: Database
  ) {}

  async getAll(): Promise<TagEntity[]> {
    return await this.database.table('tags')
      .leftJoinAndSelect('tags.site', 'site')
      .leftJoinAndSelect('site.pages', 'pages')
      .getMany();
  }

  async truncate(): Promise<void> {
    return await this.database.table('tags')
      .truncate();
  }
}

Zauważ proszę, że umieściłem w nim również metodę truncate – mimo, że nie ma to większego sensu, zrobiłem to celowo, aby uzmysłowić Ci, że DAO, to po prostu niskopoziomowy wrapper na bazę, zapytania.

Czym jest Repository (wzorzec repozytorium)?

Repozytorium w ogólnym ujęciu jest wzorcem projektowym, który podobnie, jak DAO, służy do oddziału warstwy logiki oraz warstwy dostępu do danych / persystencji. Sama persystencja może odbywać się na bazie danych, plikach lub dotyczyć zewnętrznej usługi. Repozytorium dostarcza metody, które przypominają te stosowane w kontekście kolekcji danych. Używając więc instancji repozytorium możemy zwyczajowo: wyszukiwać, dodawać, usuwać czy zmieniać pewne dane. W najprostszych rozwiązaniach repozytoria spotkać możemy wraz z systemami ORM, gdzie służą one do pośredniej komunikacji z bazą danych. 

Repozytorium powinno dotyczyć tylko jednego typu danych (podobnie, jak kolekcja) i nie oferować możliwości operowania np. na kilku tabelach (co jest możliwe w DAO).

Wzorzec ten jest bardziej zaawansowany od DAO. W ciałach metod możemy również tworzyć instancje obiektów, encji wyższego poziomu na podstawie surowych danych, tak, aby w momencie odczytania były gotowe do użycia, np. w klasie serwisowej.

Przykład Repository

Poniższy przykład wzorca repozytorium zawiera metodę findOneById, która najpierw odpytuje bazę danych – w bardzo podobny sposób, jak DAO w poprzednim przykładzie, a następnie wykonuje coś więcej, sprawdza czy rekord istnieje i na jego podstawie tworzy obiekt User.

export class UserSqlRepository implements UserRepository {  
  constructor(
    private readonly db: Database
  ) {}

  async findOneById(id: string): Promise<User | null> {
   const record = await this.database
     .table('users')
     .where('id', '=', id)
     .getOne();
   
   if (!record) return null;
 
   const address = JSON.parse(user.address);

   return User.create({
     ...record,
     address
   });
  }
}

Nic nie stoi na przeszkodzie, aby nasze repozytorium użyło DAO, bo z drugiej strony o to właśnie nam chodzi, aby DAO było wrapper-em na bazę danych. Repozytorium pozostanie wtedy tym czym w idealnym świecie być powinno: dynamiczną kolekcją, której źródłem jest baza danych.

export class UserSqlRepository implements UserRepository {  
  constructor(
    private readonly userDAO: UserDAO
  ) {}

  async findOneById(id: string): Promise<User | null> {
   const record = await this.userDAO.getOneById(id);
   
   if (!record) return null;
 
   const address = JSON.parse(user.address);

   return User.create({
     ...record,
     address
   });
  }
}

Jak widzisz, repository jest bardziej elastyczne niż DAO, ponieważ skupia się na interakcjach wyższego poziomu – można tu także dodać filtrowanie, sortowanie czy dodatkową agregację już na danych pobranych z bazy.

Repository i DAO – co wybrać? jak żyć?

Wybór pomiędzy DAO a Repository zależy od kontekstu operacji w naszej aplikacji…

  1. DAO sprawdza się w aplikacjach o strukturze bardziej proceduralnej, gdzie potrzebujemy prostych operacji CRUD na danych.
  2. Repository znajduje zastosowanie w bardziej złożonych aplikacjach, opartych o obiektowy model domeny. Pozwala na lepszą integrację z logiką biznesową i umożliwia bardziej skomplikowane operacje na obiektach.

W praktyce często wykorzystuje się oba wzorce w jednej aplikacji. DAO można użyć do implementacji niskopoziomowych operacji (np. dostępu do SQL), natomiast Repository może wywoływać te operacje, aby zarządzać kolekcjami obiektów i dbać o spójność na wyższym poziomie abstrakcji.

DAORepository
Skupia się na operacjach CRUDZarządza kolekcją obiektów
Bezpośredni dostęp do bazy danychAbstrakcja logiki biznesowej
Wykorzystuje konkretne zapytania SQLOperuje na obiektach domeny aplikacji
Bardziej niski poziomWyższy poziom, związany z logiką domeny

Podsumowanie

Repository i DAO to potężne wzorce, które – choć różnią się poziomem abstrakcji – wzajemnie się uzupełniają i pozwalają na zorganizowanie warstwy dostępu do danych. DAO sprawdza się w aplikacjach, które wymagają bezpośrednich operacji na bazach danych, podczas gdy Repository lepiej integruje się z logiką biznesową aplikacji o bardziej złożonej strukturze domeny.

Dobrze przemyślany wybór pomiędzy nimi, a często ich łączenie, zapewnia przejrzystość i łatwość w utrzymaniu aplikacji.

Autor wpisu

blog@orbisbit.com

Komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Sprawdź również