Czy powinienem używać warstwy między usługą a repozytorium dla czystej architektury - Spring

9

Pracuję w architekturze, będzie oferować odpoczynek API dla klienta WWW i aplikacji mobilnych. Używam Spring (spring mvc, spring data jpa, ... itd.). Model domeny jest kodowany zgodnie ze specyfikacją JPA.

Próbuję zastosować pewne koncepcje czystej architektury ( https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html ). Nie wszystkie, ponieważ zamierzam zachować model domeny jpa.

Rzeczywisty przepływ przez warstwy jest następujący:

Zaczepy <-> API Usługi -> Usługi -> Repository -> DB

  • Frontend : klient WWW, aplikacje mobilne
  • Usługa API : kontrolery odpoczynku, tutaj korzystam z konwerterów oraz usług DTO i połączeń
  • Usługa : interfejsy z implementacjami i zawierają logikę biznesową
  • Repozytorium : Interfejsy repozytorium z automatycznymi implementacjami (wykonanymi przez Spring Data JPA), które mają wpływ na operacje CRUD i być może niektóre zapytania SQL

Moje wątpliwości: czy powinienem użyć dodatkowej warstwy między usługą a repozytorium?

Planuję ten nowy przepływ:

Zaczepy <-> API Usługi -> Usługi -> Trwałość -> Repository -> DB

Dlaczego warto korzystać z tej warstwy trwałości? Jak napisano w artykule o czystej architekturze, chciałbym mieć implementację usługi (logikę biznesową lub przypadek użycia), która ma dostęp do warstwy trwałości agnostycznej. I żadne zmiany nie będą potrzebne, jeśli zdecyduję się użyć innego wzorca „dostępu do danych”, na przykład jeśli zdecyduję się przestać używać repozytorium.

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

Więc myślę, że użyj takiej warstwy trwałości:

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

i implementacja warstwy trwałości takiej jak ta:

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

Muszę więc tylko zmienić implementacje warstwy trwałości, opuściłem usługę bez zmian. W połączeniu z tym, że repozytorium jest powiązane z frameworkiem.

Co myślisz? Dzięki.

Alejandro
źródło
3
repozytorium JEST warstwą abstrakcji. dodanie kolejnego nie pomaga
Ewan
Och, ale musiałbym użyć interfejsu zaproponowanego przez Spring, to znaczy nazw metod repozytorium. A jeśli chcę zmienić repozytorium, musiałbym zachować nazwy wywołujące, nie?
Alejandro,
wygląda mi na to, że repozytorium wiosenne nie zmusza cię do odsłonięcia obiektów wiosennych. Wystarczy użyć go do zaimplementowania interfejsu agnostycznego
Ewan

Odpowiedzi:

6

Interfejs <--> Usługa interfejsu API -> Usługa -> Repozytorium -> DB

Dobrze. Jest to podstawowy projekt oparty na segregacji problemów zaproponowanych przez Spring Framework. Jesteś więc na „ właściwej drodze wiosny ”.

Mimo że repozytoria są często używane jako DAO, prawda jest taka, że ​​deweloperzy Spring przyjęli pojęcie repozytorium z DDD Erica Evansa. Interfejsy repozytoriów będą często wyglądać bardzo podobnie do DAO ze względu na metody CRUD i ponieważ wielu programistów stara się, aby interfejsy repozytoriów były tak ogólne, że ostatecznie nie mają one różnicy z EntityManager (tutaj prawdziwe DAO) 1, ale wsparcie dla zapytania i kryteria.

Przełożony na komponenty Spring, twój projekt jest podobny do

@RestController > @Service > @Repository >  EntityManager

Repozytorium jest już abstrakcją pomiędzy usługami i magazynami danych. Kiedy rozszerzamy interfejsy repozytorium JPA Spring Data, implementujemy ten projekt niejawnie. Kiedy to robimy, płacimy podatek: ścisłe połączenie z komponentami Spring. Dodatkowo łamiemy LoD i YAGNI, dziedzicząc kilka metod, których możemy nie potrzebować lub których nie chcieliśmy mieć. Nie wspominając o tym, że taki interfejs nie zapewnia nam żadnego cennego wglądu w potrzeby domen, które obsługują.

To powiedziawszy, rozszerzenie repozytoriów JPA Spring Data nie jest obowiązkowe i można uniknąć tych kompromisów, wdrażając bardziej prostą i niestandardową hierarchię klas.

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

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

        //Interface implentation
        //...
    }

Zmiana źródła danych wymaga teraz nowej implementacji, która zastępuje EntityManager innym źródłem danych .

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

i tak dalej. 2)

Wracając do pytania, czy należy dodać jeszcze jedną warstwę abstrakcji, powiedziałbym „nie”, ponieważ nie jest to konieczne. Twój przykład tylko zwiększa złożoność. Proponowana warstwa stanie się pośrednikiem między usługami i repozytoriami lub warstwą pseudo-usług-repozytorium , gdy potrzebna będzie konkretna logika, a Ty nie będziesz jej umieszczać.


1: W przeciwieństwie do wielu programistów, interfejsy repozytoriów mogą się całkowicie różnić od siebie, ponieważ każde repozytorium spełnia różne potrzeby domeny. W JPA Spring Data rolę DAO odgrywa EntityManager . Zarządza sesjami, dostępem do źródła danych , mapowań itp.

2: Podobne rozwiązanie polega na ulepszeniu interfejsów repozytorium Springa, łącząc je z niestandardowymi interfejsami. Aby uzyskać więcej informacji, poszukaj BaseRepositoryFactoryBean i @NoRepositoryBean . Jednak uważam to podejście za kłopotliwe i mylące.

Laiv
źródło
3

Najlepszym sposobem udowodnienia elastyczności projektu jest jego wygięcie.

Chcesz miejsca w kodzie, które odpowiada za trwałość, ale nie jest związane z pomysłem użycia repozytorium. W porządku. W tej chwili nie robi nic przydatnego ... westchnienie, w porządku.

OK, sprawdźmy, czy ta warstwa bocznikowa działała dobrze. Utwórz płaską warstwę plików, która utrwali Twoje produkty w plikach. Gdzie teraz idzie ta nowa warstwa w tym projekcie?

Cóż, powinno być w stanie iść tam, gdzie jest DB. W końcu nie potrzebujemy już DB, ponieważ używamy płaskich plików. Ale miało to również nie wymagać repozytorium.

Zastanów się, może repozytorium jest szczegółem implementacji. W końcu mogę rozmawiać z DB bez użycia wzorca repozytorium.

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

Jeśli możesz sprawić, aby wszystko działało bez dotykania usługi, masz elastyczny kod.

Ale nie wierz mi na słowo. Napisz i zobacz, co się stanie. Jedynym kodem, któremu ufam, że jest elastyczny, jest kod elastyczny.

candied_orange
źródło