Pattern del repository e query personalizzate / API REST. È l'approccio giusto?

2

Sono nelle prime fasi di lavoro su un'applicazione che utilizza il modello di repository per fornire un'astrazione di accesso ai dati. Questa applicazione avrà una qualche forma di una semplice API REST ma non sono sicuro di come affrontare questo utilizzando repository. Per illustrare questo, un esempio tipico (molto semplificato) di un repository è qualcosa del genere:

<?php
$repo = new PostRepository(); // this would be resolved from an IoC container
$post = $repo->getByEmail('[email protected]');

o forse un po 'meno rigido, come questo:

$post = $repo->getBy('first_name', 'Bob');

Ma il modello non sembra adatto a qualcosa di più complicato come, ad esempio, la query da questo URL:

api.example.com/posts?firstName=Bob&lastName=Doe&[email protected]&with=comments

Sembra che per gestire cose del genere (per non parlare di query molto più complicate supportate da molte API) si finirebbe per dover reimplementare l'ORM sottostante, o creare un'astrazione molto leaky, nessuna delle due sembra ideale. Sto andando su questo sbagliato?

    
posta dtaub 30.01.2015 - 22:49
fonte

2 risposte

0

Un modo per trovare le istanze in base a criteri variabili, come suggerito da anteprime di scriptin è il modo giusto. Tuttavia, non è necessario il peso totale dei framework menzionati.

Ho recentemente programmato esattamente quello che hai descritto. Il repository necessita di meta-informazioni sui campi dell'oggetto che stai richiedendo - una classe di configurazione è una buona opzione e un modo per verificare se una richiesta è valida per un oggetto (un modo per convalidare i criteri - basato sulla meta-informazione ).

Nella mia implementazione, ho unificato il pattern del repository con il pattern identity-map. Il repository contiene una raccolta di entità, inizialmente vuota. Una richiesta di selectByCriteria in entrata controllerà prima la raccolta e, se non viene trovata alcuna entità, i criteri verranno passati a un metodo per la trasformazione in una query db ed eseguiti. Le righe restituite vengono archiviate come entità nella raccolta-repository e quindi restituite al chiamante del metodo come propria "sotto-raccolta". Le potenziali future richieste a tali risorse nel corso della vita dell'applicazione verranno quindi soddisfatte dalla raccolta mantenuta in memoria.

Hai bisogno di un modo per normalizzare i criteri di selezione. Il tipo di richieste che un'API REST deve soddisfare può essere facilmente normalizzato in Disjunctive Normal Form (DNF) o Congiuntivo modulo normale (CNF) . Per semplicità di codice e logica, ho scelto DNF per la mia implementazione: leggero, come semplice array depth-3.

Un insieme di criteri di selezione sarebbe quindi simile a questo:

array(                                               
    array(                                           
        array('fieldName','comparator','fieldValue'),
        **AND**                                      
        array('fieldName','comparator','fieldValue') 
        ...                                          
    ),                                               
    **OR**                                           
    array(                                           
        array('fieldName','comparator','fieldValue'),
        **AND**                                      
        array('fieldName','comparator','fieldValue') 
        ...                                          
    )                                                
    ...                                              
)                                                    

(I comparatori non devono essere stringhe, possono essere costanti di classe - i valori non devono necessariamente essere stringhe - anzi è più trasparente usare i tipi con cui vengono confrontati - specialmente se abbiamo bisogno per costruire una DB-Query in seguito)

Un oggetto di servizio prima convalida un insieme di criteri rispetto a un oggetto di configurazione per una classe di dati, controllando la struttura dell'array, se esistono nomi di campo, se i comparatori sono consentiti e se i tipi di campo possono essere significativamente confrontato con i valori dati e comparatori. Se l'array dei criteri è valido, viene verificato con foreach-loop contro la raccolta. Il vantaggio di DNF è che un ciclo può terminare controllando ogni disgiunzione una volta che il primo congiunto non è valido, e può terminare l'intero ciclo esterno (oltre i disgiunti) una volta che il primo disgiunto è stato trovato valido - semplificando il codice.

La selezione da una raccolta è implementata in questo modo:

    $resultCollection  = $this->getEmptyCollection;

    if(!$this->criteriaValidationService->validateCriteria($dataObjectConfig,$criteria)) {
        return $resultCollection;
    }

    foreach ($this->elements as $instance) {                                                           
        foreach ($criteria as $disjunctCriteria) {                                                     
            $isValidDisjunct = TRUE;                                                                   
            foreach ($disjunctCriteria as $conjunctCriterion) {                                        
                $fieldName    = $conjunctCriterion[0];                                                 
                $comparator   = $conjunctCriterion[1];                                                 
                if(isset($conjunctCriterion[2])) {                                                          
                    $compareValue = $conjunctCriterion[2];                                             
                }                                                                                      

                if ($comparator == 'IS NULL') {                                                        
                    if(!(is_null($instance->$fieldName))) {                                            
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == 'IS NOT NULL') {                                              
                    if(is_null($instance->$fieldName)) {                                               
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == '=') {                                                        
                    if (!(isset($instance->$fieldName) && $instance->$fieldName == $compareValue)) {   
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == '!=') {                                                       
                    if (!($instance->$fieldName != $compareValue)) {                                   
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == '>') {                                                        
                    if (!(isset($instance->$fieldName) && ($instance->$fieldName > $compareValue))) {  
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == '<') {                                                        
                    if (!(isset($instance->$fieldName) && ($instance->$fieldName < $compareValue))) {  
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == '<=') {                                                       
                    if (!(isset($instance->$fieldName) && !($instance->$fieldName <= $compareValue))) {
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == '>=') {                                                       
                    if (!(isset($instance->$fieldName) || ($instance->$fieldName >= $compareValue))) { 
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == 'IN') {                                                       
                    if (!(in_array($instance->$fieldName, $compareValue))) {                           
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                } elseif ($comparator == 'LIKE') {                                                     
                    //Replace SQL-Wildcard with fnmatch-wildcard                                     
                    $compareValue = preg_replace('/(?<!\\)%/', '*', $compareValue);                              
                    if (!(fnmatch($compareValue, $instance->$fieldName))) {                            
                        $isValidDisjunct = FALSE;                                                      
                        break;                                                                         
                    }                                                                                  
                }                                                                                      
            }                                                                                          
            if ($isValidDisjunct) {                                                                    
                $resultCollection->add($instance, TRUE);
                break;                                               
            }                                                                                          
        }                                                                                              
    }                                                                                                  
    return $resultCollection;                                                                          
    
risposta data 04.02.2015 - 00:40
fonte
0

È necessario utilizzare un qualche tipo di generatore di query o API Criteri. Potrebbe assomigliare a questo:

Criteria $criteria = new CriteriaBuilder()
    ->where('firstName', '=', $firstName)
    ->and('lastName', '=', $lastName)
    ->build();
$user = $repo->fetchOne($criteria);

Questo è solo un esempio immaginario, ma i framework più usati hanno già questi strumenti:

Non re-implementare l'ORM sottostante, basta usare l'API che fornisce. In caso contrario, scegli un altro che lo faccia.

Metodi come $repo->findById($id) possono anche essere utili se l'API REST contiene risorse come api.example.com/posts/123 , in cui ottieni l'entità usando solo la chiave primaria.

    
risposta data 31.01.2015 - 03:22
fonte

Leggi altre domande sui tag