Skip to main content

Controlled scope

Introduction

A specific use of Guice custom scopes is presented. You can have many problems trying something similar, so be careful.
The reason to present this post, it to have a reference to explain a similar custom scope used in Service Architecture Model:

Problem: nested passing of parameters

A business operation may be explicitly defined in a context of several high-level context objects. To improve reusability, code is split in several layers and nested method execution pass the context object. In the example below such situation is shown in one class.
public class NestedParamaterPass {

 public void businessOperation(BusinessData data, Person person, Manager manager, Context context){
  // some operation
  firstNestedCall(data, person, manager, context);
 }

 private void firstNestedCall(BusinessData data, Person person, Manager manager, Context context) {
  secondNestedCall(data, person, manager, context);
 }

 private void secondNestedCall(BusinessData data, Person person, Manager manager, Context context) {
  // just keep going and pass all parameters
 }
}
Because this is one class, it may be possible to create private fields and some how synchronize code execution (We don't want to lose thread confinement). Below it is done with ThreadLocal.
public class NestedParamaterPassLocally {

 private ThreadLocal<BusinessData> data;
 private ThreadLocal<Person> person;
 private ThreadLocal<Manager> manager;
 private ThreadLocal<Context> context;

 private void clearBusinessContext() {
  this.data.set(null);
  this.person.set(null);
  this.manager.set(null);
  this.context.set(null);
 }

 private void setupBusinessContext(BusinessData data, Person person, Manager manager, Context context) {
  this.data.set(data);
  this.person.set(person);
  this.manager.set(manager);
  this.context.set(context);
 }

 public void businessOperation(BusinessData data, Person person, Manager manager, Context context){
  setupBusinessContext(data, person, manager, context);

  // some operation
  firstNestedCall();
  
  clearBusinessContext();
 }


 private void firstNestedCall() {
  secondNestedCall();
  BusinessData currentData = data.get();
 }

 private void secondNestedCall() {
  // just keep going

  // How to pass context object to other class?????
  SomeHelperClass helper = new SomeHelperClass();
  helper.passMePleaseTheBusinessObjects();
 }
}
Note that this solution only solve the problem for local methods. To expose the business objects to other classes, it is necessary to some how publish references to the ThreadLocal containers. This is the case of the SomeHelperClass helper class used on secondNestedCall above.

Solution: Publish objects by a controlled scope

The controlled scope approach will allow You to have a implementation of SomeHelperClass as below:
public class SomeHelperClass {
 @Inject
 private Provider<Person> person;
 @Inject
 private Provider<Manager> manager;
 
 public void passMePleaseTheBusinessObjects(){
  Person currentPerson = person.get();
  Manager currentManager = manager.get();
  // make some business operation on Person and Manager only.
 }
}
To make this work, the guice provider must be aware of the ThreadLocal containers used to keep the current context object.A solution is to create a custom scope that will manage the 4 ThreadLocalContainers given above. More generic solutions can be done, but to ilustrate the idea assume the following Guice scope:
public class ControlledScope implements Scope, BusinessContextControl {

 private final ThreadLocal<BusinessData> data = new ThreadLocal<BusinessData>();
 private final ThreadLocal<Person> person = new ThreadLocal<Person>();
 private final ThreadLocal<Manager> manager = new ThreadLocal<Manager>();
 private final ThreadLocal<Context> context = new ThreadLocal<Context>();

 private static class ThreadLocalWrapperProvider<T> implements Provider<T> {
  private final ThreadLocal<T> reference;
  
  public T get() {
   return reference.get();
  }

  private ThreadLocalWrapperProvider(ThreadLocal<T> reference) {
   this.reference = reference;
  }
 }

 @SuppressWarnings("unchecked")
 public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
  if (Key.get(BusinessData.class).equals(key)) {
   return (Provider<T>) new ThreadLocalWrapperProvider<BusinessData>(data);
  } else if (Key.get(Person.class).equals(key)) {
   return (Provider<T>) new ThreadLocalWrapperProvider<Person>(person);
  } else if (Key.get(Manager.class).equals(key)) {
   return (Provider<T>) new ThreadLocalWrapperProvider<Manager>(manager);
  } else if (Key.get(Context.class).equals(key)) {
   return (Provider<T>) new ThreadLocalWrapperProvider<Context>(context);
  } else {
   throw new IllegalArgumentException("This controlled scope is only for specific business object types.");
  }
 }

 public void clearBusinessContext() {
  this.data.set(null);
  this.person.set(null);
  this.manager.set(null);
  this.context.set(null);
 }

 public void setupBusinessContext(BusinessData data, Person person, Manager manager, Context context) {
  this.data.set(data);
  this.person.set(person);
  this.manager.set(manager);
  this.context.set(context);
 }
}
Note the BusinessContextControl interface in the scope implementation. This interface defines a public interface to control the business context:
public interface BusinessContextControl {

 void setupBusinessContext(BusinessData data, Person person, Manager manager, Context context);
 
 void clearBusinessContext();
}
Now the first solution can be simplified to
public class NestedParamaterPassControlled {
 @Inject
 private BusinessContextControl controller;
 @Inject
 private Provider<BusinessData> dataProvider;
 @Inject
 private SomeHelperClass helper;

 public void businessOperation(BusinessData data, Person person, Manager manager, Context context){
  controller.setupBusinessContext(data, person, manager, context);
  // some operation
  firstNestedCall();
  
  controller.clearBusinessContext();
 }


 private void firstNestedCall() {
  secondNestedCall();
  BusinessData currentData = dataProvider.get();
 }

 private void secondNestedCall() {
  // just keep going
  helper.passMePleaseTheBusinessObjects();
 }
}
Now the business code have only two method calls related to context setup, and business objects are available in any nested method call in the thread. Note that helper object is created before the business context is setup, this is ok because SomeHelperClass injects Providers of the business objects.

Guice configuration

To put it all together configure the guice module as follow:
public class ControlledContextModule extends PrivateModule {

 @Override
 protected void configure() {
  ControlledScope scope = new ControlledScope();
  bind(BusinessData.class).in(scope);
  bind(Context.class).in(scope);
  bind(Person.class).in(scope);
  bind(Manager.class).in(scope);
  bind(BusinessContextControl.class).toInstance(scope);
  // additional bindings related to business operation with context given
  // by the controlled scope.
  // Don't forget to expose business API
 }

}

Comments

Popular posts from this blog

Inverse of Control is not Dependency Injection

How to understand the difference between "Dependency Injection" and "Inverse of Control"? Problem lies in lack of examples to "Inverse of Control" different than "Dependency Injection". So, below You can find a simple example to see the difference. On car traffic, each car is "controlled" by a driver. On front of stoplights, cars line up waiting for green light. Drivers accelerate at the sight of green lights, but they do it with delay. This leads to a situation as shown: Applying the Inverse of Control principle, stoplights could take "control" of car's acceleration and execute a synchronized and fast start on green light. So inverse of control for car-semaphore scenario looks like this: How is Dependency Injection related to Inverse of Control?? Well, on Dependency Injection you take the "control" over the new statement from developers to architect. Control on developer looks like this: public clas...

Canonical Protocol

Introduction I will like to show some ideas about the canonical protocol I have defined. In the final example it is shown that such protocol provide features given by OAuth at the protocol level, it means that You can pass external resources without any change on services implementations or additional inter-service integration. RPC integration paradigm When using webservices to integrate remote systems, the development process can be summarized to: Client side -- Prepare request data from context -- make call -- Parse response and interpret result Service provider side -- Parse request -- interpret data to make internal API calls -- Create response Well, not so difficult. But the problem is that all the code used to serialize/parse context information and interpret request/response does not add any value to the system. Note that change from technologies like CORBA to web-services is just a standardization of the binary format used to create/parse the messages. The RPC ...