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.
Depend on an interface, not on an implementation
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. 😉
Create a dummy implementation
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. 😁
Let's recap!
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!