Automated problem solving with Constraints & Contracts

Deploy services with constraints, and watch Vyne orchestrate solutions to adapt data when constraints aren't satisfied.

Overview

In this guide, we'll deploy a rules based compliance engine for financial trades, where rules require data sourced from different locations, and in specific formats.

We'll assume you've covered the previous walkthroughs, so we'll skip the basics and get straight to the main course.

This walkthrough doesn't have the full code for the three services - we just highlight the parts that are interesting. The full source for this demo is available on Gitlab, here.

Expressing constraints on services

Vyne uses Taxi to allow services to describe constraints they have on input parameters.

These are the classes & services we'll be using for this demo:

@Service
class TradeValueRuleService {
@Operation
@PostMapping("/rules/traderLimits")
fun evaluate(@RequestBody request: TradeValueRuleRequest):TradeValueRuleResponse {
// Not shown
}
}
@DataType
@ParameterType
data class TradeValueRuleRequest(
@Constraint("currency = 'USD'")
val tradeValue: TradeValue,
val traderLimit: TraderMaxTradeValue
)
@DataType
typealias TradeValue = Money
@DataType("io.vyne.Money")
@ParameterType
data class Money(
val currency: Currency,
val value: MoneyAmount)

This service evaluates a rule to do with TradeValue. The actual implementation of the rule isn't as interesting as the contract itself - let's take a look in closer detail:

@Operation
fun evaluate(@RequestBody request: TradeValueRuleRequest):TradeValueRuleResponse {}
data class TradeValueRuleRequest(
@Constraint("currency = 'USD'")
val tradeValue: TradeValue,
val traderLimit: TraderMaxTradeValue
)

Our Operation takes a TradeValueRequest as it's parameter - which in turn, declares a constraint on the tradeValue parameter - the currency of the tradeValue must be expressed in USD.

Let's run a couple of queries, and see how Vyne applies this constraint.

We'll use the TradeRequest as a starting point for our query, and populate it as follows:

When we submit this query, you should see the service gets invoked, and we get our response back. This is possible, because the currency we provided of USD satisfies the constraint.

If we change the Currency to USD and re-run the same query, we should get a different response:

We need something that can convert currencies!

Using contracts to describe behaviour

Contracts on Operations describe what functionality an operation provides. It's how Vyne can discover services that resolve constraints.

Over in another service, we have a RateConverterService, which has the following operation:

@PostMapping("/tradeValues/{targetCurrency}")
@ResponseContract(basedOn = "source",
constraints = ResponseConstraint("currency = targetCurrency")
)
@Operation
fun convert(@PathVariable("targetCurrency") targetCurrency: Currency, @RequestBody source: TradeValue): TradeValue {

The @ResponseContract on this operation tells use that it returns a TradeValue, that is based on the source parameter, but with it's currency value updated to the targetCurrency provided in the request.

It's perhaps a little easier to read in the generated Taxi schema:

operation convert( source : Money( currency = "GBP" ),
targetCurrency : String ) : Money( from source, currency = targetCurrency )

In other words - it converts currencies. Perfect!

With this service deployed, if we re-run the same query, we now get a different response & execution plan:

Note that now that a service is exposed that can convert currencies, Vyne automatically invokes it and then uses the returned result to call our TradeValueRuleService.

Summary

This walkthrough has shown how Vyne understands constraints on operation inputs, and when a constraint isn't satisfied, how Vyne will attempt to leverage existing services to automatically adapt data.