Scope Handling: from gCore to common-scope

From Gcube Wiki
Jump to: navigation, search

There is still too much scope-aware code in gCube services. gCore provides facilities for hiding request scope from most code components, but these succeed only partially, and are not widely adopted outside the gHN (e.g. in portlets). Better facilities are now available in a small and fully self-contained library, common-scope. Starting from version 1.6.0, gCore deprecates its support for scope handling and redirects developers towards the facilities in common-scope.

Scope Revisited

gCube services may execute on behalf of different groups of users. Since their execution may leave different traces for different groups, requests must carry an indication of the intended scope of execution. The requirement for 'scoped requests' propagates across chains of service calls, typically starting from an initial user request. For example, a user logs in the portal to operate in the scope of a given group. He then triggers through the GUI a request for a front-end service (portlet), which discovers and calls a back-end service that operates in the same scope. This service may discover and call in turn another back-end service that operates in that scope, and so on until the request can be satisfied. Thus all services have the onus to issue calls in the same scope in which they are invoked.

gCore Facilities

Since its inception, gCore has provided facilities to minimise the burden of request scope handling in service code. The approach is to limit handling only at the 'boundaries' of request processing: when incoming requests are received and when outgoing requests are about to be issued. This is achieved by treating scope as threadlocal information under the control of a GCUBEScopeManager. A service component at one boundary extracts the scope from the incoming request and gives it to a GCUBEScopeManager. Other components at the opposite boundary obtain the 'current scope' from the GCUBEScopeManager (i..e. the scope of the current request) and set it on outgoing requests. Any other component involved in request processing may ignore the current scope.

Inside a gHN, gCore can in fact intercept incoming requests and interact transparently with a GCUBEScopeManager provided by the service. Inside as well as outside the gHN, gCore can also proxy outgoing requests and take care of setting the current scope on them.

New Facilities

gCore facilities do reduce scope handling requirement, but they do not succeed in hiding scope altogether. In particular:

  1. services are still responsible for instantiating a GCUBEScopeManager and for making it available to gCore. Inside a gHN, this is not an issue as the ServiceContext doubles as a GCUBEScopeManager. Outside the gHN, however, the service must still design for it, or else seek support from a different framework. Portlets, for example, rely on the ASL to store and provide scope information as part of the user session. Scope is set on the session by a distinguished portlet that lets user log in to call all other portlets in a given scope. The ASL is also relied upon by plain HTTP servlets, which delegate to it by convention at each call. Remaining within a single set of facilities for handling scope would be desirable.
  2. services must explicitly interact with their GCUBEScopeManager to pass the scope of the current request to gCore proxies, or in fact to any other API built on top such proxies. These include ubiquitous APIs for publication, discovery, and notification, as well as client libraries of individual services.
  3. services must explicit interact with their GCUBEScopeManager to propagate the scope of the current request to any child thread they may use, either directly or indirectly (e.g. through ExecutorServices).
  4. services that use scope handling facilities depend on the entire gCore, hence on its dependencies in turn. The closure of gCore dependencies is very large as it includes the full gHN stack, which is turn made up of ageing libraries. Services outside the gHN thus inherit a much more cumbersome and clash-prone runtime than they really should.

Now:

  • Problem 1. and Problem 2. above are due to the lack a GCUBEScopeManager shared by all services that run within a JVM (more precisely, under the same class loader). If such common GCUBEScopeManager existed, services would not have to define their own and proxies would know where the find the scope of the current request. A shared GCUBEScopeManager was introduced much later (cf. ScopeManager#DEFAULT), but by then most client libraries were written and widely used and only a few new, higher-level libraries could make use of it.
  • Problem 3. is inherent to the thread-local approach, but its impact could be reduced if the GCUBEScopeManager automatically propagated scope information to child threads using dedicated platform facilities (cf. InheritableThreadLocal). Shared thread pools would still require explicit handling from the service, but this could be made more transparent through dedicated facilities.
  • Problem 4. is one of modularity, and puts the blame on the monolithic approach with which gCore facilities are offered across the spectrum of required support.

The problems above have been addressed in a new dedicated library, common-scope, now released with the system and used in new parts of it:

  • common-scope is a small and standalone library, which fixes Problem 4. above.
  • it defines a shared ScopeProvider (cf. ScopeProvider#instance), which solves Problem 1. and Problem 2. above.
  • it uses InheritableThreadLocal and wrapping facilities to bind Runnable and Callable tasks to the current scope prior to submission to Executor Services, which alleviates Problem 3 above.

Note also that common-scope treats scopes as plain Strings (vs. GCUBEScope in gCore facilities).

Migrating Code

Starting with version 1.6.0, gCore deprecates its scope-handling facilities, redirecting clients towards those provided by common-scope. In particular, the following is deprecated:

  • the GCUBEScopeManager interface
  • the default implementation of GCUBEScopeManager, GCUBEScopeManagerImpl
  • the shared GCUBEScopeManager (GCUBEScopeManager#DEFAULT)
  • the methods of GCUBERemotePortTypeContext that take GCUBEScope or GCUBEScopeManager as parameters

Internally, the facilities above have been refactored to use common-scope and may be removed in future versions.

Services and libraries may observe these changes when they access the current scope, i.e.:

  • when creating proxies for outgoing calls.
In this case, they should use the methods of GCUBERemotePortTypeContext that do not expect a scope, e.g. replace code like:
MyStub stub = ….
GCUBEScope currentScope = ..
stub = GCUBERemotePortTypeContext.getProxy(stub,currentScope);

with

MyStub stub = ….
stub = GCUBERemotePortTypeContext.getProxy(stub);
  • when propagating scope to child threads.
If the threads are explicitly created in code, then the propagation can be avoided/removed altogether, e.g. replacing code like:
Thread t = new Thread() {….}
GCUBEScopeManager manager = … //	
manager.setScope(t);
….
t.start();

with:

Thread t = new Thread() {}
….
t.start();
If the threads are instead handled by an ExecutorService, then the tasks submitted to the executor should be wrapped with versions that are bound to the current scope, as shown here for a Runnable task:
import static ………ScopedTasks.*;
...
ExecutorService service = …
...
Runnable task = new Runnable() {}Future<?> future = service.submit(bind(task))

Based on these changes, library and service APIs should avoid explicit provision of scope information.

If services run outside the gHN, then they may also need to change how they set the current scope, e.g. replacing code like:

ScopeManager manager = ….
GCUBEScope scope = ...
manager.setScope(scope);

or later code like:

GCUBEScope scope = ...
ScopeManager.DEFAULT.setScope(scope);

with:

String scope = ...
ScopeProvider.instance.setScope(scope);

Finally, services and libraries that interact directly with the IS will have to bridge with the old model of scope, e.g. replace code like:

ISClient client = …
ISQuery query = …
GCUBEScope scope = ….
client.execute(query, scope);

with:

ISClient client = …
ISQuery query = …
String scope = ….
client.execute(query, GCUBEScope.getScope(scope));

at least until gCore interfaces and implementations for IS-based interactions will not be augmented to use the current scope in ScopeProvider.instance.