Build a Reactive Flight Tracker API with Spring Boot and WebFlux

How to build a real time flight tracker API using Spring boot and Spring WebFlux with a reactive mongodb repository, WebClient to send requests and RouterFunction to create controllers

Reactive Programming still attracts developer community attention and gains more and more popularity in the last few years because of its ability to change the way of building applications, from imperative to a declarative approach, resulting in a more responsive, resilient applications. So, Reactive Programming aims essentially to create non-blocking applications based on event-driven and asynchronous architecture and simplifying the scalability at the same time. In this tutorial we are going to see a real world reactive example of building a real time flight tracker API using Spring boot and Spring WebFlux.

Before starting the implementation of our application let’s first take a brief look at some definitions.

Reactive Streams

Reactive Streams describe an API specification that aims to standardize asynchronous stream processing with non-blocking back pressure on the JVM. This specification is defined in the Reactive Manifesto. With the backpressure feature, Reactive Streams allows to control the data processing rate between the subscriber and publisher to help avoiding the out-of-memory problems.

The Reactive Streams API has officially been introduced in Java 9 as java.util.concurrent.Flow.

Reactor

Reactor is a fully non-blocking reactive programming foundation for the JVM, developed by the Spring Team. It is the 4th Generation reactive Library aiming to build non-blocking applications on the JVM, it implement the Reactive Streams Specification.

Reactor introduces two main reactive types of Publishers  Flux  and  Mono . A Flux is a standard Publisher that may contain an uninterrupted number of asynchronous emitted events. A Mono is a specialized Publisher that can emit zero or one item.

Spring WebFlux

Spring WebFlux is a web framework that brings support for the reactive programming model. Spring WebFlux uses the Project Reactor internally, the library chosen by Spring. WebFlux is not a replacement for Spring MVC but it supports 2 distinct programming models, one is annotation-based @controller similar to Spring MVC, the other model is Functional using Java's 8 lambda functions for routing and handling methods.

We will use WebFlux in our project using the second programming model to have a better understanding of reactive paradigm. Enough theory and let’s start.

Real-time Flight Tracker API

To get a better understanding of the reactive model, we will walk through a practical example of building a realtime flight tracker app, showing available flights based on some criteria using opensky-network.org API; we will also retrieve aircraft details from the database.

Modeling the application domain

Our domain is composed of 4 classes: The first 3 classes  StateVector ,  FlightState  and  Flight  are related to the opensky-network API and used to map the response of the API; the last one is the  Aircraft  class representing the aircraft details retrieved from our local MongoDB database.

/**
 * @author Mohamed Makkaoui
 * 23 sept. 2018
 */
@Data
@JsonFormat(shape=JsonFormat.Shape.ARRAY)
public class StateVector {

 private String icao24;
    private String callsign;
    private String originCountry;
    private Double lastPositionUpdate;
    private Double lastContact;
    private Double longitude;
    private Double latitude;
    private Double geoAltitude;
    private boolean onGround;
    private Double velocity;
    private Double heading;
    private Double verticalRate;
    private Set sensors;
    private Double baroAltitude;
    private String squawk;
    private boolean spi;
    private PositionSource positionSource;
}

enum PositionSource {
    ADS_B(0),
    ASTERIX(1),
    MLAT(2),
    FLARM(3),
    UNKNOWN(4);

    private final int number;

    PositionSource(final int number) {
        this.number = number;
    }

    @JsonValue
    int getNumber() {
        return this.number;
    }
}

@Data
public class FlightState {
    
 private int time;
 private Collection states;
}

/**
 * @author Mohamed Makkaoui
 * 23 sept. 2018
 */
@Data
public class Flight {

 private String icao24;
 private int firstSeen;
 private String estDepartureAirport;
 private int lastSeen;
 private String estArrivalAirport;
 private String callsign;
 private int estDepartureAirportHorizDistance;
 private int estDepartureAirportVertDistance;
 private int estArrivalAirportHorizDistance;
 private int estArrivalAirportVertDistance;
 private int departureAirportCandidatesCount;
 private int arrivalAirportCandidatesCount;
}

/**
 * @author Mohamed Makkaoui
 * 23 sept. 2018
 */
@Data
@AllArgsConstructor
@Document(collection="aircraft")
public class Aircraft {

    @Id
    private String icao;
    private String registration, manufacturericao, manufacturername, model, owner, operator, reguntil, engines, built;
}

Add these classes inside the model package. For more understanding you can visit the opensky-network docs.

Repository

In our Article, we are using a reactive repository by extending the  ReactiveCrudRepository  interface that doesn’t differ too much from a normal CRUD repository, except that a reactive repository follows reactive paradigms and uses Project Reactor types as return types. These types can also be used as parameters. So there is nothing to worry about.

/**
 * @author Mohamed Makkaoui
 * 26 sept. 2018
 */
public interface AircraftRepository extends ReactiveCrudRepository{

 Mono getByIcao(String icao);
}

Next, we will use this repository to import the aircrafts details downloaded in csv format from the opensky-network.org into our mongo database. For this purpose we will use the  apache commons csv  to parse the csv file, filter empty rows and finally save it in our database. To achieve this create the  AircraftImporter  class that implements  CommandLineRunner  interface, so we can execute our run method at runtime.

/**
 * @author Mohamed Makkaoui
 * 28 sept. 2018
 */
@Component
public class AircraftImporter implements CommandLineRunner {

 private static final Logger LOGGER= LoggerFactory.getLogger(AircraftImporter.class);
 @Autowired
 private AircraftRepository aircraftRepo;
 @Autowired
 private ResourceLoader resourceLoader;
 private List aircraftList = new ArrayList();
 private CSVParser parser;
 
 private int recordNumber = 1;
 
 @Override
 public void run(String... args) throws Exception {
  
  Resource resource = resourceLoader.getResource("classpath:aircraftDatabase.csv");
  LOGGER.info("Start importing aircrafts ...");
  InputStream is = resource.getInputStream();
  BufferedReader br = new BufferedReader(new InputStreamReader(is));
  parser = new CSVParser(br, CSVFormat.EXCEL.withHeader().withTrim());
  
  Iterable records = parser.getRecords();
  for (CSVRecord r : records ){
   
   //save just the first 100 records for testing
   if(this.recordNumber > 100){
       break;
      }
   
      if (!r.get("icao24").isEmpty()){
       aircraftList.add(
                  new Aircraft(r.get("icao24"),
                          r.get("registration"),
                          r.get("manufacturericao"),
                          r.get("manufacturername"),
                          r.get("model"),
                          r.get("owner"),
                          r.get("operator"),
                          r.get("reguntil"),
                          r.get("engines"),
                          r.get("built")
                  ));
       this.recordNumber++;
      }
      
  };
  LOGGER.info("Clear database before inserting ...");
  this.aircraftRepo.deleteAll().subscribe(v -> LOGGER.info("Delete {}", v),
    e -> LOGGER.error("Delete Failed", e),
    () -> LOGGER.info("Clearing Data Complete!"));
  LOGGER.info("Inserting the new aircraft list in database ...");
  this.aircraftRepo.saveAll(aircraftList)
   .subscribe(v -> LOGGER.info("saving {}", v),
      e -> LOGGER.error("Saving Failed", e),
      () -> LOGGER.info("Importing Data Complete!"));
 }

}

Service Layer

Our service layer will be composed of two classes:

  •  AircraftServiceImpl  class: interacting with our local mongo database to retrieve aircrafts details.
  •  FlightServiceImpl  class: will interact with the opensky-network API server using the reactive WebClient interface in order to serve our requests.
/**
 * @author Mohamed Makkaoui
 * 26 sept. 2018
 */
@Service
public class AircraftServiceImpl implements IAircraftService {

 @Autowired
 private AircraftRepository aircraftRepo;
 
 @Override
 public Mono aircraftDetails(String icao) {
  
  return this.aircraftRepo.getByIcao(icao);
 }

}

/**
 * @author Mohamed Makkaoui
 * 26 sept. 2018
 */
@Service
public class FlightServiceImpl implements IFlightService{

    @Value("${opensky.root_url}")
    private String root_url;
    @Value("${opensky.all_states}")
    private String allStates;
    @Value("${opensky.all_flights}")
    private String allFlights;

    @Bean
    private WebClient client(){
        return WebClient.create(root_url);
    }
    
 @Override
 public Mono fetchFlightState() {
  
  return client().get()
    .uri(allStates)
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .flatMap(resp -> resp.bodyToMono(FlightState.class));
  
 }

 @Override
 public Mono fetchFlightsByInterval(int begin, int end) {
  
  return client().get()
    .uri(uriBuilder -> uriBuilder.path(allFlights)
      .queryParam("begin", Integer.toString(begin))
      .queryParam("end", Integer.toString(end))
      .build())
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .flatMap(resp -> resp.bodyToMono(Flight[].class));
 }

}

Controller

As we mentioned above, we will choose the functional programming model using the  RouterFunction  interface and lambda functions in our controller to differ from the traditional approach, which is based on annotations, at the same time we will discover and learn more about this new way of programming. Kill two birds with one stone, right?

So, our API will be composed of 3 endpoints:

  •  /flightState : REST endpoint, which returns the list of all live tracked flights.
  •  /flights : REST endpoint, which retrieves flights based on a certain time interval that must be sent with the request as request parameters “begin” and “end”.
  •  /aircraft/{icao} : REST endpoint, which returns the aircraft’s details from our database using the icao path variable .
/**
 * @author Mohamed Makkaoui
 * 26 sept. 2018
 */
@Component
public class FlightRouter {
 
 @Autowired
 private IFlightService flightService;
 @Autowired
 private IAircraftService aircraftService;
 
 @Bean
 public RouterFunction router() {
  
  return RouterFunctions
    .route(RequestPredicates.GET("/flightState")
      .and(RequestPredicates.accept(MediaType.TEXT_EVENT_STREAM)), this::flightState)
    .andRoute(RequestPredicates.GET("/flights")
      .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), this::flightsInTimeInterval)
    .andRoute(RequestPredicates.GET("/aircraft/{icao}")
      .and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), this::aircraftDetails);
 }
 
 private Mono flightState(ServerRequest req) {
  
  Mono flightState = this.flightService.fetchFlightState();
  
  return flightState
    .flatMap(f -> {
     System.out.println("Get all Flights");
     return ServerResponse.ok().body(Flux.from(flightState), FlightState.class);
     })
    .switchIfEmpty(ServerResponse.notFound().build());
 }
 
 private Mono flightsInTimeInterval(ServerRequest req) {
  int begin = Integer.valueOf(req.queryParam("begin").get());
  int end = Integer.valueOf(req.queryParam("end").get());
  Mono flights = this.flightService.fetchFlightsByInterval(begin, end);
  
  return flights
    .flatMap(f -> {
     return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(flights, Flight[].class);
    })
    .switchIfEmpty(ServerResponse.notFound().build());
 }
 
 private Mono aircraftDetails(ServerRequest req) {
  
  Mono aircraft = this.aircraftService.aircraftDetails(req.pathVariable("icao"));
  
  return aircraft
    .flatMap(f -> {
     return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(aircraft, Aircraft.class);
    })
    .switchIfEmpty(ServerResponse.notFound().build());
 }
}

In one of our routes, we are using the  text/event-stream  which is an attribute belonging to the concept of SSE (Server-sent Event). It will help us send new data when available from server to the browser at any time.

Test It!

Our last step will be the testing of our API. In order to do this you can choose between two options:

  1. Write a test unit using the Spring 5 WebTestClient that contains request methods similar to WebClient. Also, it has methods to check the response status, headers and body.
  2. Use the Google Chrome extension Postman.

Conclusion

Congratulations! In this article you have learned how you can apply the reactive programming paradigm in such a use case which is a real time flight tracker that is based on a lot of amount of transferred data. We have seen the reactive repository and how it is different from the traditional repository, we saw how to use the  WebClient  interface to send requests to a remote server and process the response and Finally, we learned how to create a controller to handle our requests using the WebFlux functional approach with  RouterFunction  interface and java’8 lambda functions.

I hope that this article was a great help for you. You can find the complete sample project in my Github repository.

Thanks for reading. I really enjoyed writing this article and stick around for my next article. If you have any questions feel free to post in the comment section and if you loved the content share it!

Name

Angular,7,Angular 8,1,Best Practices,1,Design,1,Firebase,1,Ionic,1,Java,5,Nodejs,2,Python,1,Restful API,1,Software Development,1,Spring,3,Spring Batch,1,Spring Boot 2,1,Web Development,1,
ltr
item
Programming Tutorials, News and Reviews: Build a Reactive Flight Tracker API with Spring Boot and WebFlux
Build a Reactive Flight Tracker API with Spring Boot and WebFlux
How to build a real time flight tracker API using Spring boot and Spring WebFlux with a reactive mongodb repository, WebClient to send requests and RouterFunction to create controllers
Programming Tutorials, News and Reviews
https://www.ninjadevcorner.com/2018/10/build-reactive-flight-tracker-api-with-spring-boot-and-webflux.html
https://www.ninjadevcorner.com/
https://www.ninjadevcorner.com/
https://www.ninjadevcorner.com/2018/10/build-reactive-flight-tracker-api-with-spring-boot-and-webflux.html
true
493653397416713395
UTF-8
Loaded All Posts Not found any posts VIEW ALL Readmore Reply Cancel reply Delete By Home PAGES POSTS View All RECOMMENDED FOR YOU LABEL ARCHIVE SEARCH ALL POSTS Not found any post match with your request Back Home Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sun Mon Tue Wed Thu Fri Sat January February March April May June July August September October November December Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec just now 1 minute ago $$1$$ minutes ago 1 hour ago $$1$$ hours ago Yesterday $$1$$ days ago $$1$$ weeks ago more than 5 weeks ago Followers Follow THIS CONTENT IS PREMIUM Please share to unlock Copy All Code Select All Code All codes were copied to your clipboard Can not copy the codes / texts, please press [CTRL]+[C] (or CMD+C with Mac) to copy