Just imagine for a second that the OMDb API is not a free service anymore, and you have to pay for each call. In that case, you definitely wouldn’t want to keep calling it in dev and test environments. 💰
How could we have our application call the OMDb API in production, but not in dev and test environments? It would be good if we could have two different versions of MovieRatingService in our application. One that works as normal as it is for the production environment, and one that returns some random or fixed values just to keep the rest of the application working.
But how can we do that?
The solution for such scenarios is to have a common interface for MovieRatingService and have the WatchlistService depend on this interface. From there, you can have two (or more) implementations. Based on some conditions (dev or prod environment in our case), you can pick one of the implementations and put in the context. In this way, WatchlistService would be unaware of the implementations and would have the right one passed to it based on the environment. Let’s see how it’s done.
In this section, we want to refactor MovieRatingService and create and implement an interface for it. When we are done with that, we’ll change the WatchlistService to depend on that interface rather than the implementation.
First, change WatchlistService from a simple class to an interface and an implementation. So, rename the WatchlistService to MovieRatingServiceLiveImpl, and make it implement an interface called WatchlistService.
@Service
public class MovieRatingServiceLiveImpl implements MovieRatingService {
private String apiUrl = "http://www.omdbapi.com/?apikey=cc9bf9ef&t=";
@Override
public String getMovieRating(String title) {
/** Rest of the class**/ Then create the MovieRatingService interface in the service package to fix the compilation errors you are getting:
package com.openclassrooms.watchlist.service;
import java.util.Optional;
public interface MovieRatingService {
String getMovieRating(String title);
} Make sure that WatchlistService depends on the interface and not implementation. So you should see this in WatchlistService:
private WatchlistRepository watchlistRepository;
private MovieRatingService movieRatingService;
@Autowired
public WatchlistService(WatchlistRepository watchlistRepository, MovieRatingService movieRatingService) {
super();
this.watchlistRepository = watchlistRepository;
this.movieRatingService = movieRatingService;
}
/** Rest of the class **/That’s it. Give it a test! We refactored, so everything should work as before. 😉
So far, nothing has changed in the functionality, but now we are able to create an alternative implementation of the MovieRatingService interface exclusively for the dev environment. This implementation won’t call the external OMDb API. Instead, it will return 9.99 as rating no matter what input comes in. We can still run our application in the development environment without calling the expensive API.
We want to create another implementation of the MovieRatingService interface to be used in the dev environment. Let’s create this implementation and return 9.99 for all movies. Also, with @ConditionalOnProperty annotation, we specify under what condition we want this implementation to be added to the Spring container.
package com.openclassrooms.watchlist.service;
import java.util.Optional;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@ConditionalOnProperty(name = "app.environment", havingValue = "dev")
@Service
public class MovieRatingServiceDummyImpl implements MovieRatingService {
@Override
public String getMovieRating(String title) {
return "9.99";
}
}We made it clear that we want @ConditionalOnProperty to MovieRatingServiceLiveImpl to be put in the Spring container in the development environment, but we also need to make it clear that we want MovieRatingServiceLiveImpl to be put in the context only when we are in the production environment, or when app.environment has dev value - so also put @ConditionalOnProperty to MovieRatingServiceLiveImpl class.
@ConditionalOnProperty(name = "app.environment", havingValue = "prod")
@Service
public class MovieRatingServiceLiveImpl implements MovieRatingService {
/** Rest of the class **/Now you can test the application with different values of app.environment in the application.properties file and see how the application behaves. 😁
Now you know how to:
Make a service have dependency on an interface rather than implementation of another service.
Use @ConditionalOnProperty to add a bean to a Spring container based on a value in the application properties file.
Pretty neat, huh? 🤓
Now you're ready to take on the end-of-part activity, where you'll refactor an application! When you're done, I'll meet you in Part 4, where we'll review how to check our application, once it's in production. 😎 See you there!