Qual è l'approccio corretto al livello DAO in presenza di framework ORM

3

Sto usando JPA / Hibernate ma probabilmente non limita la domanda.

Attualmente sto scrivendo il mio codice di accesso ai dati nelle mie classi di controller web. Grazie a JPA, nella maggior parte dei casi questo codice è molto semplice, in pratica semplice query con configurazione banale.

Ho provato a spostare questi metodi in classi separate per migliorare il riutilizzo del codice. Ma ho riscontrato un problema importante. Sebbene esistano metodi simili, non sono uguali. Questo vale in particolare per il recupero delle classi unite (tabelle). In alcuni controller non ho bisogno di andare a prendere quelle lezioni. Ma in alcuni controller ho bisogno di questo. Quindi ho le seguenti varianti:

  1. Sposta tutti i metodi, anche leggermente diversi, come metodi separati nella classe DAO. Questo rende DAO complesso, i loro nomi di metodo saranno molto lunghi, sarebbe difficile trovarne uno corretto.

  2. Dimentica i join di recupero. Applicazione di installazione per eseguire il caricamento quando necessario (caricamento lento). Ciò ridurrà il numero di metodi, ma l'accesso al database sarà molto meno efficace e la transazione deve estendersi su tutta la richiesta, non solo sul codice del controller.

  3. Inserisci la logica complessa nel metodo quando necessario. Per esempio. fornire parametri booleani fetchSomething . E il metodo ottimizzerà la query in base a quei parametri. L'implementazione del metodo sarà molto complessa dato che dovrei creare una stringa di query da pezzi o utilizzare un'API di criteri molto ingombrante. Il metodo sarà difficile da leggere e capire.

  4. Invia così com'è. In genere mi piace questo approccio. Porta ad alcune duplicazioni di codice, ma non molto e le query sono dichiarative quindi non vedo molti problemi con questo. Il problema principale è la mancanza di testabilità. Per testare il controller avrei bisogno di preparare un database, perché non c'è (o non ho trovato) un'implementazione di simulazione JPA complessa con dati in memoria forniti.

Mi chiedo quale approccio viene utilizzato in progetti complessi? Ho visto il 4 ° approccio e ho visto un ORM interno molto complesso basato su Hibernate.

Sto parlando ora del cosiddetto "modello di repository". Non capisco veramente cosa significhi la gente per "livello di servizio" e non ho mai visto alcuna necessità con questo, quindi attualmente la domanda riguarda le classi di repository di dati.

    
posta vbezhenar 10.05.2015 - 11:50
fonte

2 risposte

2

Come vedi, la soluzione potrebbe non essere molto soddisfacente. Il problema dell'accesso ai dati è che dobbiamo considerare molti aspetti diversi che rendono il codice non riutilizzabile al 100%. Riguarda le transazioni e il blocco e il modo, ad es. ORM associa un database relazionale agli oggetti e viceversa.

Soprattutto se provi a scrivere componenti di architettura, cercando di risolvere il problema che noterai presto, devi semplicemente spostare il problema in un altro posto. Il motivo è semplicemente che un approccio di tipo bottom-top come la tua domanda è, è una cattiva idea.

Ciò che vogliamo è una strategia top-down agile. Vedrai che la maggior parte degli accessi sono di sola lettura, che (quasi) significa nessun blocco e nessuna transazione. Alcune scritture sono aggiornamenti di campo che non devono essere sincronizzati e alcuni inserimenti in tabelle diverse potrebbero richiedere il blocco e devono essere in una transazione.

Ora alle tue varianti:

Move all methods, even slightly different, as a separate methods into DAO class. This makes DAO complex, their method names will be very long, it would be hard to find correct one.

viola l'OCP. ci dovrebbe essere una sola entità / definizione della tabella e componenti di creazione di query attorno ad essa, ma solo a seconda dell'entità stessa. Ciò significa che non ci sono grandi classi di accesso.

Forget about fetch joins. Setup application to perform loading when needed (lazy loading). This will reduce number of methods, but database access will be much less effective and transaction must span across entire request, not just controller code.

il caricamento lento è interessante per il codice che dovrebbe essere più mirato rispetto a quello performante, ad es. back-end con piccole liste e pochi amministratori. Non è una buona idea per le pagine front-end con accesso permanente non collegate.

Put complex logic into method when needed. E.g. supply boolean parameters fetchSomething. And method will tweak query according to those parameters. Method implementation will be very complex as I would have to either build query string from pieces or use very cumbersome Criteria API. Method will be hard to read and understand.

I parametri

come offset e limite sono ok, ma i parametri booleani se caricare sparse o complete è troppo sovraccarico nel codice, il che potrebbe portare a un comportamento magico.

Leave as is. I generally like this approach. It leads to some code duplication, but not very much and queries are declarative so I don't see much problems with that. The main problem is lack of testability. To test controller I would need to prepare a database, because there's no (or I didn't found) complex JPA mock implementation with supplied in-memory data.

Per i test di integrazione si, è necessario salvare alcuni dati nel database (è possibile utilizzare, ad esempio sqlite). Per i test unitari no, si prende in giro il livello di servizio in base all'input del controller e si verifica come si comportano i controller.

I wonder what approach used in complex projects? I saw 4-th approach and I saw once very complex in-house ORM based on Hibernate.

I grandi progetti utilizzano in realtà impostazioni orientate ai servizi al massimo livello e utilizzano ORM o query semplici in base alle loro esigenze. Non esiste una sola soluzione.

I'm talking now about so-called "repository pattern". I don't really understand what people mean by "service layer" and never saw any need with that, so currently the question is about data repository classes.

Le IO-Applications sono quasi uguali. C'è una richiesta e una risposta. Il responsabile del trattamento è responsabile della raccolta dei dati dalla richiesta per chiamare un servizio che produce dati che saranno raccolti e restituiti. Per esempio. la logica aziendale e tutti i casi d'uso del software sono al 100% parte del livello di servizio. Testare l'unità controllando i servizi è molto più semplice.

"Modello di repository" :

Ho trascorso molto tempo su Doctrine e PHP e negli ultimi anni ho abbandonato i repository, perché l'1% sono semplicemente operazioni di repository come un elenco di paesi ordinati per nome, l'80% sono query DQL o querybuilder più complesse , anche con risultati flat e il resto sono query SQL che non riesco a scrivere con DQL.

Il risultato della maggior parte delle query è una tabella con colonne di diverse tabelle unite. ORM non avrebbe alcun senso, quindi non uso più repository, ma un livello di servizio pulito e un paio di servizi di accesso ai dati.

    
risposta data 11.05.2015 - 00:45
fonte
1

Preferisco il pattern Table Data Gateway, ovvero un oggetto stateless per approssimativamente ogni tipo di entità, contenente metodi come

void CustomerRepository.createCustomer(Customer c)

List<Customer> CustomerRepository.getCustomersByCountry(String country)
  • Facile aggiunta di ulteriore gestione come serializzazione, gestione degli errori, registrazione, convalida, memorizzazione nella cache.
  • Facile trovare la funzione corretta
  • Meno probabilità di avere un codice duplicato
  • Facile migrazione a una tecnologia di database diversa
  • Facile da simulare per i test
  • Facile da utilizzare per il monitoraggio delle prestazioni
risposta data 10.05.2015 - 23:38
fonte

Leggi altre domande sui tag