¿Debo usar una capa entre el servicio y el repositorio para una arquitectura limpia? - Spring

9

Estoy trabajando en una arquitectura, va a ofrecer una API de descanso para clientes web y aplicaciones móviles. Estoy usando Spring (spring mvc, spring data jpa, ... etc.). El modelo de dominio está codificado con la especificación JPA.

Estoy tratando de aplicar algunos conceptos de arquitectura limpia ( https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html ). No todo, porque voy a mantener el modelo de dominio jpa.

El flujo real a través de las capas es este:

Front end <--> Servicio API -> Servicio -> Repositorio -> DB

  • Front end : cliente web, aplicaciones móviles
  • Servicio API : Rest Controllers, aquí uso convertidores y dto's y servicios de llamadas
  • Servicio : Interfaces con implementaciones y contienen lógica de negocios.
  • Repositorio : interfaces de repositorio con implementaciones automáticas (realizadas por spring data jpa) que contienen operaciones CRUD y quizás algunas consultas sql

Mi duda: ¿Debo usar una capa adicional entre el servicio y el repositorio?

Estoy planeando este nuevo flujo:

Front end <--> Servicio API -> Servicio -> Persistencia -> Repositorio -> DB

¿Por qué usar esta capa de persistencia? Como dice en el artículo de arquitectura limpia, me gustaría tener una implementación de servicio (lógica de negocios o caso de uso) que acceda a una capa de persistencia agnóstica. Y no se necesitarán cambios si decido usar otro patrón de "acceso a datos", por ejemplo, si decido dejar de usar el repositorio.

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

Así que estoy pensando en usar una capa de persistencia como esta:

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

e implementación de la capa de persistencia como esta:

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

Por lo tanto, solo necesito cambiar las implementaciones de la capa de persistencia, dejé el servicio sin cambios. Junto con el hecho de que el repositorio está relacionado con el marco.

¿Qué piensas? Gracias.

Alejandro
fuente
3
El repositorio es la capa de abstracción. agregar otro no ayuda
Ewan
Ah, pero tendría que usar la interfaz propuesta por Spring, quiero decir, nombres de métodos de repositorio. Y si quiero cambiar el repositorio tendría que mantener los nombres de las personas que llaman, ¿no?
Alejandro
me parece que el depósito de primavera no te obliga a exponer los objetos de primavera. Solo úselo para implementar una interfaz agnóstica
Ewan

Respuestas:

6

Front end <--> Servicio API -> Servicio -> Repositorio -> DB

Derecha. Este es el diseño básico por segregación de preocupaciones propuesto por Spring Framework. Entonces estás en el " camino correcto de la primavera ".

A pesar de que los repositorios se usan con frecuencia como DAO, la verdad es que los desarrolladores de Spring tomaron la noción de repositorio del DDD de Eric Evans. Las interfaces de repositorio a menudo se verán muy similares a las DAO debido a los métodos CRUD y porque muchos desarrolladores se esfuerzan por hacer que las interfaces de los repositorios sean tan genéricas que, al final, no tienen ninguna diferencia con el EntityManager (el verdadero DAO aquí) 1 sino el soporte de consultas y criterios.

Traducido a componentes Spring, su diseño es similar a

@RestController > @Service > @Repository >  EntityManager

El repositorio ya es una abstracción entre servicios y almacenes de datos. Cuando ampliamos las interfaces del repositorio Spring Data JPA, estamos implementando este diseño implícitamente. Cuando hacemos esto, estamos pagando un impuesto: un acoplamiento estrecho con los componentes de Spring. Además, rompemos LoD y YAGNI al heredar varios métodos que podríamos no necesitar o desear no tener. Sin mencionar que dicha interfaz no nos proporciona ninguna información valiosa sobre las necesidades de dominio a las que sirven.

Dicho esto, extender los repositorios JPA de Spring Data no es obligatorio y puede evitar estas compensaciones implementando una jerarquía de clases más simple y personalizada.

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

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

        //Interface implentation
        //...
    }

Cambiar la fuente de datos ahora solo requiere una nueva implementación que reemplaza el EntityManager con una fuente de datos diferente .

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

y así. 2

Volviendo a la pregunta, si debería agregar una capa de abstracción más, diría que no porque no es necesario. Su ejemplo solo agrega más complejidad. La capa que propone terminará como un proxy entre servicios y repositorios o como una capa de pseudo-servicio-repositorio cuando se necesita una lógica específica y no sabe dónde colocarla.


1: A diferencia de lo que piensan muchos desarrolladores, las interfaces de repositorio pueden ser totalmente diferentes entre sí porque cada repositorio sirve para diferentes necesidades de dominio. En Spring Data JPA, el rol DAO es desempeñado por el EntityManager . Gestiona las sesiones, el acceso a DataSource , mapeos , etc.

2: Una solución similar es mejorar las interfaces del repositorio de Spring mezclándolas con interfaces personalizadas. Para obtener más información, busque BaseRepositoryFactoryBean y @NoRepositoryBean . Sin embargo, este enfoque me ha parecido engorroso y confuso.

Laiv
fuente
3

La mejor manera de demostrar que un diseño es flexible es flexionarlo.

Desea un lugar en su código que sea responsable de la persistencia pero que no esté vinculado a la idea de usar un repositorio. Multa. No está haciendo nada útil en este momento ... suspiro, bien.

Bien, vamos a probar si esta capa de derivación funcionó. Cree una capa de archivo plano que persista sus productos en archivos. Ahora, ¿a dónde va esta nueva capa en este diseño?

Bueno, debería poder ir a donde está DB. Después de todo, ya no necesitamos DB ya que estamos usando archivos planos. Pero también se suponía que esto no necesitaba repositorio.

Considere, quizás el repositorio es un detalle de implementación. Después de todo, puedo hablar con el DB sin usar el patrón de repositorio.

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 puede hacer que todo funcione sin tocar el Servicio, tiene un código flexible.

Pero no confíes en mi palabra. Escríbelo y mira qué pasa. El único código en el que confío para ser flexible es el código flexionado.

naranja confitada
fuente