Search code examples
javaspringspring-bootspring-rabbit

Spring Boot call a Rest Controller method from a service


I am using Spring Boot to call a rest controller method from a service. When the method gets called, I get the error java.lang.NullPointerException The broad scenario is, my service receives a payload from RabbitMQ queue and extracts the contents of the payload which it should then pass to the controller to be saved into the database. The queue part works(I can receive messages from the queue and extract the contents). Also the database part works. The problem is calling the controller method from the service.

Here us my Rest Controller

@RestController
@RequestMapping("/auth")
public class AuthController implements AuthService {
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    AuthRepository authRepository;


    public AuthModel addAuthenticatable(AuthModel auth){
        auth.setCreatedAt(DateTimeUtility.getDateTime());
        return authRepository.save(auth);
    }
}

My service code:

public class QueueListener extends AuthController implements MessageListener{
    private String identifier;
    private JSONArray globalObject;
    private int userId;
    private String pin;

    @Autowired
    AuthController authController;

    @Override
    public void onMessage(Message message) {
        String msg = new String(message.getBody());
        String output = msg.replaceAll("\\\\", "");
        String jsonified = output.substring(1, output.length()-1);

        JSONArray obj = new JSONArray(jsonified);
        this.globalObject = obj;
        this.identifier = obj.getJSONObject(0).getString("identifier");
        resolveMessage();
    }
    public void resolveMessage() {
        if(identifier.equalsIgnoreCase("ADD_TO_AUTH")) {
            for(int i = 0; i < globalObject.length(); i++){ 
                JSONObject o = globalObject.getJSONObject(i);
                this.userId = Integer.parseInt(o.getString("userId"));
                this.pin = o.getString("pin");
            }

            AuthModel authModel = new AuthModel();
            authModel.setUserId(userId);
            authModel.setPin(pin);

            authController.addAuthenticatable(authModel);
        }
    }
}

The error occurs when I call the method addAuthenticatable() which is in AuthController. Any help will be appreciated.


Solution

  • I hope this does not go out of topic, but generally what we want to achieve is a sort of an onion architecture. The dependencies should have one direction.

    Your controller is an integration point for your application. You want per REST to trigger the execution of some piece of logic. Your controller should not extend classes or implement interfaces, that have to do with the business logic. This part belongs to another layer.

    Everything that is about logic belongs to services:

    @Service
    public class AuthService {
        @Autowired
        private AuthRepository authRepository;
    
        private String attribute;
    
        public boolean isAuthenticated(String username) {
            authRepository.doSomething();
            //implementation of the logic to check if a user is authenticated.
        }
    
       public boolean authenticate(String username, char[] password) {
           // implementation of logic to authenticate.
           authRepository.authenticate();
       }
    
       public AuthModel save(AuthModel model) {
           //implementation of saving the model
       }
    
    }
    

    Extracting your logic in the service layer, made things reusable. Now you could inject the service in a controller

    @RestController
    @RequestMapping("/auth")
    public class AuthController {
    
       @Autowired
       private AuthService authService;
    
       public AuthModel addAuthenticatable(AuthModel auth){
           //process input etc..
           return authService.save(auth);
       }
    }
    

    or in a amqListener

    @Component
    public class QueueListener implements MessageListener {
       @Autowired
       private AuthService authService;
    
       @Autowired
       private SomeOtherService otherService;
    
       @Override
       public void onMessage(Message message) {
          JSONArray array = processInput();
    
          JSONArray obj = new JSONArray(jsonified);
          String identifier = obj.getJSONObject(0).getString("identifier");
          // extract the business logic to the service layer. Don't mix layer responsibilities
          otherService.doYourThing(obj, identifier);
    
          resolveMessage();
      }
    
      private JSONArray processInput(Message message) {
         String msg = new String(message.getBody());
         String output = msg.replaceAll("\\\\", "");
         String jsonified = output.substring(1, output.length()-1);
    
    
    }
    

    and your configuration, so that you can let spring know where to look for the annotated classes.

    @Configuration
    @ComponentScan({"your.service.packages"})
    @EntityScan(basePackages = "your.model.package")
    @EnableJpaRepositories("your.repository.packages")
    @EnableRabbit // probaby
    @EnableWebMvc // probably
    public class Config {
       //you could also define other beans here
       @Bean
       public SomeBean someBean() {
           return new SomeBean();
       }
    }
    

    @pvpkiran gave the answer to your actual question. But I hope this helps you