Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Modern frameworks such as gRPC and Apache Thrift provide a very [1] provide flexible API for implementing request interceptors, with which you can solve almost any middleware task.

...

  1. Ability to pass custom context from caller to service (similar to HTTP request headers).
  2. Ability to define custom interceptors for service calls.

Public API

New public API entities

  • ServiceCallContext -  immutable

    map of custom parameters to

    user parameter map that can be implicitly passed to the service

    .
  • ServiceCallInterceptor  - intercepts service method calls.
  • ServiceInterceptorContext -  extended mutable version of caller context (interceptor obtains method call parameters from it and can use it to update the caller context).
  • ServiceInterceptException - unchecked exception that is used to highlight the exception that occurred during method interception (not execution).

Java API

...

  • (and interceptor) on every method call.

    Code Block
    languagejava
    themeRDark
    title

...

  • ServiceCallContext.java
  • collapsetrue
    public interface 

...

  • ServiceCallContext 

...

  • {
        public 

...

  • String 

...

  • attribute(

...

  • String 

...

  • name);
    
        public byte[] 

...

  • binaryAttribute(String name);
    }


  • ServiceCallInterceptor  - intercepts service method calls.

    Code Block
    languagejava
    themeRDark
    titleServiceCallInterceptor.java
    collapsetrue
    public interface ServiceCallInterceptor extends Serializable {
        

...

  • /**
     

...

  •  

...

  •  

...

  •  

...

  •  

...

  • * 

...

  • Intercepts 

...

  • a 

...

  • call 

...

  • to 

...

  • a 

...

  • service method.
        

...

  •  *
    

...

  •     

...

  •  

...

  • * 

...

  • @param 

...

  • mtd Method name.
         * @param args 

...

  • Method arguments.
    

...

  •  

...

languagejava
themeRDark
titleServiceCallContext
linenumberstrue
collapsetrue

...

  •  

...

  •  

...

  •  

...

  •  * @param ctx 

...

  • Service 

...

  • context.
         * 

...

  • @param 

...

languagejava
themeRDark
titleServiceInterceptorContext
linenumberstrue
collapsetrue

...

  • call Delegated call.
         * @return Service method call result.
        

...

  •  */
        public 

...

  • Object 

...

  • invoke(String 

...

  • mtd, 

...

  • Object[] args, ServiceContext ctx, Callable<Object> call) throws Exception;
    }


Usage example

...

draw.io Diagram
bordertrue
diagramNamemiddleware
simpleViewerfalse
widthlinksauto
tbstyletop
lboxtrue
diagramWidth1001
revision13

Code

Code Block
languagejava
themeRDark
titleexample usage in Java
linenumberstrue
Example.java
collapsetrue
        ServiceCallInterceptor security = new ServiceCallInterceptor() {
            @Override public void onInvoke(ServiceInterceptorContext ctx) throws ServiceInterceptException mtd, args, ctx, svcCall) -> {
                // Check permission before execution of the method.
                if (!CustomSecurityProvider.get().access(mtd, ctx.methodcurrentCallContext(), ctx.attribute("sessionId")))
                    throw new SecurityException("Method invocation is not permitted");

        // Execute remaining interceptors and }service method.
        }

return svcCall.call();
    };

    ServiceCallInterceptor audit = new ServiceCallInterceptor() {
    mtd, args, ctx, svcCall) -> {
        @OverrideString publicsessionId void onInvoke(ServiceInterceptorContext ctx) {
= ctx.currentCallContext().attribute("sessionId");
        AuditProvider prov = AuditProvider.get();

        // Record an event before execution of the method.
                AuditProvider.get()prov.recordStartEvent(ctx.methodname(), ctx.attribute("sessionId"))mtd, sessionId);

        try    }
{
            @Override// publicExecute void onComplete(@Nullable Object res, ServiceInterceptorContext ctx) {service method.
                AuditProvider.get().recordFinishEvent(ctx.method(), ctx.attribute("sessionId"))svcCall.call();
        }
    }

    catch (Exception e) {
     @Override public void onError(Throwable err, ServiceInterceptorContext ctx) {
// Record error.
              AuditProvider.get()prov.recordError(ctx.methodname(), ctx.attribute("sessionId"mtd, sessionId), erre.getMessage());

            // Re-throw exception to initiator.
            throw }e;
        }
        finally {
            // Set context parameters for Record finish event after execution of the service proxymethod.
         ServiceCallContext ctx = ServiceCallContextprov.builderrecordFinishEvent()ctx.put("sessionId"name(), mtd, sessionId).build();
        }
    }

    ServiceConfiguration svcCfg = new ServiceConfiguration()
            .setName("service")
            .setService(new MyServiceImpl())
            .setMaxPerNodeCount(1)
            .setInterceptors(security, audit);

    // Deploy service.
    ignite.services().deploy(svcCfg);

    // DeploySet context parameters for the service proxy.
    ServiceCallContext callCtx   ignite.services().deploy(svcCfg= ServiceCallContext.builder().put("sessionId", sessionId).build();

    // Make service proxy.
    MyService proxy = ignite.services().serviceProxy("service", MyService.class, false, ctxcallCtx, 0);

        // A business method call to be intercepted.
        proxy.placeOrder(order1);
        proxy.placeOrder(order2);  

Implementation details

...

Interceptor can modify ServiceCallContext, but Service methods can only read it.

If an interceptor has been specified, but the user has not passed the caller context through the proxy, it will be created dynamically.

Deployment

One service can have several interceptors. They are defined using the service configuration and deployed with the service.

To add/remove interceptor service should be redeployed.

Interceptor is located and executed where the service is implemented (for Java service - on Java side, for .NET-service on .NET side without any ). Its execution should not cause additional serialization).

To add/remove interceptor - service should be redeployed.

Resource injection and lifecycle

Interceptor must support ignite instance resource injection.

(question) Interceptor should be LifeCycleAware

Exception handling

// todo  ServiceInterceptException

(question) If an interceptor throws an exception, then processing is aborted, but the exception is passed to all listed interceptors (onError)

Passing Context in the Service Chain

// todo explain forwardCallerContext

Limitations

...

.

Invocation order

The user can specify multiple interceptors.Each interceptor invokes the next interceptor in the chain using a delegated call, the last interceptor will call the service method.

So the interceptor specified first in the configuration will process the result of the service method execution last.

draw.io Diagram
bordertrue
diagramNameinvocation
simpleViewerfalse
width
linksauto
tbstyletop
lboxtrue
diagramWidth471
revision3

Resource injection

Interceptor must support the injection of generic resources.

Interception scope

Interceptor does not apply to service lifecycle methods - init, execute andcancel,

Service call context

The user can create context (map with custom parameters) and bind it to the service proxy. After that, each call to the proxy method will also implicitly pass context parameters to the service.

Service method can read current context parameters using ServiceContext#currentCallContext method. It is only accessible from the current thread during the execution of a service method.

If one service calls another, then by default the current call context will not be bound to the created proxy - the user must explicitly bind it. But Java service has a special ServiceResource annotation to inject another service proxy into the current service. If the user wants to redirect the current call context to this (injected) proxy, he can set the forwardCallerContext option of this annotation.

Exception handling

Exception thrown by the interceptor will be wrapped into unchecked IgniteException and passed to the initiator.

Risks and Assumptions

The interceptor gives the user full control over the invocation of the service methods, so in case of implementation errors, the user may get unexpected behavior of the service.

Discussion Links

https://lists.apache.org/thread.html/r4236c1f23e524dc969bc55057467a2bbe7f9a59a6db7c7fcdc1b7d37%40%3Cdev.ignite.apache.org%3E

Reference Links

[1] https://pkg.go.dev/github.com/grpc-ecosystem/go-grpc-middleware

Tickets

Jira
serverASF JIRA
columnIdsissuekey,summary,issuetype,created,updated,duedate,assignee,reporter,priority,status,resolution
columnskey,summary,type,created,updated,due,assignee,reporter,priority,status,resolution
maximumIssues20
jqlQuerylabels = IEP-79 order by fixVersion
serverId5aa69414-a9e9-3523-82ec-879b028fb15b