Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Injection parameters to methods of controllers #10873

Open
Captain1653 opened this issue May 25, 2021 · 4 comments
Open

Injection parameters to methods of controllers #10873

Captain1653 opened this issue May 25, 2021 · 4 comments

Comments

@Captain1653
Copy link
Contributor

Captain1653 commented May 25, 2021

Maybe it relates to #2983 (in some way). We use multi-tenant data isolation. Our main entity is AcHost. We need to transfer it in many controllers without copy/paste. We use action composition .

Our current solution works like that:

  1. Our Action (play.mvc.Action) put some params to request like that:
public OurAction extends Action<OurCustomAnnotation> {
 // constructor and dependencies 

   @Override
  public CompletionStage<Result> call(Request request) {
           
        String clientKey = // receive client key from jwt
        ACHost acHost = ourService.getACHost(clientKey).orElseThrow(() -> new RuntimeException("Not found " + clientKey));
            
        Request newly = request.addAttr(AC_HOST_PARAM, acHost);
        return delegate.call(newly);
   }
}
  1. Then we use it in controllers
public OurController {
  public CompletionStage<Result> updateCreationRestrictions(Request request) {   
     ACHost acHost = request.attrs().getOptional(AC_HOST_PARAM).get(); // for example call Optional.get()
    
      // our busines logic
  }   
}

And it looks really bad.

It will be cool to inject our entity directly in controller like that:

public class OurController {
 
   public CompletionStage<Result> creationRestrictionsPage(ACHost acHost) {
	//  our business  logic
   }
}

Is it possible to implement?

We have some idea about API for the feature (maybe it will be useful)

  1. Play Framework provides some interface (PlayInjector for example).
interface PlayInjector {
	CompletionStage<Http.Request> fillRequest(Http.Request request);
}
  1. We implement the interface like that (spring bean for example):
@Singleton
public class AcHostInjector implements PlayInjector {

	// constructor and dependencies

    @Override
    public Http.Request fillRequest(Http.Request request) {
    	String clientKey = // receive client key from jwt
        ACHost acHost = ac.getACHost(clientKey).orElseThrow(() -> new RuntimeException("Not found " + clientKey));
            
        // part from  #2983
        Request newly = request.addAttr(SOME_PARAM_NAME, acHost);
        return delegate.call(newly);
    }

}
  1. Play detects our PlayInjector-implementation, does some "magic" actions and we can inject our AcHost in controller like that:
public class OurController {
   
    public CompletionStage<Result> creationRestrictionsPage(ACHost acHost) {
	//  our business  logic
   }
}
  1. Declare in routes-file
    GET /some/url controllers.CreationRestrictionsController.creationRestrictionsPage(acHost: AcHost)

  2. Use in jsRoutes
    jsRoutes.controllers.CreationRestrictionsController.creationRestrictionsPage() // without additional params

It would be really useful for our team.

Kind regards

@mkurz
Copy link
Member

mkurz commented Feb 16, 2022

This would need experimenting to see if something like this is even possible with the way Play's routing and action methods calls work right now.

So, if I understand correctly, you don't want to repeat the call of

ACHost acHost = request.attrs().getOptional(AC_HOST_PARAM).get(); // for example call Optional.get()

in each action method.

What about adding a small helper method like:

private static ACHost acHost(Http.Request req) {
   return request.attrs().getOptional(AC_HOST_PARAM).get(); // for example call Optional.get()
}

So, you can just call acHost(request) when you need that object. This comes close to just having acHost passed as param.
You can also put that helper method in a parent class that all your controllers extend, to have it available in all your controllers.

@Captain1653
Copy link
Contributor Author

@mkurz Thank you for your response. The helper can help, but our main idea is "Use our domain objects in the signatures of our methods of controllers without additional helpers, Play's Request and etc". It will make the code cleaner and simpler.

@mkurz
Copy link
Member

mkurz commented Feb 23, 2022

@Captain1653 sure. However, to be honest, such a feature isn't priority right now. Sure it would be nice to have, but there a lot of things which would be nice to have. Feel free to provide a pull request anytime.

@Captain1653
Copy link
Contributor Author

@mkurz Can you give me some starting point for my research? Some classes, documentation and etc... Play Framework has similar approach, but the approach requires :

Note: in a real application such method should be lightweight and not involve e.g. DB access, because the code is called on the server IO thread and must be totally non-blocking. You would therefore for example use simple objects identifier as path bindable, and retrieve the real values using action composition.

Our new approach calls DB or does other HTTP-request (unlikely to happen).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants