Liigu peamise sisu juurde

Repository

Sissejuhatus

DAO muster annab andmetele ligipääsuks eraldi koha ning võimaldab andmesalvestuse lahendusi vahetada ilma äriloogikat muutmata. Siiski on DAO suhteliselt õhuke kiht - selle meetodid peegeldavad põhilisi andmeoperatsioone.

Rakenduse kasvades hakkab DAO-d kasutavasse koodi kogunema loogika, mis sinna tegelikult ei tohiks kuuluda. Selleks võivad näiteks olla tulemuste filtreerimine, päringute kombineerimine, tuletatud väärtuste arvutamine jne.

Repository muster lahendab seda probleemi, paiknedes DAO ja ülejäänud rakenduse vahel. See pakub domeenile kohandatud, kollektsioonilaadset liidest, peites nii DAO kui ka madalama taseme andmeoperatsioonid meetodite taha, mis väljendavad rakenduse äriloogikat.

Probleemist lähemalt

Jätkame DAO peatüki näitega. Meie rakendus peaks olema suuteline läbi viima järgnevaid päringuid:

  • Leia üles kõik raamatud ühe kindla autori poolt
  • Leia üles kõik raamatud vastavalt aastanumbrile
  • Leia üles kõik raamatud antud hinnavahemikust

Ilma repository-kihita paikneks see loogika teenuskihis:

public class BookService {
private final BookDao dao;

public BookService(BookDao dao) {
this.dao = dao;
}

public List<Book> findByAuthor(String author) {
return dao.readAll().stream()
.filter(book -> book.getAuthor().equals(author))
.toList();
}

public List<Book> findByPriceRange(BigDecimal min, BigDecimal max) {
return dao.readAll().stream()
.filter(book -> book.getPrice().compareTo(min) >= 0
&& book.getPrice().compareTo(max) <= 0)
.toList();
}

// ... many more similar methods
}

Probleemid, mis sellise lahendusega tekivad:

  • Teenusesse koguneb andmeotsingu loogika. Teenuse ülesanne on sisaldada ärireegleid, mitte filtreerimiskoodi.
  • Päringud ei ole taaskasutatavad. Kui mõnel teisel teenusel on samuti vaja autori järgi raamatuid, dubleeritakse filtreerimiskood.
  • Testimine muutub keerulisemaks. Teenus vajab nüüd DAO-d koos päris andmetega, et testida isegi kõige lihtsamat päringut.

Repository muster

Repository määratleb domeenikesksed päringumeetodid ühes kohas, pakkides DAO sisemiselt enda sisse. Ülejäänud rakendus kutsub Repositoryd ega puutu kunagi DAO-ga otseselt kokku.

public class BookRepository {
private final BookDao dao;

public BookRepository(BookDao dao) {
this.dao = dao;
}

public List<Book> findAll() {
return dao.readAll();
}

public Optional<Book> findById(int id) {
return dao.findById(id);
}

public Optional<Book> findByIsbn(String isbn) {
return dao.findByIsbn(isbn);
}

public List<Book> findByAuthor(String author) {
return dao.readAll().stream()
.filter(book -> book.getAuthor().equals(author))
.toList();
}

public List<Book> findByPriceRange(BigDecimal min, BigDecimal max) {
return dao.readAll().stream()
.filter(book -> book.getPrice().compareTo(min) >= 0
&& book.getPrice().compareTo(max) <= 0)
.toList();
}

public List<Book> findPublishedAfter(int year) {
return dao.readAll().stream()
.filter(book -> book.getYear() > year)
.toList();
}

public void save(Book book) {
dao.save(book);
}

public void delete(int id) {
dao.remove(id);
}
}

Teenuskiht suhtleb nüüd ainult repository-ga:

public class BookService {
private final BookRepository repository;

public BookService(BookRepository repository) {
this.repository = repository;
}

public List<Book> getBooksInPriceRange(String min, String max) {
BigDecimal lower = new BigDecimal(min);
BigDecimal upper = new BigDecimal(max);
return repository.findByPriceRange(lower, upper);
}

public List<Book> getRecentBooksByAuthor(String author, int sinceYear) {
return repository.findByAuthor(author).stream()
.filter(book -> book.getYear() >= sinceYear)
.toList();
}
}

Teenuskiht vastutab nüüd äriloogika täide viimise eest, repository vastutab andmepäringute eest.

Seos DAO mustriga

DAO-d ja repository-t aetakse sageli segamini, kuna mõlemad tegelevad andmetele juurdepääsuga. Peamine erinevus seisneb nende abstraktsioonitasemes:

DAORepository
SkoopSalvestusoperatsioonid (lugemine, kirjutamine, kustutamine)Domeenikontseptsioonid (raamatud autori järgi, hinnavahemiku järgi)
Teab millestAndmesalvestuse vormingust (failiread, SQL-read)Domeenimudelist (Book, Author)
KasutatakseRepository pooltService, Controller
Tüüpilised meetodidreadAll(), findById(), save(), remove()findByAuthor(), findByPriceRange(), findPublishedAfter()

DAO on õhuke tehniline adapter. Repository on domeenile suunatud kollektsioon.

Väiksemates rakendustes liidetakse need kaks mõnikord üheks klassiks. See on mõistlik kompromiss, kui eraldi DAO-kihi poolt lisatud abstraktsioon ei ole vajalik. Nende lahus hoidmine tasub end ära, kui on vaja toetada mitut andmesalvestuse lahendust või kui päringute hulk on piisavalt suur, et õigustada eraldi klassi.

Andmevoog kihtide vahel

DAO ja repository koos kasutamisel näeb andmevoog välja järgmine:

  • Controller võtab vastu päringud ja delegeerib need teenusele
  • Service rakendab äriloogikat ning kasutab andmete lugemiseks või salvestamiseks Repository't.
  • Repository tõlgib domeenipäringud DAO-kutseteks.
  • DAO teostab tegeliku lugemis- või kirjutamisoperatsiooni andmesalvestuse vastu.

Iga kiht on teadlik ainult temast vahetult allpool olevast kihist. FileBookDao asendamine DatabaseBookDao-ga ei nõua muudatusi Repository-, Service- ega Controller-kihis. See järgib sama põhimõtet nagu varem nähtud kolmekihiline arhitektuur ning on näide üldisemast N-kihilisest arhitektuurist.