Skip to content

Instantly share code, notes, and snippets.

@michael-simons
Created July 22, 2019 12:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michael-simons/b15b81dc759da98cffb0ead185e688c7 to your computer and use it in GitHub Desktop.
Save michael-simons/b15b81dc759da98cffb0ead185e688c7 to your computer and use it in GitHub Desktop.
Ongoing discussion about issue when shading Project Reactor
package com.example.demo;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.NoSuchElementException;
import org.junit.Test;
import org.reactivestreams.Publisher;
/**
* This meant to be read top to bottm. The first test is the scenario describe in the official reactor docs,
* the second test describes a possible messy situation for the user, when the wrong Flux get's imported somehow
* and the third one shows the usage of our API.
* </p>
* As we only expose reactive streams, we cannot expose our contextual information and cannot / do not expect
* contextual information coming in.
* </p>
* Spring Data Neo4j wraps the reactive streams returned from the driver inside Flux' and Monos (Flux.from(Publisher)).
* Contextual information stays on that level.
*/
public class ContextualScenarious {
@Test
public void normalContextualFlow() {
// Reading about the contextual api:
// https://projectreactor.io/docs/core/release/reference/#_the_context_api
// One of the main takeaways is
//
// > Note that in your chain of operators, the relative positions of where you write to the Context and where you
// > read from it matters: the Context is immutable and its content can only be seen by operators above it, as
// > demonstrated in the following code example:
// "Above" is meant pretty literally above: The usage of context elements happens in above the line were the context element is added
Publisher<String> publisher = Mono.just("Data")
.flatMap(s -> Mono.subscriberContext().map(ctx -> s + ctx.get("k")));
Mono<String> r = Mono.from(publisher)
.subscriberContext(ctx -> ctx.put("k", "'"));
StepVerifier.create(r)
.expectNext("Data'")
.verifyComplete();
}
@Test
public void shadedTypesAndStandardTypesGetsMixedUp() {
// This is unlikely, but you never now
Publisher<String> publisher = org.neo4j.driver.internal.shaded.reactor.core.publisher.Mono.just("Data")
.flatMap(s -> org.neo4j.driver.internal.shaded.reactor.core.publisher.Mono.subscriberContext()
.map(ctx -> s + ctx.get("k")));
Mono<String> r = Mono.from(publisher)
.subscriberContext(ctx -> ctx.put("k", "'"));
StepVerifier.create(r)
.expectErrorMatches(NoSuchElementException.class::isInstance)
.verify();
}
static class FakeRxStatementResult {
Publisher<String> records() {
return org.neo4j.driver.internal.shaded.reactor.core.publisher.Flux.just("A", "B", "C");
}
}
static class FakeRxSession {
FakeRxStatementResult run(String statementTemplate) {
return new FakeRxStatementResult();
}
<T> Publisher<T> close() {
return org.neo4j.driver.internal.shaded.reactor.core.publisher.Mono.empty();
}
}
@Test
public void normalUsageOfOurDriverApi() {
// Assumes a user don't want to work with raw publishers ;)
Flux<String> strings = Flux.usingWhen(
Mono.fromSupplier(FakeRxSession::new),
// The user always has to wrap in a standard flux, he has no means of adding or using the
// Project reactor context here.
// The driver itself doesn't make use of the context and even if it would, it could not gave
// away the information to the outside world, as our api shall be publishers alonw.
s -> Flux.from(
s.run("MATCH (m:Movie) RETURN m ORDER BY m.name ASC").records()
),
FakeRxSession::close
);
strings = strings
// The user is free to do whatever he wants with the so created flux, i.e. adding contextual information
.flatMap(s -> Mono.subscriberContext().map(ctx -> s + ctx.get("k")))
.subscriberContext(ctx -> ctx.put("k", "'"));
StepVerifier.create(strings)
.expectNext("A'", "B'", "C'")
.verifyComplete();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- I used Spring Boot's dependency cause I'm lazy… -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>2.0.0-alpha03</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment