综合技术

opel — asynchronous expression language

微信扫一扫,分享到朋友圈

opel — asynchronous expression language
0

In everyday work programmers are facing various problems. We would like to focus on two of them: big systems with non-blocking API
and specific business needs that can be solved using Expression Language
. Hold tight!

How to use a non-blocking API in an expression language? The answer is the new expression language — opel
. It was designed to enable writing simple, single line asynchronous expressions. It uses Parboiled
as a language grammar engine and Java 8 CompletableFuture
.

Getting started

To use opel
, just add this dependency to your project:

gradle

compile group: 'pl.allegro.tech', name: 'opel', version: '1.0.1'

maven


pl.allegro.tech
opel
1.0.1

And you’re ready to go!

Simple use case

You can use opel
just as you would use any other existing expression language such as SpEL
or JEXL
. The main difference is that opel
always returns CompletableFuture:

String expression = "2 * 3 + 4";
OpelEngine engine = OpelEngineBuilder.create().build()
engine.eval(expression)
	.whenComplete((result, error) -> System.out.println(result));

this expression is transformed to equivalent code:

CompletableFuture.completedFuture(2)
    .thenCombine(CompletableFuture.completedFuture(3), (l, r) -> l * r)
    .thenCombine(CompletableFuture.completedFuture(4), (l, r) -> l + r)
    .whenComplete((result, error) -> System.out.println(result));

As you see, opel
efficiently hides boilerplate of the CompletableFuture API.

Taste the asynchronous

Typically, operations that use external systems through the REST API or database queries are the main source of Future objects in the system. This type of calls can be easily integrated with opel
.

To achieve this, OpelAsyncFunction
interface has to be implemented. It is an implementation of the well known adapter design pattern.

public class ExampleTemperatureFunction implements OpelAsyncFunction<BigDecimal> {
    @Override
    public CompletableFuture<BigDecimal> apply(List<CompletableFuture> args) {
        return args.get(0).thenApply(city -> {
            // add code to call external service about temperature in given city
            return BigDecimal.valueOf(22);
        });
    }
}

That function can be added to OpelEngine
:

OpelEngine engine = OpelEngineBuilder.create()
                    .withFunction('temperature', new ExampleTemperatureFunction())
                    .build()

Then you can the evaluate expressions using the function:

String expression = "if (temperature('warsaw') > 25) 'Stay at home' else 'Go for a jog' ";
engine.eval(expression)
	.whenComplete((result, error) -> System.out.println(result));

Get rid of parsing overhead

Using Future API is the first step towards improving performance. Next step is reducing the frequency of expression parsing. Expression parsing is an expensive operation and should be avoided.

OpelEngine
returns the result of expressions parsing allowing re-use of it:

OpelEngine engine = OpelEngineBuilder.create().build();
OpelParsingResult parsingResult = engine.parse(expression);

parsingResult.evaluate()
	.whenComplete((result, error) -> System.out.println(result));

parsingResult.evaluate() // no parsing, only evaluation
    .whenComplete((result, error) -> System.out.println(result));

It is also worth mentioning that parsing result
object is stateless and thread-safe, and can be used to evaluate expressions by many threads (as long as functions and values registered in the engine are thread-safe).

Evaluation context

In all the above examples evaluation of the expression depends only on the expression itself and on registered functions and values. Sometimes we may want to achieve a behaviour where every evaluation of parsed expression depends on current context. Although it is not possible to pass arguments to opel expressions, it is possible to define a local evaluation context
object and pass it to eval
method. All functions and values registered in evaluation context
will overwrite those defined in the engine. This situation is common for web applications, where context contains functions and values related to current request (eg. function or values returning information about signed in user).

String expression = "if (temperature(currentLocation) > 25) 'Stay at home' else 'Go for a jog' ";

OpelEngine engine =  OpelEngineBuilder.create()
                    .withFunction('temperature', new ExampleTemperatureFunction())
                    .build();
OpelParsingResult parsingResult = engine.parse(expression);

EvalContext context1 = EvalContext.Builder.create()
	.withValue("currentLocation", CompletableFuture.supplyAsync(() -> "Warsaw"))
	.build()

parsingResult.eval(expression, context1)
	.whenComplete((result, error) -> System.out.println(result));

EvalContext context2 = EvalContext.Builder.create()
	.withCompletedValue("currentLocation", "London")
	.build()

parsingResult.eval(expression, context2)
	.whenComplete((result, error) -> System.out.println(result));

Conclusion

In these few examples we present a vision of opel
and a way to write non-blocking expressions without boilerplate of Java 8 CompletableFuture API. But it is not the end. Opel
has many more features to write sexy expressions such as implicit conversion, map and list concise notation, support for own values etc.

Since September 1st, opel
is open source and you can find more information about its features at github
.

阅读原文...


allegro.tech

Webmaster Forums Top AMP Questions

上一篇

原型与你 - Weekly NO.38

下一篇

您也可能喜欢

评论已经被关闭。

插入图片
opel — asynchronous expression language

长按储存图像,分享给朋友