Basic Concepts

Problem Statement

There might be a situation when your Java service/component business logic is based on some resource located somewhere (file, HTTP, etc). This resource data can also be changed periodically and you need to reflect these changes.

For example, a repository of product categories taking its data from the Http REST endpoint. The most challenging part here is how to keep this service/component in sync with the underlying resource? In IoC containers like Spring, you often operate a single instance of each bean for the whole application lifetime. Using scopes and writing boilerplate code to do the reload dynamically isn't easy, nor desired.

Solution

Ressor takes over all the logic related to loading, parsing resources and creating your service instances each time it changes. ‌

It generates a proxy instance of your service type. Under the hood, this proxy delegates all method calls to the most up-to-date instance of your service/component. To create it, it loads data from the provided source, parse it using translators and calls the constructor/factory method of your class.

We already implemented the most popular sources and translators. Nevertheless, we plan to continuously increase that number. ‌

We also provide various reload strategies, such as polling or subscribing to the change events.

Sample API

It all starts with such call:

var ressor = Ressor.create();

Please try to re-use this instance whenever possible, since its creation is quite resource consuming.

Then we start building a service instance, of, for example, some YourService class:

var builder = Ressor.service(YourService.class);

It returns a fluent API builder. The next step is to select the data format which will be taken from resource and translated. For example, our YourService is built from YAML formatted source. What is meant by 'built' is depends on the context and will be covered below. We define the desired source format:

builder.translator(yaml())

That's it. Now Ressor will always provide the Jackson JsonNode instance to the YourService with all the data. By the way, you can always implement your own translator and provide it with such call:

builder.translator(myTranslator)

The next step is to select the source. For example, our service takes the YAML data from the REST endpoint:

builder.source(Http.source())
.resource(Http.url("https://api.example.com/prices.yaml"))

That's it. The latter stage depends on what YourService is actually is - interface or class.

Interface

In that case, Ressor needs to have an actual implementation of it which you're going to use. For example, you have a YourServiceImpl class, which has a YourServiceImpl(JsonNode) constructor. Then it's as easy as:

builder.factory(jsonNode -> new YourServiceImpl(jsonNode))

The factory can be anything else, probably more complex logic.

Class

By default, you can just do nothing. In that case, Ressor will find the appropriate constructor by itself, with respect to the selected source.

You can also mark the desired constructor with @ServiceFactory annotation, which will tell Ressor to always use it.

Finally, as in the case of interface, you can provide the factory lambda, the same as with interface case:

builder.factory(jsonNode -> new MyService(jsonNode));

Initial Instance

Before the first load of your resource, you'll probably want your service instance to return something. For that, provide the initial/default instance:

builder.initialInstance(emptyYouService)

It will be used before the first load occurs. This makes sense only if you required asynchronous initial load explicitly:

builder.asyncInitialReload()

Now your ready to finish:

var yourServiceInstance = builder.build();

The build() call assembles the special proxy class, inherits it from YourService and performs the initial load from the provided source. You are ready to use yourServiceInstance everywhere you need.

Reloading the service

What if your source is changing with the new data over time? In our example, the prices from https://api.example.com/prices.yaml are being updated quite regularly.

In that case, you can ask Ressor to poll for the changes. For example, check new prices every 5 seconds:

ressor.poll(yourServiceInstance).every(5, SECONDS);

When the stale data will be detected, the new version of a resource will be loaded, translated and YourService instance will be created. Under the hood, the yourServiceInstance will switch to use it, completely transparently for you. So, eventually, you will start using the new data with the same instance.

You can always stop it by:

ressor.stop(yourServiceInstance);

or switch to use the Cron schedule:

ressor.poll(yourServiceInstance).cron("* * * * * ? *");