Skip to content
Simplify the development and maintenance of message-driven applications.
Java FreeMarker
Branch: master
Clone or download
Latest commit 6b8d55e Aug 16, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
gradle/wrapper Update Gradle wrapper to 5.3 Mar 26, 2019
requirementsascodecore Release v1.2.1 Jul 6, 2019
requirementsascodeexamples Update README.md Jul 29, 2019
requirementsascodeextract Release v1.2.1 Jul 6, 2019
.gitattributes 🍭 Added .gitattributes & .gitignore files Sep 27, 2016
.gitignore Added source files Sep 27, 2016
.travis.yml Revert "Retry oraclejdk12" Mar 26, 2019
CODE_OF_CONDUCT.md Create CODE_OF_CONDUCT.md Apr 27, 2018
CONTRIBUTING.md Update CONTRIBUTING.md Oct 21, 2018
LICENSE
README.md Update README.md Aug 16, 2019
build.gradle Release v1.2.1 Jul 6, 2019
gradlew Update Gradle wrapper to 5.3 Mar 26, 2019
gradlew.bat Update Gradle wrapper to 5.3 Mar 26, 2019
settings.gradle Remove hexagon example (as it's in its own GitHub repository now) May 16, 2019

README.md

requirements as code

Build Status

This project simplifies the development of message-driven applications.

It provides a builder API to create handlers for many types of messages at once.

You can customize message handling in a simple way, for example for measuring performance, or for logging purposes.

For more advanced cases that depend on the application's state, like Process Managers and Sagas, you can create a model with flows. It's a simple alternative to state machines, understandable by developers and business people alike.

For the long term maintenance of your application, you can generate documentation from the models inside the code without the need to add comments to it.

getting started

At least Java 8 is required to use requirements as code, download and install it if necessary.

Requirements as code is available on Maven Central.

If you are using Maven, include the following in your POM, to use the core:

  <dependency>
    <groupId>org.requirementsascode</groupId>
    <artifactId>requirementsascodecore</artifactId>
    <version>1.2.1</version>
  </dependency>

If you are using Gradle, include the following in your build.gradle, to use the core:

compile 'org.requirementsascode:requirementsascodecore:1.2.1'

how to use requirements as code

Here's what you need to do as a developer.

Step 1: Build a model defining the message types to handle, and the methods that react to a message:

Model model = Model.builder()
	.user(<command class>).system(<command handler, i.e. lambda, method reference, consumer or runnable)>)
	.user(..).system(...)
	...
.build();

The order of the statements has no significance. For handling events instead of commands, use .on() instead of .user(). For handling exceptions, use the specific exception's class or Throwable.class as parameter of .on(). Use .condition() before .user()/.on() to define an additional precondition that must be fulfilled. You can also use condition(...) without .user()/.on(), meaning: execute at the beginning of the run, or after a step has been run, if the condition is fulfilled.

Step 2: Create a runner and run the model:

ModelRunner runner = new ModelRunner().run(model);

Step 3: Send messages to the runner, and enjoy watching it react:

runner.reactTo(<Message POJO Object> [, <Message POJO Object>,...]);

To customize the behavior when the runner reacts to a message, use modelRunner.handleWith(). By default, if a message's class is not declared in the model, the runner consumes it silently. To customize that behavior, use modelRunner.handleUnhandledWith(). If an unchecked exception is thrown in one of the handler methods and it is not handled by any other handler method, the runner will rethrow it.

hello world

Here's a complete Hello World example:

package hello;

import org.requirementsascode.Model;
import org.requirementsascode.ModelRunner;

public class HelloUser {
	public static void main(String[] args) {
		new HelloUser().buildAndRunModel();
	}
	
	private void buildAndRunModel() {
		Model model = Model.builder()
			.user(RequestHello.class).system(this::displayHello)
			.user(EnterName.class).system(this::displayName)
		.build();

		new ModelRunner().run(model)
			.reactTo(new RequestHello(), new EnterName("Joe"));		
	}

	public void displayHello(RequestHello requestHello) {
		System.out.println("Hello!");
	}

	public void displayName(EnterName enterName) {
		System.out.println("Welcome, " + enterName.getUserName() + ".");
	}

	class RequestHello {}
	
	class EnterName {
		private String userName;

		public EnterName(String userName) {
			this.userName = userName;
		}

		public String getUserName() {
			return userName;
		}
	}
}

event queue for non-blocking handling

The default mode for the ModelRunner is to handle messages in a blocking way. Instead, you can use a simple event queue that processes events one by one in its own thread:

Model model = ...;
ModelRunner modelRunner = new ModelRunner();
modelRunner.run(model);

EventQueue queue = new EventQueue(modelRunner::reactTo);
queue.put(new String("I'm an event, react to me!"));

The constructor argument of EventQueue specifies that each event that's put() will be placed in the queue, and then forwarded to ModelRunner.reactTo(). Note that you can forward events to any other consumer of an object as well.

publishing events

When you use the system() method, you are restricted to just consuming messages. But you can also publish events with systemPublish(), like so:

	private void buildAndRunModel() {
		Model model = Model.builder()
			.on(EnterName.class).systemPublish(this::publishNameAsString) 
			.on(String.class).system(this::displayNameString) 
		.build();		
		
		Optional<Object> userName = new ModelRunner().run(model)
			.reactTo(new EnterName("Joe"));	
	}
	
	private String publishNameAsString(EnterName enterName) {
		return enterName.getUserName();
	}
	
	public void displayNameString(String nameString) {
		System.out.println("Welcome, " + nameString + ".");
	}

As you can see, publishNameAsString() takes a command object as input parameter, and returns an event to be published. In this case, a String. By default, the model runner takes the returned event and publishes it to the model. In the example, this will print "Welcome, Joe."

This behavior can be overriden by specifying a custom event handler on the ModelRunner with publishWith(). For example, you can use modelRunner.publishWith(queue::put) to publish events to an event queue.

documentation

publications

subprojects

build from sources

Use Java >=11 and the project's gradle wrapper to build from sources.

related topics

  • The work of Ivar Jacobson on Use Cases. As an example, have a look at Use Case 2.0.
  • The work of Alistair Cockburn on Use Cases, specifically the different goal levels. Look here to get started, or read the book "Writing Effective Use Cases".
You can’t perform that action at this time.