Overview

In a previous article, I showed how to create a REST API in a Spring Boot application. In the demo we built, all the logic was in the Controller. For a complex application, this can violate the Single Responsibility principal, as the controller method is responsible for both returning data in an appropriate format and performing any business logic to retrieve and/or calculate that data, along with performing required workflows. To achieve a better separation of concerns, we should move any business logic out of the controller and into its own service layer. This will not only simplify our code, but it will allow us to deploy and scale the controller and the business logic separately.

NOTE: The code for the previous article can be found here. If you did not work through the samples in that article, you can use this code as a starting point for this article.

Some Business Logic

For this demonstration, I will create a new controller named "MathController" that contains two methods: add and subtract. These methods each accept two parameters and do exactly what their names suggest: find the sum and the difference between those numbers. The code is shown below:

@RequestMapping("math")
@RestController
public class MathController {
    @GetMapping("add/{firstNumber}/{secondNumber}")
    public ResponseEntity Add(
        @PathVariable("firstNumber") Integer firstNumber, 
        @PathVariable("secondNumber") Integer secondNumber) {
            Integer sum = firstNumber + secondNumber;
            return new ResponseEntity(sum, HttpStatus.OK);
    }

    @GetMapping("subtract/{firstNumber}/{secondNumber}")
    public ResponseEntity subtract(
        @PathVariable("firstNumber") Integer firstNumber, 
        @PathVariable("secondNumber") Integer secondNumber) {
            Integer difference = firstNumber - secondNumber;
            return new ResponseEntity(difference, HttpStatus.OK);
    }
}

If we run this locally and call it from a browser, the output is shown in Fig. 1.

AddNumbers
Fig. 1

There is nothing wrong with this code and it is probably acceptable for simple methods like these. But imagine if we were doing much more than performing simple arithmetic. Imagine complex workflows with dependencies on databases, file systems, or web services. Logic like that does not belong in the controller. It should be split into its own service.

We will pretend the logic above is complex and justifies splitting it into a distinct service.

Interfaces

In many cases, it is a good idea to create an interface on which to base your service classes. This supports polymorphism in your app and allows you to use different implementations for different purposes (for production vs for testing, for example).

I will save all my services and interface in a source folder named "services".

An interface defines the public properties and methods of any class that implements it.

We use the interface keyword to define an interface and list the name, input arguments, and return data type of each public method, as shown below:

public interface MathService {
    Integer AddNumbers(Integer firstNumber, Integer secondNumber);
    Integer SubtractNumbers(Integer firstNumber, Integer secondNumber);
}

Implementation

Now, we can create a class based on this interface. The implements keyword tells Java on which interface a class is based. If we base a class on an interface, we must implement every method in that interface. The code below shows this.

public class MathServiceImpl implements MathService {

    @Override
    public Integer AddNumbers(Integer firstNumber, Integer secondNumber) {
        Integer sum = firstNumber + secondNumber;
        return sum;
    }

    @Override
    public Integer SubtractNumbers(Integer firstNumber, Integer secondNumber) {
        Integer difference = firstNumber - secondNumber;
        return difference;
    }
}

The @Override attribute tells Java that this the methods override an interface.

Changing the Controller

Now, we can modify our controller to call this service, as shown below:

@RequestMapping("math")
@RestController
public class MathController {
    @GetMapping("add/{firstNumber}/{secondNumber}")
    public ResponseEntity Add(
        @PathVariable("firstNumber") Integer firstNumber, 
        @PathVariable("secondNumber") Integer secondNumber) {
            MathService = new MathServiceImpl();
            Integer sum = mathService.AddNumbers(firstNumber, secondNumber);
            return new ResponseEntity(sum, HttpStatus.OK);
    }

    @GetMapping("subtract/{firstNumber}/{secondNumber}")
    public ResponseEntity subtract(
        @PathVariable("firstNumber") Integer firstNumber, 
        @PathVariable("secondNumber") Integer secondNumber) {
            MathService = new MathServiceImpl();
            Integer difference = mathService.SubtractNumbers(firstNumber, secondNumber);
            return new ResponseEntity(difference, HttpStatus.OK);
    }
}

If we run this locally and call it from a browser, the output is shown in Fig. 2.

AddNumbers
Fig. 2

The results are the same as before our refactoring. The difference is that splitting into a service and interface provides more flexibility and maintainability. That may not be obvious for this simple contrived example; but it is very true for a complex workflow with external dependencies.

Dependency Injection

Conclusion

In this article, I will show you how to separate logic into its own service.

In the next article, I will show you how to simplify this using Dependency Injection.

You can find the sample code here.