0

I am building a Spring Boot project for work. In this project I have service which is tasked with getting certain Documents from another backend. There a quite a lot of different scenarios where the documents have to meet certain criteria e.g. be from a certain date, which can be matched freely. Currently this is accomplished with normal method like so:


@Service
public class DocumentService(){

  private OtherService otherService;

  @Autowire
  public DocumentService(OtherService otherService){
    this.otherService = otherService;
  }

  public List<Document> getDocuments() {
   ...
  }

  public List<Document> getDocuments(LocalDate date) {
   ...
  }

  public List<Document> getDocuments(String name){
   ...
  }

  public List<Document> getDocuments(String name, LocalDate date){
   ...
  }

}

I find this to be a rather bad solution because for every new combination there would need to be a new method. For that reason I'd like to use a fluent style interface for that, something like this:

//Some other class that uses DocumentService
documentService().getDocuments().withDate(LocalDate date).withName(String name).get();

I'm familiar with the Builder Pattern and method chaining but I don't see a way how I can adapt either one of those. Seeing as, per my understanding, @Service-classes are singletons in Spring Boot.

Is this at all possible with Spring Boot?

Puck
  • 33
  • 1
  • 9

2 Answers2

0

If you want to use a fluent interface here, the object returned by your getDocuments() method would have to be the starting point for the method chain. Perhaps create something like a DocumentFilter class that you can return from there, then you'll end up with something like this:

documentService.documents().getDocuments().withDate(LocalDate date).withName(String name).getDocuments()

In this example, your DocumentFilter will have withDate(...) and withName(...) methods, and each subsequent call includes all of the criteria from the preceding DocumentFilter.

Riaan Nel
  • 2,425
  • 11
  • 18
0

Doesn't have to be a Spring Boot solution, why not just introduce a POJO builder-like local class:

@Service
public class DocumentService(){
    public Builder documents() {
        return new Builder();
    }

    public class Builder {
        private LocalDate date;
        private String name;

        public Builder withDate(LocalDate date) {
            this.date = date;
            return this;
        }

        // etc

        public List<String> get() {
            final List<SomeDTO> results = otherService.doQuery(name, date, ...);
            // TODO - tranform DTO to List<String>
            return list;
        }
    }
}

Obviously make it static if it doesn't need access to the parent component.

You could make the Spring component and the builder be the same object but that does feel contrived, also I expect you would like to be able to support multiple builders.

Also I'm assuming the parent component is genuinely a service, i.e. it doesn't contain any state or mutators, otherwise you are introducing potential synchronization problems.

EDIT: Just for illustration the builder maintains the arguments to be passed to the otherService and performs any service-like transforms.

stridecolossus
  • 1,449
  • 10
  • 24
  • The parent component does not have any state or such but it references other components, see the code example I've updated. Would this also pose a problem? – Puck Dec 11 '20 at 14:48
  • Don't see that as a problem no, the only reason I mentioned is that the service is a singleton so you need to be aware of such considerations, but of course that applies to all your components (I shouldn't have confused the matter!) – stridecolossus Dec 11 '20 at 14:56
  • Could you also elaborate a little more on the builder? The Arguments are supposed to go inside the builder class, correct? Also should I just do the logic inside the `.get()` function or pass the builder to the @Service class? Thanks for clarifying the part before :) – Puck Dec 11 '20 at 15:08
  • @Puck Yes the arguments are properties of the builder. I've added a little bit more illustration, the `otherService` is treated as a DAO or spring data resource (just for example purposes), and the get() method does some transform of the results which is often required in a service. Hope that helps. You'll probably need to consider validation of the arguments before delegating to collaborating components. – stridecolossus Dec 11 '20 at 15:23
  • Thanks alot, that will do. I've marked you answer as accepted. – Puck Dec 11 '20 at 15:24