-1

I'm dealing with a design problem. Actually I have the following architecture:

A Process interface :

public interface Processor {

    void applyNewConfiguration(final Configuration conf);

    Result process();
}

A processor MyProcessor which implements this interface :

public class MyProcessor implements Processor {

    @Override
    public void applyNewConfiguration(Configuration conf) {
        // Manage configuration which can take a long time
        // Scan dir, fetch BIG files, etc.      
    }

    @Override
    public Result process() {
        // Process regarding the configuration
    }
}

And a Manager which trigger the process method of each Processor when needed :

public class ProcessManager {

    public void execute() {

        //  Eventually apply new configuration to a processor

        // Call process method of Processor
    }
}

I explain this : actually, on application startup, all processors are loaded and a configuration is applied. Loading the configuration can be very long and does not change so often so I did not want to recreate a Processor each time I want to make it process.

So I'm juste creating a single instance of each processor and when needed, I apply a new configuration and then call the process method as usual.

To me, it's a bad design as I'm changing the behaviour of my processors while they should be Stateless, but I do not known how to deal with that? If I called the process method while the configuration is being applied, behaviour will be dramatic....

I could of course have a constructor in each processor with the configuration as parameter, and re-create a processor each time, but the configuration management is a very long process so I do not want to do that.

I was thinking about having a separate class (maybe ConfigurationManagement) which can handle each Configuration object, process it and associate it to a Pojo (for example in a Map<Configuration,Pojo>) but as soon as a new Configuration will be applied, previous one will not be garbage collected.

Do you have any idea how to solve this problem? It seems quite simple but I can't find any good solution.

2

Caching is a trade off in favor of performance while costing increased code complexity and memory usage.

When we cache things we have to consider: when to invalidate the cache. It is one of the hard problems in computer science.

As you are realizing, if you never invalidate the cache entries, the space taken by the cache will never decrease. If it reaches a maximum by loading all the configurations, that may be a lot of memory but that may be ok. However, if, in your domain, configurations are added, even if only slowly over time, as new configurations are seen and cached, then you have the potential for memory leak problems.

The other problem with cache invalidation is what if the disc-based configuration changes? Your cached copy will be stale at best, or wrong at worst. So, if this an issue in your domain, now that you're caching you have to either notice or be told when the disc-based configuration changes. This an area where we see the cost of code complexity.

(NB: If the configuration involves multiple files, and they are changed individually, you would also want a way to ensure that you see only whole changes, not an incomplete update, but that is a problem you would have regardless of caching.)

So, what we do in caches is

  1. set up notification of changes about the original entity, and invalidate the cache on notification. In some domains, we will immediately reload the cache for that entity, and in others well will be lazy and reload on the next demand. (Depending on the (network) distance between the original and the cached value we may still have the potential of working with just marginally stale entities, so maybe carry metadata so the consumer can consider or request a particular version or timestamp.)

  2. and/or check to see if the configuration is still valid on each use of a cached entry.

  3. age the cache entries. There are various aging algorithms, and they can be difficult to get right. Generally speaking, we don't want to evict an entity from the cache if it is currently in use. So, you need to keep track of cache entity usage. Beyond that you might age entries after a certain amount of time without use. Or age entities on a threshold of memory consumption (so you would need to track entity size and total cache size). Of course, some of this aging involves potential race conditions, especially if your timer callbacks happen asynchronously.

  • Upvote for the really good explanation about caches, really appreciate. – Rouliboy Feb 11 '18 at 10:00
1

You say:

To me, it's a bad design as I'm changing the behaviour of my processors while they should be Stateless, but I do not known how to deal with that?

You make something stateless by separating out the state from the actions that occur on the state. Then you pass the state into the code which performs the actions. Perhaps you haven't separated things as cleanly as you could? Instead of having a Processor which contains a Configuration and then has its process() method called after setting the configuration, can you make it simply have just a process() method (or make it a free function if your language allows that), and you pass in a Configuration?

Loading the configuration can be very long and does not change so often so I did not want to recreate a Processor each time I want to make it process.

Are there ways you could improve this? For example, do you need to load the entire configuration every time? Could much of it be copied from an existing instance, and only a the parts that are different be updated? If you were to give the details of what "loading a configuration" involves (in another question), readers might have suggestions for how to improve its performance. Could often-used Configurations be serialized to disk and then read back in so you don't have to do the setup every time? Could Configurations be built up from more basic Configurations or other pieces to reduce the overhead of creating new ones?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.