Right. This's the basic design by seggregationsegregation of concerns proposed by Spring Framework. So you are in the "Spring's right way".
Despite Repositories are frequently used as DAOs, the truth is that Spring developers took the notion of Repository from Eric Evans' DDD. Repository interfaces will lookslook often very similar to DAOs because of the CRUD methods and because many developers strive to make repositories'srepositories' interfaces so generics that, in the end, they have no difference with the EntityManager (the true DAO here).
Repositories are an abstraction addressed to keep1 but the domain unaweresupport of the persistence implementationqueries and technical details. Their solely prupose is serving the domain as it need it. Unlike many developers think, repository interfaces can be totally different from each other.
In Spring Data JPA, the role DAO is played by the EntityManager. It manages the sessions, the access to the DataSource, mappings, etccriterias.
The Repository is already an abstraction in between services and datastoresdata stores. When we inherit fromextend Spring Data JPA repository interfaces, we are implementing this design implicitly. When we do this, we are paying a tax: a tight coupling with Spring's components. Additionally, we break LoD and YAGNI by inheriting several methods we might not need or wish not have. Not to mention that such an interface doesn't provide us with any valuable insight about the domain needs they serve.
IfThat said, extending Spring Data repository interfacesJPA repositories is too much coupling for you or you find that you don't need most of the methods inherited,not mandatory and you can make your ownavoid these tradeoffs by implementing a more plain and custom hierarchy of classes.
@Repository
public class MyRepositoryImpl implements MyRepository{
private EntityManager em;
@Autowire
public MyRepository (@Autowire EntityManager em){
this.em = em;
}
//Interface implentation
//...
}
Changing the datasourcedata source now just takes a new implementation which replacereplaces the EntityManager with a different datasourcedata source.
//@RestController > @Service > @Repository > RestTemplate
@Repository
public class MyRepositoryImpl implements MyRepository{
private RestTemplate rt;
@Autowire
public MyRepository (@Autowire 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 (@Autowire MyWebServiceClient wsClient){
this.wsClient = wsClient;
}
//Interface implentation
//...
}
and so on.2
Back to the question, whether you should add one more abstraction layer, I would say no. It's because it's not necessary. Your example, IMO, is only adding more complexity. The layer you propose is going to end up as a proxy between services and repositories or as a pseudo-service-repository layer when specific logic is needed and you don't where to place it.
Finally, if you find Spring's repository interfaces not to be enough, if you need enhance Spring Data interfaces with new methods, Spring allows you to do so. Search a little bit about the BaseRepositoryFactoryBean implentation and @NoRepositoryBean annotation.1: Unlike many developers think, repository interfaces can be totally different from each other because each repository serves different domain needs. In Spring Data JPA, the role DAO is played by the EntityManager. It manages the sessions, the access to the DataSource, mappings, etc.
2: A similar solution is enhancing Spring's repository interfaces mixing them up with custom interfaces. For more info, look for BaseRepositoryFactoryBean and @NoRepositoryBean. However, I have found this approach cumbersome and confusing.