DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Application Listeners
  • Resilient Kafka Consumers With Reactor Kafka
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • A Robust Distributed Payment Network With Enchanted Audit Functionality - Part 2: Spring Boot, Axon, and Implementation

Trending

  • Building Scalable and Resilient Data Pipelines With Apache Airflow
  • Rethinking Recruitment: A Journey Through Hiring Practices
  • Segmentation Violation and How Rust Helps Overcome It
  • Doris: Unifying SQL Dialects for a Seamless Data Query Ecosystem
  1. DZone
  2. Data Engineering
  3. Databases
  4. Spring Webflux: A Basic CRUD Application (Part 1)

Spring Webflux: A Basic CRUD Application (Part 1)

In this post, we will explore the capabilities of Spring Webflux by means of creating a basic CRUD app, and implement a few CRUD operations.

By 
Gunter Rotsaert user avatar
Gunter Rotsaert
DZone Core CORE ·
Mar. 23, 18 · Tutorial
Likes (16)
Comment
Save
Tweet
Share
29.3K Views

Join the DZone community and get the full member experience.

Join For Free

In this post, we will continue exploring the capabilities of Spring Webflux by means of creating a basic CRUD application. We will not be using a database in this post because I did not want to be distracted with database stuff. Adding a database will be handled in part 2. Furthermore, we will implement a few CRUD operations and see how we can unit test a Flux. Source code can be found at GitHub.

Setup

In order to get started, we will create a project with Spring Initializr. Select the following dependencies:

  • Java 9
  • Spring Boot 2.0.0
  • Reactive Web

Generate the project and import it into your IDE. Now we are ready to get started.

What are we going to build? Let's assume that we have a ticketing company which sells tickets for several kinds of shows (e.g. concerts, musicals, etc.). The shows are listed in our 'database' and events occur on the shows like buying a ticket.

We will need the following:

  • The domain objects.
  • A data repository.
  • A handler (read: service).
  • A router.

Create the Domain Objects

The domain objects will be created in package com.mydeveloperplanet.myspringwebfluxcrudplanet.domain. We need a Show domain object in order to store the shows we are selling tickets for. A Show has an id and a title. The corresponding getters and setters are generated.

public class Show {
  private String id;
  private String title;

  public Show(String id, String title) {
    this.id = id;
    this.title = title;
  }

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

}

Next, to the Show domain object, we have a ShowEvent domain object, which will correspond to events which occur on a Show. The ShowEvent object has an id and a date which simulates the datetime the event occurred.

public class ShowEvent {
  private String id;
  private Date date;

  public ShowEvent(String id, Date date) {
    this.id = id;
    this.date = date;
  }

  public String getId() {
    return id;
  }

  public void setId(String id) {
    this.id = id;
  }

  public Date getDate() {
    return date;
  }

  public void setDate(Date date) {
    this.date = date;
  }

}

Create the Show Repository

In order to create the repository, we will implement the ReactiveCrudRepository interface. In order to make use of this interface, we need to add the spring-data-commons dependency to our pom.

<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-commons</artifactId>
  <version>2.0.5.RELEASE</version>
</dependency>

We create in the com.mydeveloperplanet.myspringwebfluxcrudplanet.repositories package a ReactiveShowRepository. We need to add the @Repository annotation in order to be able to inject it later on. As you can see, all the methods that need to be implemented, make use of the reactive types Mono and Flux.

@Repository
public class ReactiveShowRepository implements ReactiveCrudRepository<Show, String> {

  @Override
  public <S extends Show> Mono<S> save(S s) {
    return null;
  }

  @Override
  public <S extends Show> Flux<S> saveAll(Iterable<S> iterable) {
    return null;
  }

  @Override
  public <S extends Show> Flux<S> saveAll(Publisher<S> publisher) {
    return null;
  }

  @Override
  public Mono<Show> findById(String s) {
    return null;
  }

  @Override
  public Mono<Show> findById(Publisher<String> publisher) {
    return null;
  }

  @Override
  public Mono<Boolean> existsById(String s) {
    return null;
  }

  @Override
  public Mono<Boolean> existsById(Publisher<String> publisher) {
    return null;
  }

  @Override
  public Flux<Show> findAll() {
    return null;
  }

  @Override
  public Flux<Show> findAllById(Iterable<String> iterable) {
    return null;
  }

  @Override
  public Flux<Show> findAllById(Publisher<String> publisher) {
    return null;
  }

  @Override
  public Mono<Long> count() {
    return null;
  }

  @Override
  public Mono<Void> deleteById(String s) {
    return null;
  }

  @Override
  public Mono<Void> deleteById(Publisher<String> publisher) {
    return null;
  }

  @Override
  public Mono<Void> delete(Show show) {
    return null;
  }

  @Override
  public Mono<Void> deleteAll(Iterable<? extends Show> iterable) {
    return null;
  }

  @Override
  public Mono<Void> deleteAll(Publisher<? extends Show> publisher) {
    return null;
  }

  @Override
  public Mono<Void> deleteAll() {
    return null;
  }
}

Create the Handler

As seen in the previous post, a handler is an equivalent for a service in the MVC pattern. So, we create a ShowHandler which serves a method to retrieve all the shows we have to offer. We inject our ShowRepository and make use of the findAll method, which we still need to implement. We will return the list as a list of Shows. Just notice that, although we are retrieving a list of Shows as a Flux, we convert it into a Mono of type ServerResponse. This can be confusing in the beginning.

@Component
public class ShowHandler {

  private final ReactiveShowRepository showRepository;

  public ShowHandler(ReactiveShowRepository showRepository) {
    this.showRepository = showRepository;
  }

  public Mono<ServerResponse> all(ServerRequest serverRequest) {
    Flux<Show> shows = this.showRepository.findAll();
    return ServerResponse.ok().body(shows, Show.class);
  }
}

Create the Router

The next step is to create the Router in order to link a URL to our Handler. RouterFunctions are to be defined in a WebConfig class, which you can read about here. We add a route in order to retrieve all the Shows.

@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {

  @Bean
  public RouterFunction<ServerResponse> routeShow(ShowHandler showHandler) {
    return RouterFunctions
      .route(RequestPredicates.GET("/shows"), showHandler::all);
  }

}

Implement the findAll Operation

Our last step is to implement the findAll method in our ReactiveShowRepository. Because we are not using a database, we will store the items in a map. I am taking another shortcut here by setting the key of the map equal to the id of the Show object. This will make it easier to retrieve the Show object later on. The following is added to the ReactiveShowRepository class:

private final Map<String, Show> showsMap = new ConcurrentHashMap<>();

public ReactiveShowRepository() {
  showsMap.put("1", new Show("1", "Title1"));
  showsMap.put("2", new Show("2", "Title2"));
  showsMap.put("3", new Show("3", "Title3"));
  showsMap.put("4", new Show("4", "Title4"));
  showsMap.put("5", new Show("5", "Title5"));
}

The findAll method now retrieves the values from the map and returns them as a Flux.

@Override
public Flux<Show> findAll() {
  return Flux.fromIterable(showsMap.values());
}

Now its time to see if it all works. Start the application with the Maven target spring-boot:run. If started successfully, go to the URL, http://localhost:8080/shows. The following output should be visible in your browser:

[
  {"id":"1","title":"Title1"},
  {"id":"2","title":"Title2"},
  {"id":"3","title":"Title3"},
  {"id":"4","title":"Title4"},
  {"id":"5","title":"Title5"}
]

Implement the findById Operation

We also want to be able to retrieve a specific Show. Therefore, we will implement the findById method in the ReactiveShowRepository. It takes a String as a parameter which corresponds to a key in our map. With the key, we retrieve the Show that was requested. We then return it as a Mono. Notice that we use the justOrEmpty method. If we use the just method, it would give us an exception when an id was requested that doesn't exist.

@Override
public Mono<Show> findById(String s) {
  return Mono.justOrEmpty(showsMap.get(s));
}

In ShowHandler, we add a byId method which extracts the id from the request, calls the findById method of our repository and returns it as a Mono.

public Mono<ServerResponse> byId(ServerRequest serverRequest) {
  String id = serverRequest.pathVariable("id");
  Mono<Show> show = this.showRepository.findById(id);
  return ServerResponse.ok().body(show, Show.class);
}

The only thing we need to do is to add a route to our handler in the WebConfig class. More specific routes must be put before more general routes, so therefore we add the route before the shows route.

public RouterFunction<ServerResponse> routeShow(ShowHandler showHandler) {
  return RouterFunctions
    .route(RequestPredicates.GET("/shows/{id}"), showHandler::byId)
    .andRoute(RequestPredicates.GET("/shows"), showHandler::all);
}

Run the application and go to the URL http://localhost:8080/shows/2. The following is the output in the browser:

{"id":"2","title":"Title2"}

Create the ShowEvent Handler

Now it is time to do something with the ShowEvent we created earlier as a domain object. We will create a ShowEventHandler which will emit every second an event for a given Show. In order to accomplish this, we use the static generate method of Flux. The definition is as follows:

public static <T> Flux<T> generate(Consumer<SynchronousSink<T>> generator)

So, the generate method takes a Consumer of type SynchronousSink. The SynchronousSink emits by means of the next method objects. In our ShowEventHandler we emit ShowEvents. If we run this, then we will be flooded by ShowEvents. Therefore, we add the delayElements method to the Flux in order to make sure that we slow things down by emitting an object every second.

@Component
public class ShowEventHandler {

  public Mono<ServerResponse> events(ServerRequest serverRequest) {
    String showId = serverRequest.pathVariable("id");
    Flux<ShowEvent> events = Flux.<ShowEvent>generate(sink -> sink.next(new ShowEvent(showId, new Date()))).delayElements(Duration.ofSeconds(1));
    return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(events, ShowEvent.class);
  }
}

The final step is to add a route in order to retrieve the events via a URL. Again, we place it at the beginning of the routes, because it is the most specific route.

public RouterFunction<ServerResponse> routeShow(ShowHandler showHandler, ShowEventHandler showEventHandler) {

  return RouterFunctions
    .route(RequestPredicates.GET("/shows/{id}/events"), showEventHandler::events)
    .andRoute(RequestPredicates.GET("/shows/{id}"), showHandler::byId)
    .andRoute(RequestPredicates.GET("/shows"), showHandler::all);

}

Run the application and go to the URL http://localhost:8080/shows/2/events. The following is the output in the browser:

data:{"id":"2","date":1520084205593}

data:{"id":"2","date":1520084206712}

data:{"id":"2","date":1520084207741}

As you will notice, every second a new event is added.

Test the Show Events

Up till now, we tested manually. In the next unit test, we will show how we can test the show events URL. We will make use of a WebTestClient for this. In the first test, we will invoke the URL, check the content type, and verify the response status.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MySpringWebfluxCrudPlanetApplicationTests {

  @Autowired
  private WebTestClient webTestClient;

  @Test
  public void contextLoads() {
  }

  @Test
  public void testShowEvents() {
    webTestClient
      .get().uri("/shows/2/events")
      .accept(MediaType.TEXT_EVENT_STREAM)
      .exchange()
      .expectStatus().isOk();
  }
}

Running this test results in the following exception:

2018-03-03 14:51:47.468 ERROR 7508 --- [ctor-http-nio-1] r.ipc.netty.channel.ChannelOperations : [HttpClient] Error processing connection. Requesting close the channel

java.io.IOException: Connection closed prematurely

The stream continues sending data, resulting in this exception. The stream is doing exactly what we want, but this causes a problem for our test.

A solution is to catch the streaming response into a FluxExchangeResult. Next, we retrieve the body in a Flux. Finally, we use a StepVerifier in order to consume the Flux. We check whether 10 items are received and cancel the consumption of the Flux.

@Test
public void testShowEvents() {
  FluxExchangeResult<ShowEvent> result = webTestClient
    .get().uri("/shows/2/events")
    .accept(MediaType.TEXT_EVENT_STREAM)
    .exchange()
    .expectStatus().isOk()
    .returnResult(ShowEvent.class);

  Flux<ShowEvent> eventFlux = result.getResponseBody();

  StepVerifier.create(eventFlux)
    .expectNextCount(10)
    .thenCancel()
    .verify();

}

Unfortunately, an exception is thrown again:

java.lang.AssertionError: expectation "expectNextCount(10)" failed (expected: count = 10; actual: counted = 0; signal: onError(org.springframework.core.codec.CodecException: Type definition error: [simple type, class com.mydeveloperplanet.myspringwebfluxcrudplanet.domain.ShowEvent]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.mydeveloperplanet.myspringwebfluxcrudplanet.domain.ShowEvent` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: UNKNOWN; line: -1, column: -1]))

The problem is that the ShowEvent domain object cannot be deserialized because we don't have a default constructor. The solution is to add the default constructor to the ShowEvent domain object:

public ShowEvent() {
  super();
}

Running the test results in a successful test result!

Summary

In this post, we explored some of the capabilities of Spring Webflux in order to build a basic CRUD application without an underlying database. In the end, we showed how a stream of Flux can be created and how it can be tested. In part 2, we will explore how we can add a database to this application.

Spring Framework application unit test Object (computer science) Flux (machine-learning framework) Database Event

Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Application Listeners
  • Resilient Kafka Consumers With Reactor Kafka
  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • A Robust Distributed Payment Network With Enchanted Audit Functionality - Part 2: Spring Boot, Axon, and Implementation

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!