After this step you should know how to:
SmartParam is distributed via Maven Central, so all you need to do is
add dependencies to project pom.xml
(sorry, only Maven is covered here).
First, engine:
<dependency>
<groupId>org.smartparam</groupId>
<artifactId>smartparam-engine</artifactId>
<version>1.1.1</version>
</dependency>
Secondly, repository (FS repository contains both filesystem and classpath implementations):
<dependency>
<groupId>org.smartparam</groupId>
<artifactId>smartparam-repository-fs</artifactId>
<version>1.1.1</version>
</dependency>
Classpath repository requires relfections to be present in classpath, so we need to add this dependency as well:
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.9-RC1</version>
</dependency>
Rebuild the project and all dependencies should be fetched. Now it's time to make use of freshly imported dependencies.
RootContext class in tutorial application is responsible for instantiating Spring and it is the place where SmartParam initialization will be defined. Creating ParamEngine is of course a generic piece of code that can be used in any other place in your real application.
First step is creating the repository. For sake of simplicity for first few tutorial steps we will be using Classpath repository, which accepts serialized parameters defined in text files.
ClasspathParamRepository repository = new ClasspathParamRepository("/param", ".*\\.param$");
Repository constructor accepts two arguments: path prefix and regular expression for filtering parameter files. If you need more information, please read classpath repository documentation.
Now we need the ParamEngine itself. Common convention shared among SmartParam modules that have many configuration options is to define immutable Config object and ConfigBuilder to construct it. ParamEngine follows this convention:
ParamEngineConfig config = ParamEngineConfigBuilder.paramEngineConfig()
.withParameterRepositories(repository)
.build();
ParamEngine paramEngine = ParamEngineFactory.paramEngine(config);
To read more about ParamEngine options see its documentation.
Now that we have ParamEngine, we need some parameters that make use of it. From business requirements described in step 0 we need two parameters. First one is loyalty discount.
Loyalty discount varies depending on registration date and account type. It will be defined in /param/discount-loyalty.param
file located in Maven other resources. Serialized parameter has two parts: metadata that describe behavior and
content (serialization documentation). Metadata for loyalty discount is defined in JSON:
{
name: "discount.loyalty",
inputLevels: 2,
levels: [
{name: "registrationDate", type: "date", matcher: "between/ie"},
{name: "accountType", type: "string"},
{name: "value", type: "integer"}
]
}
This tells ParamEngine, that:
discount.loyalty
date
, which is going to use between/ie
matcherstring
integer
Levels are dimensions (or "columns") of parameter. Input levels tells how many levels define the input criterias - in this case there are two input levels: registration date and account type. This covers business requirements ( Loyalty discount varies depending on registration date and account type. ). All other levels are returned by ParamEngine. See domain to read about other parameter options.
Now we should define contents of parameter. This contains all conditions that business requires and is defined in CSV format (with header). By default semicolon is used as value separator. CSV columns map to parameter levels. Example content of loyalty discount parameter:
registrationDate;accountType;value
*:2013-12-01;PREMIUM;20
*:2013-12-01;REGULAR;5
2013-12-01:*;PREMIUM;5
2013-12-01:*;REGULAR;0
First row says, that for any user registered in period between infinity and 2013-12-01, excluding the end date with account type PREMIUM should get 20% discount. On the other hand, REGULAR account registered during same period will get only 5% discount (and so on).
To wrap it up, whole parameter definition looks like this:
{
name: "discount.loyalty",
inputLevels: 2,
levels: [
{name: "registrationDate", type: "date", matcher: "between/ie"},
{name: "accountType", type: "string"},
{name: "value", type: "integer"}
]
}
registrationDate;accountType;value
*:2013-12-01;PREMIUM;20
*:2013-12-01;REGULAR;5
2013-12-01:*;PREMIUM;5
2013-12-01:*;REGULAR;0
Now we can combine ParamEngine and parameter definition to evaluate the output. This should be done
in DiscountCalculator
class.
public Discount calculateForUser(User user) {
long discountValue = paramEngine.get("discount.loyalty",
user.registrationDate().toDate(),
user.accountType().name()).getLong();
return new Discount(discountValue);
}
We ask paramEngine
to get value from parameter discount.loyalty
. Value to match against patterns of
first input level is user registration date: user.registrationDate().toDate()
. Second level value is
account type: user.accountType().name()
. After evaluating the parameter ParamEngine always returns a
submatrix of parameter, which means that it can have multiple columns and rows. In our case we expect only one
integer value. Call getLong()
to get this value as long and construct Discount
value object.
This is the most basic method of parameter value evaluation. You have to explicitly define each input level
value. It might be useful, but is also very fragile. Imagine change of requirements, you would have to find
all usages of discount.loyalty
parameter and change the invocation. In next tutorial step I will show how to use
SmartParam features to avoid this.
Second parameter, the targeted discount depends on current date and user login. Since the implementation
is more or less the same as of first parameter, treat it as homework. You might want to use DateProvider
to get current date.
Step 2 source code has the implementation in place.
All code can be found in branch step-1-first-parameter
of tutorial project.