Sollte ich eine Schicht zwischen Service und Repository für eine saubere Architektur verwenden - Spring

9

Ich arbeite in einer Architektur, die eine Rest-API für Web-Clients und mobile Apps bietet. Ich benutze Spring (spring mvc, spring data jpa, ... etc). Das Domänenmodell ist mit der JPA-Spezifikation codiert.

Ich versuche, einige Konzepte einer sauberen Architektur anzuwenden ( https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html ). Nicht alle, weil ich das jpa-Domain-Modell behalten werde.

Der tatsächliche Fluss durch die Schichten ist folgender:

Frontend <-> API-Service -> Service -> Repository -> DB

  • Frontend : Webclient, mobile Apps
  • API Service : Rest Controller, hier verwende ich Konverter und Dto's und Call Services
  • Service : Schnittstellen zu Implementierungen, die Geschäftslogik enthalten
  • Repository : Das Repository ist mit automatischen Implementierungen (von spring data jpa) verbunden, die CRUD-Operationen und möglicherweise einige SQL-Abfragen enthalten

Mein Zweifel: Soll ich eine zusätzliche Schicht zwischen Service und Repository verwenden?

Ich plane diesen neuen Ablauf:

Frontend <-> API-Service -> Service -> Persistenz -> Repository -> DB

Warum diese Persistenzschicht verwenden? Wie im Artikel über saubere Architektur angegeben, möchte ich eine Service-Implementierung (Geschäftslogik oder Anwendungsfall) haben, die auf eine agnostische Persistenzschicht zugreift. Und es sind keine Änderungen erforderlich, wenn ich mich für die Verwendung eines anderen "Datenzugriffs" -Musters entscheide, beispielsweise wenn ich mich entscheide, das Repository nicht mehr zu verwenden.

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

Ich denke also, verwenden Sie eine Persistenzschicht wie diese:

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

und Implementierung der Persistenzschicht wie folgt:

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

Ich muss also nur die Implementierungen der Persistenzschicht ändern und den Dienst ohne Änderungen belassen. Verbunden mit der Tatsache, dass das Repository mit dem Framework verknüpft ist.

Was denkst du? Vielen Dank.

Alejandro
quelle
3
Das Repository ist die Abstraktionsschicht. Das Hinzufügen eines weiteren hilft nicht
Ewan
Oh, aber ich müsste die von Spring vorgeschlagene Schnittstelle verwenden, ich meine, Repository-Methodennamen. Und wenn ich das Repository ändern möchte, müsste ich die aufrufenden Namen behalten, Nein?
Alejandro
Es sieht für mich so aus, als ob das Feder-Repository Sie nicht zwingt, Federobjekte freizulegen. Verwenden Sie es einfach, um eine agnostische Schnittstelle zu implementieren
Ewan

Antworten:

6

Frontend <-> API-Service -> Service -> Repository -> DB

Richtig. Dies ist das grundlegende Design durch Trennung der vom Spring Framework vorgeschlagenen Bedenken. Sie sind also auf dem " richtigen Weg des Frühlings ".

Obwohl Repositories häufig als DAOs verwendet werden, ist die Wahrheit, dass Spring-Entwickler den Begriff Repository aus Eric Evans 'DDD übernommen haben. Repository-Schnittstellen sehen DAOs aufgrund der CRUD-Methoden oft sehr ähnlich und weil viele Entwickler bestrebt sind, die Schnittstellen von Repositorys so allgemein zu gestalten, dass sie letztendlich keinen Unterschied zum EntityManager (hier das wahre DAO) 1 haben, sondern die Unterstützung von Abfragen und Kriterien.

In Federkomponenten übersetzt, ähnelt Ihr Design

@RestController > @Service > @Repository >  EntityManager

Das Repository ist bereits eine Abstraktion zwischen Diensten und Datenspeichern. Wenn wir Spring Data JPA-Repository-Schnittstellen erweitern, implementieren wir dieses Design implizit. Wenn wir dies tun, zahlen wir eine Steuer: eine enge Verbindung mit den Komponenten von Spring. Zusätzlich brechen wir LoD und YAGNI, indem wir mehrere Methoden erben, die wir möglicherweise nicht benötigen oder nicht haben möchten. Ganz zu schweigen davon, dass eine solche Schnittstelle uns keine wertvollen Einblicke in die Domain-Anforderungen bietet, denen sie dienen.

Das Erweitern von Spring Data JPA-Repositorys ist jedoch nicht obligatorisch, und Sie können diese Kompromisse vermeiden, indem Sie eine einfachere und benutzerdefiniertere Hierarchie von Klassen implementieren.

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

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

        //Interface implentation
        //...
    }

Das Ändern der Datenquelle erfordert jetzt nur eine neue Implementierung, die den EntityManager durch eine andere Datenquelle ersetzt .

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

und so weiter. 2

Zurück zur Frage, ob Sie noch eine Abstraktionsschicht hinzufügen sollten, würde ich nein sagen, weil es nicht notwendig ist. Ihr Beispiel erhöht nur die Komplexität. Die von Ihnen vorgeschlagene Schicht wird als Proxy zwischen Diensten und Repositorys oder als Pseudo-Dienst-Repository- Schicht enden, wenn eine bestimmte Logik benötigt wird und Sie nicht wissen, wo Sie sie platzieren sollen.


1: Anders als viele Entwickler denken, können sich die Repository-Schnittstellen völlig voneinander unterscheiden, da jedes Repository unterschiedliche Domänenanforderungen erfüllt. In Spring Data JPA spielt der EntityManager die Rolle des DAO . Es verwaltet die Sitzungen, den Zugriff auf die DataSource , Zuordnungen usw.

2: Eine ähnliche Lösung besteht darin, die Repository-Schnittstellen von Spring zu verbessern und sie mit benutzerdefinierten Schnittstellen zu verwechseln. Weitere Informationen finden Sie unter BaseRepositoryFactoryBean und @NoRepositoryBean . Ich habe diesen Ansatz jedoch als umständlich und verwirrend empfunden.

Laiv
quelle
3

Der beste Weg, um zu beweisen, dass ein Design flexibel ist, besteht darin, es zu biegen.

Sie möchten einen Platz in Ihrem Code, der für die Persistenz verantwortlich ist, aber nicht an die Idee gebunden ist, ein Repository zu verwenden. Fein. Es macht im Moment nichts Nützliches ... seufz, gut.

OK, lassen Sie uns testen, ob diese Shunt-Schicht etwas Gutes getan hat. Erstellen Sie eine flache Dateiebene, die Ihre Produkte in Dateien beibehält. Wohin geht diese neue Ebene in diesem Design?

Nun, es sollte in der Lage sein, dorthin zu gehen, wo DB ist. Schließlich brauchen wir keine DB mehr, da wir Flatfiles verwenden. Dies sollte aber auch kein Repository benötigen.

Bedenken Sie, dass das Repository möglicherweise ein Implementierungsdetail ist. Immerhin kann ich mit der DB sprechen, ohne das Repository-Muster zu verwenden.

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

Wenn Sie all das zum Laufen bringen können, ohne Service zu berühren, haben Sie flexiblen Code.

Aber nimm mein Wort nicht dafür. Schreiben Sie es und sehen Sie, was passiert. Der einzige Code, dem ich vertraue, um flexibel zu sein, ist Flexed Code.

candied_orange
quelle