3

I have a service, call it Service A, that is built on a Storage Layer, call it DB, that is 5-6 levels deep. This layer is depended upon by many other services.

Service A is having memory issues and the fix is to throw an exception from DB if the resource we are trying to fetch is too large for Service A. The resource itself is fetched in multiple roundtrips so we are counting the number of roundtrips and it is too high, we throw and abort and try a different, slower, more expensive code path.

The problem is this limit is specific to Service A. All other services that depend on DB do not need this limit.

Solution 1: read global setting set by Service

FetchResource(...) {
    if (numberOfRoundTrips > ServiceDefinedLimit) {
        throw new Exception("Resource is too large");
    }
}

Right now the limit is Infinity for all services but Service A, which seems sketchy. Otherwise it is not conceptually different from setting some config setting that says "ThrowIfResourceTooLarge".

Solution 2: pass in a boolean

FetchResource(..., bool throwIfSizeTooLarge) {
    if (numberOfRoundTrips > HardCodedThreshold && throwIfSizetooLarge) {
        throw new Exception("Resource is too large");
    }
}

The problem here is that it just seems like a more complex version of Solution 1. In addition, I have to modify every layer from the caller all the way to this function.

Solution 3: Pass in a function

FetchResource(..., Func<State> condition) {
    if (condition(numberOfRoundTrips, ...)) {
        throw new Exception("Resource is too large");
    }
}

The problem with the third approach is, once again, modifying all the layers above it to pipe this value from the caller to this layer. It seems conceptually right to me.

Looking for people's feedback on which would be the best approach. The more I think about it, the more #1 seems correct, and #3 seems like nice to have but too much work to implement now.

3 Answers 3

5

The problem is this limit is specific to Service A. All other services that depend on DB do not need this limit.

Solution P: Abstract away the problem

Resource r = service.fetchResource();    

Don't expect using code to deal with your little problems. Don't even ask it to know what it's talking to.

class ServiceA implements Service  {
    public Resource FetchResource(...) {
        if (numberOfRoundTrips > ServiceDefinedLimit) {
            throw new Exception("Resource is too large");
        }          
        return DB.resource("A");  
    }
}

Don't expect other services to know anything about your weird problems either.

class ServiceB implements Service {
    public Resource FetchResource(...) {
        //what round trips?
        return DB.resource("B");
    }
}
0

I have three thoughts regarding this design.

  1. Does the caller really know or care if that particular service has a size limit? Is it supposed to know about that? Seems to be you may be violating separation of concerns. Especially if there are really 5 layers to go through.

  2. I'm a big fan of limiting flexibility when the flexibility exceeds system constraints. What is the point of passing this flag or callback as an input parameter, if it's the same every time? All you're doing is quizzing your developer to see if he knows the magic code to put between the parentheses-- the flexibility is not actually needed or used-- and if he fails the quiz, the system crashes.

  3. What happens when you add memory to the server? Do you want to go back and change a bunch of code? What if you only added it on certain servers?

For those reasons, I'd suggest either

Option #1. Make it a configurable size threshold that is set in configuration file for the layer closest to the data/size issue.

Option #4. Make it a constructor parameter of the service itself, not the method. Set it in your initialization routine, e.g. when you set up your Inversion of Control container or whatever it is you are using. And drive the initialization with configuration, so you can make it specific to a particular instance of the application.

0

This is one of the areas where C++ language features make it such a much superior choice to Java (which it appears you maybe using, but you didn't say).

The best way I know to configure a feature of a component which is 'distant' (not directly called by) from one of your modules, but used differently by other modules, is with thread_local configuration variables. By setting thread_local configuration variables, you don't need to pollute your interfaces and intervening code with configuration information being passed down through layers of code, and you can still have distant code configure other distant, indirectly used modules (differently from different callers).

THIS - the thread_local static variable - you can easily do in Java as well. Where C++ shines, (as usual) - is with RAII (https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization). In C++, you can declare

struct StorageLayer {...
    static thread_local optional<size_t> sMaxResourceSize;
    ....
};

struct StorageLayer::AdjustMaxResourceSize {
     optional<size_t> prev;
     AdjustMaxResourceSize (size_t sz) 
       : prev (sMaxResourceSize)
         {
           sMaxResourceSize = sz;
         }
     ~AdjustMaxResourceSize () 
      {
           sMaxResourceSize = prev;
      }
      // no assign/no copy CTOR etc...
};

and then in your code, just declare:

StorageLayer::AdjustMaxResourceSize adjustMaxSizeForServiceA { 300 };
...
rest of code which operates with this constraint, doesn't directly call StorageLayer
but does INDIRECTLY call it
...

The only way I know to do something similar in Java is with try/finally blocks to save/restore the configuration value, and that would be highly duplicative.

An example usage of this tecnhique in in real life (Stroika) would be suppressing thread interruption:

and then example usage:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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