• 15 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 7/3/20

Configure beans conditionally for multiple versions

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!

Example of certificate of achievement
Example of certificate of achievement