Advantages of coding to interfaces

I am excited to share my recent selection as a contributor for C4GT’23, a prestigious mentoring program aimed at fostering the development of Digital Public Goods (DPGs) in India. While working on my C4GT project at NHA ABDM, I noticed one of the problems I had to work on was synchronising API calls in the ABDM architecture so that it becomes easier for developers to use the model.

One of the ways through which this can be done is by incorporating callbacks in our code. Callbacks are a common way to handle asynchronous operations in many programming languages. When making an asynchronous API call, you provide a callback function that will be executed when the operation completes. Within the callback function, you can handle the result or perform further actions. The key is to ensure that subsequent API calls or actions are triggered only after the completion of the previous one. This can lead to callback hell or nested callbacks, which can be difficult to manage.

In my case, I have to use Springboot, java, and NoSQL as my tech stack. Spring's asynchronous support doesn't directly make asynchronous design synchronous. It provides a way to execute tasks asynchronously within your application. However, you can leverage this support to synchronize asynchronous operations if needed. A sample code for the same can be:

@Service
public class MySyncService {
    private final MyAsyncService asyncService;

    public MySyncService(MyAsyncService asyncService) {
        this.asyncService = asyncService;
    }

    public void performSyncOperations() {
        CompletableFuture<String> asyncResult1 = asyncService.performAsyncOperation1();
        CompletableFuture<String> asyncResult2 = asyncService.performAsyncOperation2();

        CompletableFuture.allOf(asyncResult1, asyncResult2)
                .join();

        String result1 = asyncResult1.join();
        String result2 = asyncResult2.join();

        // Process the results synchronously
    }
}

But I realised this will overcomplicate the architecture since there are various levels of API layers in our architecture. It involves waiting for the completion of each asynchronous operation, which may introduce blocking behaviour if not handled carefully.

So the solution that works for our architecture involves the design of interfaces which interact with each other with 1-2 API calls in a synchronous manner.

Using interfaces instead of concrete classes in Spring Boot for making synchronous API calls offers several advantages:

  1. Abstraction and Lose Coupling: Interfaces provide a level of abstraction and decouple the code from specific implementations. By programming to an interface, you can switch implementations easily without modifying the code that depends on it. This flexibility allows you to introduce new implementations or mock objects during testing, making your code more maintainable and adaptable.

  2. Dependency Injection: Spring Boot promotes the use of dependency injection, where objects are provided as dependencies rather than being created directly within the class. By depending on interfaces rather than concrete classes, you allow Spring to handle the creation and management of the actual implementation instances. This improves modularity, testability, and the ability to swap implementations.

  3. Easy Mocking and Testing: Interfaces make it easier to create mock objects for testing purposes. In unit tests, you can create mock implementations of interfaces to simulate the behaviour and isolate the class under test. This enables more effective testing of the class's functionality without relying on real, potentially external dependencies. Additionally, mocking interfaces simplifies integration testing by allowing you to substitute implementations with test-specific ones.

  4. Flexibility for Multiple Implementations: In some scenarios, you may have multiple implementations of an API for different use cases or to support different integrations. Using interfaces allows you to define a contract for the API and have multiple classes implementing that interface based on specific requirements. This promotes modular and extensible code that can easily accommodate different implementations without impacting the consuming code.

  5. Future Compatibility: Interfaces provide a level of future compatibility and extensibility. If you anticipate that the synchronous API implementation may change or evolve in the future, programming to an interface ensures that your code will remain compatible as long as the interface contract is maintained. This allows for easier upgrades or modifications without impacting the code that relies on the interface.

So I found out that using interfaces instead of concrete classes in Spring Boot for making synchronous API calls improves code modularity, testability, flexibility, and future compatibility. It aligns with object-oriented programming principles and Spring's dependency injection approach, promoting clean and maintainable code.