After this step you should know how to:
Among business requirements listed in step 0 one says about implementing different policies for joining two discounts. We are going to implement those policies as SmartParam functions.
SmartParam supports calling isolated functions and using them as plugins. Function can be defined in any JVM language as long as there is an implementation of invoker capable of calling it. For more technical details on functions read documentation. In this tutorial step I am going to show how to use bundled function implementations in practice.
Tutorial project has an interface MultiDiscountPolicy
for combining values of 2 discounts. Let's implement first policy,
that returns sum of two discounts:
public class SumDiscountsPolicy implements MultiDiscountPolicy {
@Override
@JavaPlugin("discount.policy.sum")
public Discount combine(Discount discountA, Discount discountB) {
return new Discount(discountA.value() + discountB.value());
}
}
You probably noticed, that overriden function has a new annotation on top of it: @JavaPlugin
. By placing this annotation
on a method, you tell function repository to register this method under discount.policy.sum
name. And thats it! If you
wonder what happens under the hood, check out Java function documentation.
Second policy, choosing higher discount, is going to be implemented as Spring function. Before starting with implementation, we need to add Spring integration module to dependencies:
<dependency>
<groupId>org.smartparam</groupId>
<artifactId>smartparam-spring</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
And configure Spring in ParamEngine (in RootContext.java
):
ParamEngineConfig config = ParamEngineConfigBuilder.paramEngineConfig()
.registerModule(new SpringModule(applicationContext))
.withParameterRepositories(repository)
.build();
ParamEngine configuration has a concept of modules that are helpful in encapsulating registration steps for more sophisticated modules. Spring module needs to have applicationContext to extract Spring beans from it.
Now let's implement the choose higher policy:
@Service
public class ChooseHigherDiscountPolicy implements MultiDiscountPolicy {
@Override
@SpringPlugin("discount.policy.chooseHigher")
public Discount combine(Discount discountA, Discount discountB) {
return discountA.value() >= discountB.value() ? discountA : discountB;
}
}
Just as with Java function, all you need to do to register a Spring function is annotate Spring bean method with @SpringPlugin
.
Documentation describes Spring functions in more details.
What is the difference between Spring and Java function? Spring function invokes annotated method in context of Spring bean
extracted from ApplicationContext
. This is initialized bean with all autowired dependencies in place. On the other hand,
Java function is invoked on new instance of parent class, so this class needs to have a no-arg constructor (doesn't have
to be public). Pseudocode below shows function invocation logic:
// Java:
(new SumDiscountsPolicy()).combine(...);
// Spring:
(applicationContext.getBean(ChooseHigherDiscountPolicy)).combine(...);
ParamEngine exposes a method to call any function:
Object functionReturnValue = paramEngine.callFunction("functionName", Object... args);
Let's use it in DiscountCalculator
to combine two discounts using choose higher policy:
public Discount calculateForUser(User user) {
long loyaltyDiscountValue = paramEngine.get("discount.loyalty",
user.registrationDate().toDate(),
user.accountType().name()
).getLong();
Discount loyaltyDiscount = new Discount(loyaltyDiscountValue);
long targetedDiscountValue = paramEngine.get("discount.targeted",
dateProvider.currentDate().toDate(),
user.login().value()
).getLong();
Discount targetedDiscount = new Discount(targetedDiscountValue);
Discount combinedDiscount = (Discount) paramEngine.callFunction("discount.policy.chooseHigher",
loyaltyDiscount, targetedDiscount);
return combinedDiscount;
}
Business requirements also mention using different policies depending on current date and account type. Let's define new parameter, discount-policy.param:
{
name: "discount.policy",
inputLevels: 2,
levels: [
{name: "date", type: "date", matcher: "between/ie"},
{name: "accountType", type: "string"},
{name: "policy", type: "string"}
]
}
date;accountType;policy
2013-06-01:2013-06-23;*;discount.policy.sum
*;PREMIUM;discount.policy.sum
*;*;discount.policy.chooseHigher
Content of this parameter is not so important (all in all premium users get better policy). Let's see it in action:
String policyFunction = paramEngine.get("discount.policy",
dateProvider.currentDate().toDate(),
user.accountType().name()
).getString();
Discount combinedDiscount = (Discount) paramEngine.callFunction(policyFunction,
loyaltyDiscount, targetedDiscount);
discount.policy
parameter.This is really flexible. Discount calculator doesn't need to know about any specific implementations, just policy function contract (take two discounts and return one). We could change policies in runtime without reloading the app.
ParamEngine has a convenience method callEvaluatedFunction
to avoid boilerplate code when calling functions that come
from evaluation of parameter:
Discount combinedDiscount = (Discount) paramEngine.callEvaluatedFunction("discount.policy",
new LevelValues(dateProvider.currentDate().toDate(), user.accountType().name()),
loyaltyDiscount, targetedDiscount);
It evaluates parameter of given name using ParamContext (step 3 of tutorial covers contexts) and interprets the output value as name of function to call.
All code can be found in branch step-2-first-function
of tutorial project.