Variable Tutorial

This tutorial demonstrates passing values downstream between functions using variables within a Spring Boot application.

Unlike direct method parameters, variables do not need to be passed explicitly by the caller. A variable set in one function is available for injection into any downstream function, allowing pipelines to be composed from independent classes that know nothing of each other.

Tutorial Source

Maven dependency

		<dependency>
			<groupId>net.officefloor.springboot</groupId>
			<artifactId>officefloor-rest-spring-boot-4-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

Application class

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

Out<T> / Var<T>

The following method demonstrates setting values for variables.

public class OutLogic {

	public static void setValues(Out<Person> person, @Description Out<String> description) {
		person.set(new Person("Daniel", "Sagenschneider"));
		description.set("Need to watch his code!");
	}
}

Variables are created by reflectively interrogating the method parameters. Because many variables may share the same type, qualifiers distinguish them — much like naming a variable.

The @Qualifier meta-annotation is the recommended approach. The qualifier used in this tutorial is:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Description {
}

Prefer Out<T> to set a variable. This allows the compiler to reason about out/in combinations and catch uninitialised variables.

When a function must both read and write the same variable, use Var<T>. Use this sparingly as it reduces the ability to statically reason about flow composition.

public class VarLogic {

	public static void setValues(Var<Person> person, @Description Var<String> description) {
		person.set(new Person("Daniel", "Sagenschneider"));
		description.set("Need to watch his code!");
	}
}

@Val / In<T>

The following method demonstrates reading variable values downstream.

public class ValLogic {

	public static void useValues(@Val Person person, @Description @Val String description,
			ObjectResponse<ServerResponse> response) {
		response.send(new ServerResponse(person, description));
	}
}

@Val annotates a parameter to receive the current value of a variable. It distinguishes the variable from other injected objects such as ObjectResponse.

The preferred combination is Out<T> upstream with @Val downstream — readable and avoids importing extra framework types at the read site.

For symmetry there is also In<T>:

public class InLogic {

	public static void useValues(In<Person> person, @Description In<String> description,
			ObjectResponse<ServerResponse> response) {
		response.send(new ServerResponse(person.get(), description.get()));
	}
}

Composing flows — YAML endpoint files

The YAML files wire together the upstream setter and downstream reader as a two-step pipeline. Each step is independent — neither class references the other.

OutSet:
  class: net.officefloor.tutorial.variablehttpserver.OutLogic
  method: setValues
  next: InUse
InUse:
  class: net.officefloor.tutorial.variablehttpserver.InLogic
  method: useValues
VarSet:
  class: net.officefloor.tutorial.variablehttpserver.VarLogic
  method: setValues
  next: ValUse
ValUse:
  class: net.officefloor.tutorial.variablehttpserver.ValLogic
  method: useValues
OutToValSet:
  class: net.officefloor.tutorial.variablehttpserver.OutLogic
  method: setValues
  next: OutToValUse
OutToValUse:
  class: net.officefloor.tutorial.variablehttpserver.ValLogic
  method: useValues

Testing

Tests use MockMvc to exercise the full composed pipeline through Spring MVC:

@SpringBootTest
@AutoConfigureMockMvc
public class VariableHttpServerTest {

	@Autowired
	private MockMvc mvc;

	@Autowired
	private ObjectMapper mapper;

	private static final ServerResponse EXPECTED =
			new ServerResponse(new Person("Daniel", "Sagenschneider"), "Need to watch his code!");

	@Test
	public void outIn() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/outIn")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json(mapper.writeValueAsString(EXPECTED)));
	}

	@Test
	public void varVal() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/varVal")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json(mapper.writeValueAsString(EXPECTED)));
	}

	@Test
	public void outVal() throws Exception {
		mvc.perform(MockMvcRequestBuilders.get("/outVal")
				.accept(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk())
				.andExpect(content().json(mapper.writeValueAsString(EXPECTED)));
	}
}

Next

The Kotlin tutorial demonstrates implementing REST procedure logic in Kotlin alongside Java procedures.