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…
- DAO sprawdza się w aplikacjach o strukturze bardziej proceduralnej, gdzie potrzebujemy prostych operacji CRUD na danych.
- 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.
DAO | Repository |
---|
Skupia się na operacjach CRUD | Zarządza kolekcją obiektów |
Bezpośredni dostęp do bazy danych | Abstrakcja logiki biznesowej |
Wykorzystuje konkretne zapytania SQL | Operuje na obiektach domeny aplikacji |
Bardziej niski poziom | Wyż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.
Dodaj komentarz