Angular and Spring Webflux
In this article, we'll look at how to combine Angular, Spring Boot, MongoDB, and Webflux to create a full stack web application.
Join the DZone community and get the full member experience.
Join For FreeReactive in the Frontend and the Backend
The project AngularAndSpring shows how to be reactive in the frontend by using Angular and in the backend by using Spring Webflux and the reactive driver of MongoDB. Angular is reactive (non-blocking) by using Observables or Promises. Spring Webflux uses Flux<T> and Mono<T> to be reactive.
Why Reactive?
Normal rest controllers receive a request and a thread is used until the response is sent. In that time, the controller has to retrieve the data the thread is blocking while the data store performs the query. That turns in a performance bottleneck with rising concurrent requests. With reactive programming, the thread can perform other tasks while the data store retrieves the data. That offers a performance boost but requires a change to the reactive programming style.
DZone has a good article with a performance test on this topic.
The Project
The project AngularAndSpring is an example of Angular, Angular Material, Spring Webflux, and reactive MongoDB. It has a Maven build that uses npm. Two languages are supported and request rewriting is implemented.
It reads in the quotes of a few cryptocurrencies and displays them in a table. Each quote has a detail page with its data and a chart of today's quotes. The quotes are read into MongoDB with a scheduled task and read from there to display the pages. The user can sign in/login and then have access to the order books. The goal was to build a project with Angular and a reactive backend to have a reactive (non-blocking) system from the browser to the data store.
Angular Frontend
With the use of Observables or Promises, the Angular frontend is already reactive. The REST services can be used just the same. Here is an example service:
@Injectable()
export class CoinbaseService {
private _reqOptionsArgs= { headers: new HttpHeaders().set( 'Content-Type', 'application/json' ) };
private readonly _coinbase = '/coinbase';
private _utils = new Utils();
constructor(private http: HttpClient, private pl: PlatformLocation ) {
}
getCurrentQuote(): Observable<QuoteCb> {
return this.http.get(this._coinbase+'/current', this._reqOptionsArgs)
.map(res => this.lowercaseKeys(<QuoteCb>res))
.catch(this._utils.handleError);
}
getTodayQuotes(): Observable<QuoteCbSmall[]> {
return this.http.get(this._coinbase+'/today', this._reqOptionsArgs)
.catch(this._utils.handleError);
}
private lowercaseKeys(quote: QuoteCb): QuoteCb {
for (let p in quote) {
if( quote.hasOwnProperty(p) && p !== '_id' && p !== 'createdAt') {
quote[p.toLowerCase()] = quote[p];
}
}
return quote;
}
}
In line 11 the httpClient service gets the Coinbase Quote from the server.
In line 12 the response is mapped to the lowercaseKeys function. It copies the values of the uppercase keys in lowercase keys for further processing.
In line 13 possible errors are handled (logged).
That is the reactive service request of the Angular frontend. The HttpClient no longer needs a map call for '.json()'. It will become the standard HTTP client in the future.
In development setup, the UI is served by Angular-CLI and the REST services are provided by the Spring Boot application. To make the setup work, the Angular-CLI needs to proxy the REST requests to Spring Boot. For that, there is a config file.
Spring Webflux Backend
Spring provides the new reactive Webflux framework. It offers a controller interface that looks familiar and a reactive syntax. Here is an example controller:
@RestController
@RequestMapping("/coinbase")
public class CoinbaseController {
@Autowired
private ReactiveMongoOperations operations;
@GetMapping
public Flux<QuoteCb> allQuotes() {
return this.operations.findAll(QuoteCb.class);
}
@GetMapping("/today")
public Flux<QuoteCbSmall> todayQuotesBc() {
Query query = MongoUtils.buildTodayQuery(Optional.empty());
return this.operations.find(query,QuoteCb.class)
.filter(q -> filterEvenMinutes(q))
.map(quote -> new QuoteCbSmall(quote.getCreatedAt(), quote.getUsd(), quote.getEur(), quote.getEth(), quote.getLtc()));
}
@GetMapping("/current")
public Mono<QuoteCb> currentQuoteBc() {
Query query = MongoUtils.buildCurrentQuery(Optional.empty());
return this.operations.findOne(query,QuoteCb.class);
}
private boolean filterEvenMinutes(QuoteCb quote) {
return MongoUtils.filterEvenMinutes(quote.getCreatedAt());
}
}
In line 1-2 the Rest Controller is registered.
In line 5-6 the reactive MongoDB driver is injected.
In line 13-14 the "today" rest endpoint is registered and it returns the list of today's quotes.
In line 15 the query for MongoDB is built.
In line 16 MongoDB retrieves the quotes from the collection.
In line 17 the odd minutes are filtered out.
In line 18 the big quotes of type QuoteCb are mapped in a smaller object of type QuoteCbSmall.
Spring Webflux performs the retrieval to the data (non-blocking) and frees the thread until the data has arrived. The reactive programming style is familiar from the functional programming with the collections and streams.
Webflux Client
The client is a scheduled task:
@Component
public class ScheduledTask {
private static final Logger log = LoggerFactory.getLogger(ScheduledTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
private static final String URLCB = "https://api.coinbase.com/v2";
@Autowired
private ReactiveMongoOperations operations;
private WebClient buildWebClient(String url) {
ReactorClientHttpConnector connector =
new ReactorClientHttpConnector(options ->
options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000));
return WebClient.builder().clientConnector(connector).baseUrl(url).build();
}
@Scheduled(fixedRate = 60000, initialDelay=15000)
public void insertCoinbaseQuote() {
Date start = new Date();
WebClient wc = buildWebClient(URLCB);
try {
operations.insert(
wc.get().uri("/exchange-rates?currency=BTC")
.accept(MediaType.APPLICATION_JSON).exchange()
.flatMap(response ->response.bodyToMono(WrapperCb.class))
.flatMap(resp -> Mono.just(resp.getData()))
.flatMap(resp2 -> {log.info(resp2.getRates().toString());
return Mono.just(resp2.getRates());})
).then().block(Duration.ofSeconds(3));
log.info("CoinbaseQuote " + dateFormat.format(new Date()) + " " + (new Date().getTime()-start.getTime()) + "ms");
} catch (Exception e) {
log.error("Coinbase insert error "+ dateFormat.format(new Date()));
}
}
}
Line 1 is the @Component Annotations for Spring.
Line 8-9 is the injection of the MongoDB driver.
Line 11-16 creates a WebClient with a timeout.
Line 18 annotates a scheduled method that is run once a minute with an initial delay of 15 sec.
Line 23 is the insert statement for MongoDB.
Line 24 is the Webclient getting the method and the URI set.
Line 25 sets the accepted Media and calls exchange to start the request.
Line 26 takes the JSON body of the response and parses it into the WrapperCb object. Mono means that only one Object of the type is expected.
Line 27-28 unwrap two layers in the response object.
Line 28-29 log the result object and return a Mono<QuoteCb> to MongoDB.
Line 30 blocks the insert operation for a max of 3 seconds.
Line 31 logs the time the operation took.
This code is not reactive. It has a block(...) in line 30 that makes it wait a max of 3 seconds. That means 1 thread of the project is used to read in the data. Each request has 3 seconds to succeed or it fails. The requests are started every 3 seconds. Usually, 3 seconds are more than enough to get and store the response.
Conclusion
With Spring Webflux, Angular gets are a reactive partner. Developers who know Spring will find a familiar syntax with a functional programming model that is easy to learn. For people who want to go a more functional route, there is a more functional API too. MongoDB offers a reactive driver and a nice API/CLI to work with. For all projects that work with DBs that have a functional driver, Spring Webflux offers low hanging fruit.
Opinions expressed by DZone contributors are their own.
Comments