Devo usar uma camada entre serviço e repositório para uma arquitetura limpa - Spring

9

Estou trabalhando em uma arquitetura, ela oferecerá uma API de descanso para aplicativos da Web e aplicativos móveis. Estou usando o Spring (spring mvc, spring data jpa, ... etc). O modelo de domínio é codificado com a especificação JPA.

Estou tentando aplicar alguns conceitos de arquitetura limpa ( https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html ). Nem todos, porque vou manter o modelo de domínio jpa.

O fluxo real através das camadas é o seguinte:

Front end <--> Serviço de API -> Serviço -> Repositório -> DB

  • Front-end : cliente Web, aplicativos móveis
  • Serviço API : Controladores Rest, aqui eu uso conversores e dto e serviços de chamada
  • Serviço : faz interface com implementações e elas contêm lógica de negócios
  • Repositório : O repositório faz interface com implementações automáticas (feitas pelo spring data jpa) que contêm operações CRUD e talvez algumas consultas sql

Minha dúvida: devo usar uma camada extra entre o serviço e o repositório?

Estou planejando esse novo fluxo:

Front end <--> Serviço de API -> Serviço -> Persistência -> Repositório -> DB

Por que usar essa camada de persistência? Como diz o artigo de arquitetura limpa, gostaria de ter uma implementação de serviço (lógica de negócios ou caso de uso) que acesse uma camada de persistência agnóstica. E não serão necessárias alterações se eu decidir usar outro padrão de "acesso a dados", por exemplo, se eu decidir parar de usar o repositório.

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

Então, eu estou pensando em usar uma camada de persistência como esta:

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

e implementação da camada de persistência assim:

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

Então, eu só preciso alterar as implementações da camada de persistência, deixar o serviço sem alterações. Associado ao fato de que o Repositório está relacionado ao framework.

O que você acha? Obrigado.

Alejandro
fonte
3
o repositório é a camada de abstração. adicionando outro não ajuda
Ewan
Ah, mas eu teria que usar a interface proposta pelo Spring, quero dizer, nomes de métodos de repositório. E se eu quiser mudar o repositório, teria que manter os nomes das chamadas, não?
Alejandro
parece-me que o repositório do spring não o força a expor objetos do spring. Basta usá-lo para implementar uma interface agnóstico
Ewan

Respostas:

6

Front end <--> Serviço de API -> Serviço -> Repositório -> DB

Direita. Esse é o design básico por segregação de preocupações proposto pelo Spring Framework. Então você está no " caminho certo da primavera ".

Apesar de os Repositórios serem frequentemente usados ​​como DAOs, a verdade é que os desenvolvedores do Spring adotaram a noção de Repositório do DDD de Eric Evans. As interfaces de repositório geralmente se parecem muito com os DAOs devido aos métodos CRUD e porque muitos desenvolvedores se esforçam para tornar as interfaces dos repositórios tão genéricas que, no final, elas não têm diferença com o EntityManager (o verdadeiro DAO aqui) 1, mas com o suporte do consultas e critérios.

Traduzido para componentes Spring, seu design é semelhante ao

@RestController > @Service > @Repository >  EntityManager

O Repositório já é uma abstração entre serviços e repositórios de dados. Quando estendemos as interfaces do repositório Spring Data JPA, estamos implementando esse design implicitamente. Quando fazemos isso, estamos pagando um imposto: um forte acoplamento com os componentes da Spring. Além disso, quebramos o LoD e o YAGNI herdando vários métodos que talvez não sejam necessários ou que não desejamos ter. Sem mencionar que essa interface não nos fornece informações valiosas sobre as necessidades de domínio que elas atendem.

Dito isso, a extensão dos repositórios JPA do Spring Data não é obrigatória e você pode evitar essas trocas implementando uma hierarquia de classes mais simples e customizada.

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

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

        //Interface implentation
        //...
    }

A alteração da fonte de dados agora requer apenas uma nova implementação que substitui o EntityManager por uma fonte de dados 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
        //...
    }

e assim por diante. 2

Voltando à pergunta, se você deve adicionar mais uma camada de abstração, eu diria que não, porque não é necessário. Seu exemplo é apenas adicionar mais complexidade. A camada que você propõe acabará como um proxy entre serviços e repositórios ou como uma camada de pseudo-serviço-repositório quando lógica específica for necessária e você não especificar onde colocá-la.


1: Ao contrário do que muitos desenvolvedores pensam, as interfaces do repositório podem ser totalmente diferentes uma da outra porque cada repositório atende a diferentes necessidades de domínio. No Spring Data JPA, o papel DAO é desempenhado pelo EntityManager . Ele gerencia as sessões, o acesso ao DataSource , mapeamentos etc.

2: Uma solução semelhante está aprimorando as interfaces de repositório do Spring, misturando-as com interfaces personalizadas. Para obter mais informações, procure BaseRepositoryFactoryBean e @NoRepositoryBean . No entanto, eu achei essa abordagem complicada e confusa.

Laiv
fonte
3

A melhor maneira de provar que um design é flexível é flexioná-lo.

Você deseja um lugar no seu código que seja responsável pela persistência, mas não esteja vinculado à idéia de usar um repositório. Bem. Não está fazendo nada de útil no momento ... suspiro, tudo bem.

OK, vamos testar se essa camada de derivação fez algum bem. Crie uma camada de arquivo simples que persistirá em seus produtos. Agora, para onde essa nova camada vai nesse design?

Bem, ele deve poder ir para onde o DB está. Afinal, não precisamos mais do DB, pois estamos usando arquivos simples. Mas isso também deveria não precisar de repositório.

Considere, talvez o repositório seja um detalhe de implementação. Afinal, posso falar com o banco de dados sem usar o padrão de repositório.

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 você pode fazer tudo isso funcionar sem tocar em Serviço, possui um código flexível.

Mas não aceite minha palavra. Escreva e veja o que acontece. O único código que eu confio para ser flexível é o código flexível.

candied_orange
fonte