gareth-antlr

Gareth is a tool that helps you to make the validation business goals part of your development project

License

License

Categories

Categories

Ant Build Tools ANTLR Compiler-compiler
GroupId

GroupId

org.craftsmenlabs.gareth
ArtifactId

ArtifactId

gareth-antlr
Last Version

Last Version

0.8.7
Release Date

Release Date

Type

Type

jar
Description

Description

gareth-antlr
Gareth is a tool that helps you to make the validation business goals part of your development project
Project Organization

Project Organization

craftsmenlabs

Download gareth-antlr

How to add to project

<!-- https://jarcasting.com/artifacts/org.craftsmenlabs.gareth/gareth-antlr/ -->
<dependency>
    <groupId>org.craftsmenlabs.gareth</groupId>
    <artifactId>gareth-antlr</artifactId>
    <version>0.8.7</version>
</dependency>
// https://jarcasting.com/artifacts/org.craftsmenlabs.gareth/gareth-antlr/
implementation 'org.craftsmenlabs.gareth:gareth-antlr:0.8.7'
// https://jarcasting.com/artifacts/org.craftsmenlabs.gareth/gareth-antlr/
implementation ("org.craftsmenlabs.gareth:gareth-antlr:0.8.7")
'org.craftsmenlabs.gareth:gareth-antlr:jar:0.8.7'
<dependency org="org.craftsmenlabs.gareth" name="gareth-antlr" rev="0.8.7">
  <artifact name="gareth-antlr" type="jar" />
</dependency>
@Grapes(
@Grab(group='org.craftsmenlabs.gareth', module='gareth-antlr', version='0.8.7')
)
libraryDependencies += "org.craftsmenlabs.gareth" % "gareth-antlr" % "0.8.7"
[org.craftsmenlabs.gareth/gareth-antlr "0.8.7"]

Dependencies

compile (3)

Group / Artifact Type Version
org.antlr : antlr4 jar 4.5.1
commons-io : commons-io jar 2.4
ch.qos.logback : logback-classic jar 1.1.3

provided (1)

Group / Artifact Type Version
org.projectlombok : lombok jar 1.16.6

test (3)

Group / Artifact Type Version
junit : junit jar 4.12
org.mockito : mockito-core jar 1.10.19
org.assertj : assertj-core jar 3.4.0

Project Modules

There are no modules declared in this project.

Gareth

Build Status Coverage Status

Gareth is platform that allows you to make business goal validation part of your development process. For extra information visit our website and our blog.

Building Gareth from source

Gareth is built using Apache maven. Download it here: https://maven.apache.org/install.html Note: to use the code base in Eclipse or IntelliJ, make sure you install the Lombok plugin and configure the compiler settings accordingly: https://github.com/mplushnikov/lombok-intellij-plugin

Running Gareth

Gareth can be run in two different manners, can be run with or without a REST interface.

Feeding Gareth

The Gareth framework must be fed with two pieces of information, the 'experiment' and the 'definitions'.

Experiment

The experiments describes the assumptions somebody has when they have certain functionality build. For example a certain functionality is build to reduce failed logins. (The why) Within a experiment you can describe this in a structured manner.

Experiment: reduce failed logins

Baseline: Get current failed logins
Assume: Failed login are reduced by 500
Time: 1 week

Baseline: Get current failed logins
Assume: Failed login are reduced by 1000
Time: 1 month

Baseline: Get current failed logins
Assume: Failed login are reduced by 2000
Time: 1 months
Success: Sent cake to the developers
Failure: Remove functionality

Definitions

Definitions is the code that is glued to the experiments structured language, by allowing this aproach there is no limitation how you can validate your assumptions.

import org.craftsmenlabs.gareth.api.annotation.Assume;
import org.craftsmenlabs.gareth.api.annotation.Baseline;
import org.craftsmenlabs.gareth.api.annotation.Failure;
import org.craftsmenlabs.gareth.api.annotation.Time;
import org.craftsmenlabs.gareth.api.storage.Storage;

import java.time.Duration;
import java.time.temporal.ChronoUnit;

public class LoginFailuresDefinitions {

    @Baseline(glueLine = "Get current failed logins")
    public void baseline(final Storage storage) {
        // Code to get failed logins
        storage.store("failedLogins", 1);
    }

    @Assume(glueLine = "Failed login are reduced by 500")
    public void assume500(final Storage storage) {
        // Code to get failed logins
        int failedLoginBefore = storage.get("failedLogins");
        // Code to get failed logins
    }

    @Assume(glueLine = "Failed login are reduced by 1000")
    public void assume1000(final Storage storage) {
        // Code to get failed logins
        int failedLoginBefore = storage.get("failedLogins");
        // Code to get failed logins
    }

    @Assume(glueLine = "Failed login are reduced by 2000")
    public void assume2000(final Storage storage) {
        // Code to get failed logins
        int failedLoginBefore = storage.get("failedLogins");
        // Code to get failed logins
    }

    @Success(glueLine = "Sent cake to the developers")
    public void success(){
        // Code to sent cake to the developers
    }

    @Failure(glueLine = "Remove functionality")
    public void failure(){
        // Code to remove functionality
    }

    @Time(glueLine = "1 week")
    public Duration getOneWeek(){
        return Duration.of(1, ChronoUnit.WEEKS);
    }

    @Time(glueLine = "1 month")
    public Duration getOneMonth(){
        return Duration.of(1, ChronoUnit.MONTHS);
    }

    @Time(glueLine = "2 months")
    public Duration getTwoMonths(){
        return Duration.of(2, ChronoUnit.MONTHS);
    }
}

Running Gareth (without REST interface)

Running Gareth without a REST interface can be done by creating a maven project that depends on the following maven dependency:

<dependency>
    <groupId>org.craftsmenlabs.gareth</groupId>
    <artifactId>gareth-core</artifactId>
    <version>0.8.6</version>
</dependency>

Create a java application that starts the Gareth framework:

import org.craftsmenlabs.gareth.api.ExperimentEngine;
import org.craftsmenlabs.gareth.api.ExperimentEngineConfig;
import org.craftsmenlabs.gareth.core.ExperimentEngineConfig;
import org.craftsmenlabs.gareth.core.ExperimentEngine;
import org.craftsmenlabs.gareth.examples.definition.SampleDefinition;

public class ExampleApplication {


    public static void main(final String[] args) {
        final ExperimentEnginePersistence experimentEnginePersistence = new FileSystemExperimentEnginePersistence.Builder().build();
        final ExperimentEngineConfig experimentEngineConfig = new ExperimentEngineConfigImpl
                .Builder()
                .addDefinitionClass(SampleDefinition.class)
                .addInputStreams(ExampleApplication.class.getClass().getResourceAsStream("/experiments/businessgoal-01.experiment"))
                .setIgnoreInvocationExceptions(true)
                .build();
        final ExperimentEngine experimentEngine = new ExperimentEngineImpl
                .Builder(experimentEngineConfig)
                .setExperimentEnginePersistence(experimentEnginePersistence)
                .build();
        experimentEngine.start();

        Runtime.getRuntime().addShutdownHook(new ShutdownHook(experimentEngine));
    }

    /**
     * Shutdown hook when application is stopped then also stop the experiment engine.
     */
    static class ShutdownHook extends Thread {

        private final ExperimentEngine experimentEngine;

        private ShutdownHook(final ExperimentEngine experimentEngine) {
            this.experimentEngine = experimentEngine;
        }

        @Override
        public void run() {
            experimentEngine.stop();
        }
    }
}

For building the standalone application, you can use the maven-shade-plugin to create a single jar containing all the necessary classes. You can add this your project pom.xml.

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.4.1</version>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.craftsmenlabs.gareth.examples.ExampleApplication</mainClass>
                    </transformer>
                </transformers>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

After doing a maven clean package you now can run the Gareth platform with your own definitions and experiments.

java -jar /experimentsPath/to/project.jar

Running Gareth (with REST interface)

Running Gareth with a REST interface can be done by creating a maven project that depends on the following maven dependency:

<dependency>
    <groupId>org.craftsmenlabs.gareth</groupId>
    <artifactId>gareth-core</artifactId>
    <version>0.8.6</version>
</dependency>
<dependency>
    <groupId>org.craftsmenlabs.gareth</groupId>
    <artifactId>gareth-rest</artifactId>
    <version>0.8.6</version>
</dependency>

Create a java application that starts the Gareth framework:

import org.craftsmenlabs.gareth.api.ExperimentEngine;
import org.craftsmenlabs.gareth.api.ExperimentEngineConfig;
import org.craftsmenlabs.gareth.api.rest.RestServiceFactory;
import org.craftsmenlabs.gareth.core.ExperimentEngineConfig;
import org.craftsmenlabs.gareth.core.ExperimentEngine;

public class GarethContext {

    public static void main(final String[] args) throws Exception {
        final RestServiceFactory restServiceFactory = new RestServiceFactoryImpl(); // Create a new rest service factory
        final ExperimentEngineConfig config = new ExperimentEngineConfigImpl.Builder().build();
        final ExperimentEngine engine = new ExperimentEngineImpl.Builder(config).setRestServiceFactory(restServiceFactory).build(); // And just include it in the engine
        engine.start();
    }
}

For building the standalone application, you can use the maven-shade-plugin to create a single jar containing all the necessary classes. You can add this your project pom.xml.

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.4.1</version>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.craftsmenlabs.gareth.examples.ExampleApplication</mainClass>
                    </transformer>
                </transformers>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

After doing a maven clean package you now can run the Gareth platform with your own definitions and experiments.

java -jar /experimentsPath/to/project.jar

Matching glue lines to definition methods

Your baselines, assumptions, success and failure definitions need to correspond to Java methods that do the actual work. These Java methods reside in so-called definition classes that are known to Gareth through configuration, and the relevant methods are identified through the @Baseline, @Assume, @Time, @Success and @Failure annotations. In its simplest form, it looks like this:

Baseline: sale of anvils

@Baseline(glueLine = "sale of anvils")
public void getSaleOfAnvils() {  
  System.out.println("Getting sale of anvils");
}

or with using the built-in Storage:

@Baseline(glueLine = "sale of anvils")
public void getSaleOfAnvils(final Storage storage) {  
  storage.store(product, dbase.getCurrentSalesOfProduct("anvil"));
}

A shortcoming of this approach is that there is a strict one-to-one mapping between glue line and definition method. If you want to get the sale of hammers or screwdrivers in a different experiment you'd need to write new methods for each with probably very similar code. That hardly seems efficient. It would be much better if we could make our method configurable by adding the product as a parameter: public void getSaleByProductCode(final String productCode){...}

Then we can indicate the configurable part in the glue line by means of a grouped regular expression, like so:

@Baseline(glueLine = "sale of (.*?)")
public void getSaleOfProductByCode(final Storage storage,final String productCode) {  
  storage.store(product, dbase.getCurrentSalesOfProduct(productCode));
}

Multiple groupings are allowed:

@Success(glueLine = "order (\\d{1,3}) (.*?) from (.*?)")
public void sendTreats(int amount, String treat, String supplier) {
	String.format("Enjoy the %d %s from %s", amount, treat, supplier);
}

And this lets you re-use the same Java code for very different glue lines: Success: order 3 carrot cakes from local bakery Success: order 5 iPhones from Amazon

It's a powerful mechanism, but there are some rules to the game:

  • The number of parenthesised regex groups must be exactly equal to the number of parameters in the method, ignoring the optional Storage parameter, which always comes first and is injected by Gareth when specified. The values are then extracted from the glueline and the definition method is called with these parameters: "order 3 carrot cakes from local bakery" matches on 3, "carrot cakes" and "local bakery" and calls sendTreats(3,"carrot cakes","local bakery")

  • Permitted arguments types are String, Integer, Long, Double and their corresponding primitive types. Use of other types in definition methods will cause an error. Gareth must be able to convert parse the regex matches (always String) to a valid Java type.

Specifying expected

The regex mechanism as described above is not available for the Time glue line, meaning that the @Time annotated definition method cannot be configured with arguments. However, you may leave it out entirely if your Time glue line follows the pattern of [number] [expected], where expected is one of second, minute, hour, day, week, month, year, or their corresponding plurals:

  • Time: 48 hours
  • Time: 3 weeks
  • Time: 42 days
  • Time: 1 month
  • Time: 1 year

Note that month is always 30 days and year is 365 days. Running the experiment with 1 month expected on the 1st of February will check the assume on the 3rd of March, ignoring leap years. If you want specific behaviour you can still write your own implementation:

Time: Tuesday after next Easter
@Time(glueLine = "Tuesday after next Easter")
public Duration sampleTime() { .. }

Contribute

You can contribute to this repository the by following these steps.

  • Fork this repository
  • Create a branch
  • Do your coding
  • Create a pull request to integrate the branch

Help wanted!

We need some help to achieve the following goals:

  • Create plugins for the major IDEs
  • Have support for other languages than Java
org.craftsmenlabs.gareth

Versions

Version
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0