Dovrei usare un livello tra servizio e repository per un'architettura pulita - Primavera


9

Sto lavorando in un'architettura, offrirà api di riposo per client Web e app mobili. Sto usando Spring (spring mvc, spring data jpa, ... ecc.). Il modello di dominio è codificato con le specifiche JPA.

Sto cercando di applicare alcuni concetti di architettura pulita ( https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html ). Non tutti, perché manterrò il modello di dominio jpa.

Il flusso effettivo attraverso i livelli è questo:

Front-end <--> Servizio API -> Servizio -> Repository -> DB

  • Front-end : client Web, app mobili
  • Servizio API : Rest Controller, qui utilizzo convertitori, dto e servizi di chiamata
  • Servizio : si interfaccia con le implementazioni e contengono la logica aziendale
  • Repository : il repository si interfaccia con le implementazioni automatiche (eseguite da jap di dati di primavera) che contagiano le operazioni CRUD e forse alcune query sql

Il mio dubbio: dovrei usare un ulteriore livello tra servizio e repository?

Sto pianificando questo nuovo flusso:

Front-end <--> Servizio API -> Servizio -> Persistenza -> Repository -> DB

Perché usare questo livello di persistenza? Come indicato nell'articolo sull'architettura pulita, vorrei avere un'implementazione del servizio (logica aziendale o caso d'uso) che acceda a un livello di persistenza agnostica. E non saranno necessarie modifiche se decido di utilizzare un altro modello di "accesso ai dati", ad esempio se decido di smettere di usare il repository.

class ProductServiceImpl implements ProductService {
    ProductRepository productRepository;
    void save(Product product) {
        // do business logic
        productRepository.save(product)
    }
}

Quindi sto pensando di usare un livello di persistenza come questo:

class ProductServiceImpl implements ProductService {
    ProductPersistence productPersistence;
    void save(Product product) {
        // do business logic
        productPersistence.save(product)
    }
}

e implementazione del livello di persistenza in questo modo:

class ProductPersistenceImpl implements ProductPersistence {
    ProductRepository productRepository;
    void save(Product product) {
        productRepository.save(product)
    }
}

Quindi ho solo bisogno di cambiare le implementazioni del livello di persistenza, ho lasciato il servizio senza cambiamenti. Insieme al fatto che il repository è correlato al framework.

Cosa ne pensi? Grazie.


3
il repository È lo strato di astrazione. l'aggiunta di un altro non aiuta
Ewan

Oh, ma dovrei usare l'interfaccia proposta da Spring, intendo, i nomi dei metodi di repository. E se voglio cambiare il repository dovrei conservare i nomi delle chiamate, No?
Alejandro,

mi sembra che il repository spring non ti costringa a esporre oggetti spring. Usalo solo per implementare un'interfaccia agnostica
Ewan,

Risposte:


6

Front-end <--> Servizio API -> Servizio -> Repository -> DB

Giusto. Questo è il progetto di base per segregazione delle preoccupazioni proposto da Spring Framework. Quindi sei nel " modo giusto di primavera ".

Nonostante i repository siano spesso usati come DAO, la verità è che gli sviluppatori di Spring hanno preso l'idea di repository dal DDD di Eric Evans. Le interfacce dei repository appariranno spesso molto simili ai DAO a causa dei metodi CRUD e perché molti sviluppatori si sforzano di rendere le interfacce dei repository così generiche che, alla fine, non hanno alcuna differenza con l' EntityManager (il vero DAO qui) 1 ma il supporto di query e criteri.

Tradotto in componenti Spring, il tuo design è simile a

@RestController > @Service > @Repository >  EntityManager

Il repository è già un'astrazione tra servizi e archivi dati. Quando estendiamo le interfacce del repository JPA Spring Data, stiamo implementando implicitamente questo progetto. Quando lo facciamo, stiamo pagando una tassa: un accoppiamento stretto con i componenti di Spring. Inoltre, rompiamo LoD e YAGNI ereditando diversi metodi che potremmo non avere bisogno o desideriamo non avere. Per non parlare del fatto che una tale interfaccia non ci fornisce informazioni preziose sulle esigenze del dominio che servono.

Detto questo, l'estensione dei repository JPA di Spring Data non è obbligatoria e puoi evitare questi compromessi implementando una gerarchia di classi più semplice e personalizzata.

    @Repository
    public class MyRepositoryImpl implements MyRepository{
        private EntityManager em;

        @Autowire
        public MyRepository (EntityManager em){    
             this.em = em;
        }

        //Interface implentation
        //...
    }

La modifica dell'origine dati ora richiede solo una nuova implementazione che sostituisce EntityManager con un'origine dati diversa .

    //@RestController > @Service > @Repository >  RestTemplate

    @Repository
    public class MyRepositoryImpl implements MyRepository{
        private RestTemplate rt;

        @Autowire 
        public MyRepository (RestTemplate rt){    
             this.rt = rt;
        }

        //Interface implentation
        //...
    }
    //@RestController > @Service > @Repository >  File

    @Repository
    public class MyRepositoryImpl implements MyRepository{

        private File file; 
        public MyRepository (File file){    
            this.file = file;
        }

        //Interface implentation
        //...
    }
    //@RestController > @Service > @Repository >  SoapWSClient

    @Repository
    public class MyRepositoryImpl implements MyRepository{

        private MyWebServiceClient wsClient; 

        @Autowire
        public MyRepository (MyWebServiceClient  wsClient){    
               this.wsClient = wsClient;
        }

        //Interface implentation
        //...
    }

e così via. 2

Tornando alla domanda, se dovresti aggiungere un altro livello di astrazione, direi di no perché non è necessario. Il tuo esempio sta solo aggiungendo più complessità. Il livello che proponi finirà come proxy tra servizi e repository o come livello di pseudo-servizio-repository quando è necessaria una logica specifica e non sei in grado di posizionarlo.


1: A differenza di molti sviluppatori, le interfacce del repository possono essere totalmente diverse l'una dall'altra perché ogni repository soddisfa esigenze di dominio diverse. In Spring Data JPA, il ruolo DAO è svolto da EntityManager . Gestisce le sessioni, l'accesso all'origine dati , i mapping , ecc.

2: Una soluzione simile sta migliorando le interfacce del repository di Spring mescolandole con interfacce personalizzate. Per maggiori informazioni, cerca BaseRepositoryFactoryBean e @NoRepositoryBean . Tuttavia, ho trovato questo approccio ingombrante e confuso.


3

Il modo migliore per dimostrare che un design è flessibile è fletterlo.

Vuoi un posto nel tuo codice che è responsabile della persistenza ma non è legato all'idea di usare un repository. Belle. Al momento non sta facendo nulla di utile ... sospiro, va bene.

OK, testiamo se questo shunt layer ha funzionato bene. Crea un livello file piatto che consenta di conservare i tuoi prodotti in file. Ora dove va questo nuovo strato in questo disegno?

Bene, dovrebbe essere in grado di andare dove si trova DB. Dopo tutto non abbiamo più bisogno di DB poiché stiamo usando file flat. Ma anche questo non avrebbe avuto bisogno di un repository.

Considera, forse il repository è un dettaglio di implementazione. Dopo tutto, posso parlare con il DB senza usare il modello di repository.

Front end <--> API Service -> Service -> Repository -> DB

Front end <--> API Service -> Service -> Repository -> Files

Front end <--> API Service -> Service -> Persistence -> DB

Front end <--> API Service -> Service -> Persistence -> Files

Se riesci a far funzionare tutto senza toccare il servizio, hai un codice flessibile.

Ma non crederci sulla parola. Scrivilo e vedi cosa succede. L'unico codice che mi fido di essere flessibile è il codice flessibile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.