7

I have quite some JpaRepository extended Repository interfaces due to the design of the database.

In order to construct a simple object i.e Person I have to make method calls to about 4 - 5 repositories just because the data is spread like that throughout the database. Something like this (pardon for pseudocode):

@Service
public class PersonConstructService {

    public PersonConstructService(Repository repository,
                                  RepositoryTwo repositoryTwo,
                                  RepositoryThree repositoryThree) {

        public Person constructPerson() {
            person
                .add(GetDataFromRepositoryOne())
                .add(GetDataFromRepositoryTwo())
                .add(GetDataFromRepositoryThree());

            return person;
        }

        private SomeDataTypeReturnedOne GetDataFromRepositoryOne() {
            repository.doSomething();
        }

        private SomeDataTypeReturnedTwo GetDataFromRepositoryTwo() {
            repositoryTwo.doSomething();
        }

        private SomeDataTypeReturnedThree GetDataFromRepositoryThree() {
            repositoryThree.doSomething();
        }
    }
}

PersonConstructService class uses all these interfaces just to construct a simple Person object. I am calling these repositories from different methods inside the PersonConstructService class. I have thought about spreading this class into multiple classes, but I do not think this is correct.

Instead I would like to use a repositoryService which would include all the repositories listed necessary for creation of a Person object. Is that a good approach? Is it possible in Spring?

The reason I am asking is that sometimes the count of injected Services into a class is about 7-8. This is definitely not good.

jannis
  • 4,843
  • 1
  • 23
  • 53
Deniss M.
  • 3,617
  • 17
  • 52
  • 100
  • With Spring you can at first use `@Autowired` annotation to get your services ready-for-use in your class, but you shouldn't directly call repositories method from your class `PersonConstructService`. It's not a problem, if you need 7 services in your class, why is it bad to call these 7 services ? And I guess if your POJO are bound together with relationships like (`@ManyToOne`, `@OneToMany`, ...) you can just construct simply your object without manually adding others. – Alex Nov 24 '16 at 11:04
  • @Alex Well if you have that many services in your class constructor that as I understand is a code smell according to Spring Docs: `As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.` Are you suggesting field injection by this: `With Spring you can at first use @Autowired annotation to get your services ready-for-use in your class`? – Deniss M. Nov 24 '16 at 11:14
  • Yes totally. Constructor arguments are a thing, but inject your services is another way to properly do the stuff you want with it. Java EE already implemented this in v6 with `@Inject`, Spring official keyword is `@Autowired`, and I personnally have in some of my classes 6 or 7 of them sometimes. I never witnessed particular issues – Alex Nov 24 '16 at 11:21
  • @Alex It will work, but you cannot be sure that your services are not null. With constructor injection these things are checked during compile time. Whilst with fields you cannot be sure. – Deniss M. Nov 24 '16 at 11:24
  • if your services are annotated with `@Service` and your Spring Context is loaded, there is no way your `@Autowired` fields are null. – Alex Nov 24 '16 at 11:30
  • 1
    Appart from the constructor vs. setter argument, I actually do think your sample code is good design. You have repositories mapping your Database tables to Object concepts. You use a service layer on top of it to construct more abstract domain objects. This looks good to me The fact that your "Person" abstraction needs many many repositories to be constructed is another question (is your DB modelisation goog enough ?). But given what it is, aggregating DAOs/Repositories at the service layer to build high level business model abstractions actually is a good practice in my view. – GPI Nov 24 '16 at 11:57
  • @GPI The database design is just that. My concern is that in order to get my simple `Object` fully 'filled' I have to gather data from 5-7 different repositories. And what if that `Object` needs to be extended with even more data that will come from other repositories? Does it mean I have to increase my `@Service` class constructor argument count again? I am just searching for a way to utilize one `@Service` class which would have a reference to all the repositories at least in one Data Source. And then call that from `PersonConstructService` class. If that is good design. – Deniss M. Nov 24 '16 at 12:03
  • I guess you have it a bit backwards. If your @Service is asked to build an object that is so complex, that it need to fetch from all tables of all datables, then the question is not "should I inject more repositories in the service" but "is the concept I am building makes any sense, and is my database design up to this challenge". That being said, if your concern is more akin to "how could Spring discover all relevant repositories for me and inject them in the @Service", then yes, it (sort of) can. Is this last part really the point of your question ? (honest question ! :-) ) – GPI Nov 24 '16 at 12:08
  • @GPI I wish I could alter the current database design. Then composing such a simple object would only require one Repository. It's a matter of using what is in hand and making it look acceptable in code. But I understood your point :-) – Deniss M. Nov 24 '16 at 12:11
  • @GPI I am looking for a clean solution to somehow group my repositories and let go of excessive constructor overloading with DI. – Deniss M. Nov 24 '16 at 12:17

2 Answers2

2

I do not think you can / shoudl create a meta-repository like abstraction. Repositories have a well defined meaning, conceptually, they are CRUD services (and a bit more sometimes :-)) for your Hibernate/JPA/Datastore entities. And I guess this is enough for them. Anything more is confusing.

Now what I would propose is a "smart" way of building your "Person" objects that is automa(g)tically aware of any new services that contribute to the meaning of the Person object.

The crux of it would be that :

  • you could have your Repositories implement a given Interface, say PersonDataProvider, which would have a method, say public PersonPart contributeDataToPersonBuidler(PersonBuilder).
  • You would make your @Service implement Spring's BeanFactoryPostProcessor interface, allowing you to inspect the container for all such PersonDataProvider instances, and inject them to your service (see accepted answer at How to collect and inject all beans of a given type in Spring XML configuration)
  • Your @Service implementation would then be to ask all the PersonDataProviders in turn to ask them to contribute their data.

I could expand a bit, but this seems to me like the way to go.

One could argue that this is not clean (it makes your Repositories aware of "something" that happens at the service layer, and they should not have to), and one could work around that, but it's simpler to expose the gist of the solution that way.

EDIT : since this post was first written, I came aware that Spring can auto-detect and inject all beans of a certain type, without the need of PostProcessors. See the accepted answer here : Autowire reference beans into list by type

GPI
  • 9,088
  • 2
  • 31
  • 38
  • Autowire is a nice approach. However, how can you fetch the desired repo from the list of repos based on certain condition? – bluelurker Aug 18 '20 at 12:30
  • The question was, as I recall "how can I get all of them", and not "select one of them". If you want to select, you have multiple choices. Either name your beans (@Named/@Qualifier and the likes) if you know at build time how to chose. If you can only know at startup, see if you can use Spring EL, and if you can only kown at runtime, then you have to build a method (e.g. `public boolean supports(YourObject object)`) to choose which one(s) to call. – GPI Aug 18 '20 at 13:29
  • ok, in my case I have to resolve it at runtime so I guess I will go for the second approach. Thanks – bluelurker Aug 18 '20 at 14:03
2

I see it as a quite reasonable and practical data aggregation on Service layer. It's perfectly achievable in Spring. If you have access to repositories code you can name them all like:

@Repository("repoOne")
public class RepositoryOne {

@Repository("repoTwo")
public class RepositoryTwo {

And inject them into the aggregation service as necessary:

@Service
public class MultipleRepoService {

    @Autowired
    @Qualifier("repoOne")
    private RepositoryOne repositoryOne;

    @Autowired
    @Qualifier("repoTwo")
    private RepositoryTwo repositoryTwo;

    public void doMultipleBusiness() {
        repositoryOne.one();
        repositoryTwo.two();
    }
}

In fact, you even don't need to name and Qualify them if they are different classes, but if they are in hierarchy or have the same interface...

Also, you can inject directly to constructing method if autowiring is not a case:

public void construct(@Qualifier("repoOne")RepositoryOne repoOne,
                          @Qualifier("repoTwo")RepositoryTwo repoTwo) {
        repoOne.one();
        repoTwo.two();
    }
AlexGera
  • 756
  • 9
  • 19