Dois-je utiliser une couche entre le service et le référentiel pour une architecture propre - Spring

9

Je travaille dans une architecture, elle va proposer une api de repos pour le client web et les applications mobiles. J'utilise Spring (spring mvc, spring data jpa, ... etc). Le modèle de domaine est codé avec la spécification JPA.

J'essaie d'appliquer certains concepts d'architecture propre ( https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html ). Pas tous, car je vais garder le modèle de domaine jpa.

Le flux réel à travers les couches est le suivant:

Front-end <--> Service API -> Service -> Référentiel -> DB

  • Front end : client web, applications mobiles
  • Service API : Rest Contrôleurs, ici j'utilise des convertisseurs et des dto et des services d'appel
  • Service : Interfaces avec les implémentations et elles contiennent la logique métier
  • Référentiel : le référentiel s'interface avec des implémentations automatiques (effectuées par Spring Data JPA) qui contatinent les opérations CRUD et peut-être certaines requêtes SQL

Mon doute: dois-je utiliser une couche supplémentaire entre le service et le référentiel?

Je prévois ce nouveau flux:

Front end <--> Service API -> Service -> Persistance -> Référentiel -> DB

Pourquoi utiliser cette couche de persistance? Comme il est dit dans l'article sur l'architecture propre, j'aimerais avoir une implémentation de service (logique métier ou cas d'utilisation) qui accède à une couche de persistance agnostique. Et aucune modification ne sera nécessaire si je décide d'utiliser un autre modèle "d'accès aux données", par exemple si je décide d'arrêter d'utiliser le référentiel.

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

Je pense donc utiliser une couche de persistance comme celle-ci:

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

et l'implémentation de la couche de persistance comme ceci:

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

J'ai donc seulement besoin de changer les implémentations de la couche de persistance, laissé le service sans changements. Couplé avec le fait que le référentiel est lié au framework.

Qu'est-ce que tu penses? Merci.

Alejandro
la source
3
le référentiel EST la couche d'abstraction. ajouter un autre n'aide pas
Ewan
Oh, mais je devrais utiliser l'interface proposée par Spring, je veux dire, les noms des méthodes de référentiel. Et si je veux changer le référentiel, je devrais garder les noms des appelants, non?
Alejandro
il me semble que le dépôt Spring ne vous oblige pas à exposer des objets Spring. Il suffit de l'utiliser pour implémenter une interface agnostique
Ewan

Réponses:

6

<--> API Service frontal -> Service -> Référentiel -> DB

Droite. Il s'agit de la conception de base par séparation des préoccupations proposée par Spring Framework. Vous êtes donc dans la " bonne voie du printemps ".

Bien que les référentiels soient fréquemment utilisés comme DAO, la vérité est que les développeurs de Spring ont repris la notion de référentiel du DDD d'Eric Evans. Les interfaces de référentiel seront souvent très similaires aux DAO en raison des méthodes CRUD et parce que de nombreux développeurs s'efforcent de rendre les interfaces des référentiels si génériques que, en fin de compte, elles n'ont aucune différence avec EntityManager (le vrai DAO ici) 1 mais le support de requêtes et critères.

Traduit en composants Spring, votre conception est similaire à

@RestController > @Service > @Repository >  EntityManager

Le référentiel est déjà une abstraction entre les services et les magasins de données. Lorsque nous étendons les interfaces du référentiel Spring Data JPA, nous implémentons cette conception implicitement. Lorsque nous faisons cela, nous payons une taxe: un couplage étroit avec les composants de Spring. De plus, nous cassons LoD et YAGNI en héritant de plusieurs méthodes dont nous pourrions ne pas avoir besoin ou souhaiter ne pas avoir. Sans oublier qu'une telle interface ne nous fournit aucune information précieuse sur les besoins de domaine qu'ils servent.

Cela dit, l'extension des référentiels Spring Data JPA n'est pas obligatoire et vous pouvez éviter ces compromis en implémentant une hiérarchie de classes plus simple et personnalisée.

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

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

        //Interface implentation
        //...
    }

Changer la source de données prend maintenant juste une nouvelle implémentation qui remplace EntityManager par une source de données différente .

    //@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
        //...
    }

etc. 2

Revenons à la question de savoir si vous devez ajouter une couche d'abstraction supplémentaire, je dirais non car ce n'est pas nécessaire. Votre exemple ajoute seulement plus de complexité. La couche que vous proposez va finir comme un proxy entre les services et les référentiels ou comme une couche de pseudo-service-référentiel lorsqu'une logique spécifique est nécessaire et que vous ne savez pas où la placer.


1: Contrairement à ce que pensent de nombreux développeurs, les interfaces de référentiel peuvent être totalement différentes les unes des autres car chaque référentiel répond à des besoins de domaine différents. Dans Spring Data JPA, le rôle DAO est joué par EntityManager . Il gère les sessions, l'accès à la DataSource , les mappages , etc.

2: Une solution similaire améliore les interfaces de référentiel de Spring en les mélangeant avec des interfaces personnalisées. Pour plus d'informations, recherchez BaseRepositoryFactoryBean et @NoRepositoryBean . Cependant, j'ai trouvé cette approche lourde et déroutante.

Laiv
la source
3

La meilleure façon de prouver qu'un design est flexible est de le fléchir.

Vous voulez un endroit dans votre code qui soit responsable de la persistance mais n'est pas lié à l'idée d'utiliser un référentiel. Bien. Ça ne fait rien d'utile pour le moment ... soupir, très bien.

OK, testons si cette couche shunt a fait du bien. Créez une couche de fichier plate qui conservera vos produits dans les fichiers. Maintenant, où va cette nouvelle couche dans cette conception?

Eh bien, il devrait pouvoir aller où se trouve DB. Après tout, nous n'avons plus besoin de DB car nous utilisons des fichiers plats. Mais cela était également censé ne pas nécessiter de référentiel.

Considérez, peut-être que le référentiel est un détail d'implémentation. Après tout, je peux parler à la base de données sans utiliser le modèle de référentiel.

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

Si vous pouvez faire fonctionner tout cela sans toucher au service, vous disposez d'un code flexible.

Mais ne me croyez pas sur parole. Écrivez-le et voyez ce qui se passe. Le seul code auquel je fais confiance pour être flexible est le code flexible.

candied_orange
la source