Sunday 23 April 2017

Various Proxy Design Pattern implementation variants in Java, ABAP and JavaScript

This blog gives an introduction about various proxy design pattern implementation variant in Java and ABAP.

Below paragraph is quoted directly from Wikipedia:

“A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. In short, a proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. For the client, usage of a proxy object is similar to using the real object, because both implement the same interface.“

The UML diagram for Proxy Design pattern:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

I will first introduce various proxy implementation approaches in Java and then research whether these approaches could be simulated in ABAP as well.

Java Proxy Design pattern – static proxy


The example is very simple: an interface IDeveloper with only one method writeCode:
public interface IDeveloper {
public void writeCode();
}
// An implementation class for this interface:
public class Developer implements IDeveloper{
private String name;
public Developer(String name){
this.name = name;
}
@Override
public void writeCode() {
System.out.println("Developer " + name + " writes code");
}
}

test code:

public class DeveloperTest {
public static void main(String[] args) {
IDeveloper jerry = new Developer("Jerry");
jerry.writeCode();
}
}

Test output:

Developer Jerry writes code

Now the trouble is, Jerry’s project lead is not satisfied with the fact that the developers in the team like Jerry write code without any documentation. After discussion the whole team come to an agreement that the corresponding documentation must always be available together with the code.
In order to force the developer to write documentation without directly making modification on the existing implementation class Developer, now the static proxy comes on stage:
public class DeveloperProxy implements IDeveloper{
private IDeveloper developer;
public DeveloperProxy(IDeveloper developer){
this.developer = developer;
}
@Override
public void writeCode() {
System.out.println("Write documentation...");
this.developer.writeCode();
}
}

Pros of static proxy


Suppose you would like to enhance the existing STABLE implementation against an interface without modification on itself, you can create a new proxy which implements the same interface and wrap the original implementation as a private attribute within the proxy. The enhancement is done in proxy implementation which is completely transparent to the consumer code. Back to the Developer example above, the consumer code does not care whether the variable it uses to call writeCode method points to a real developer or a developer proxy at all.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

The advantage of static proxy:
1. easy to implement and understand
2. the relationship between original implementation and its proxy is early determined in compilation time. No additional overhead in runtime execution.

Cons of static proxy


I still use the example to illustrate the drawback of static proxy.
Suppose now the missing documentation issue also persists in QA colleagues. If we would like to still fix the issue via static proxy, it is inevitable that another proxy class for Tester has to be introduced.
This is interface for Tester:

public interface ITester {
public void doTesting();
}
Original tester implementation class:
public class Tester implements ITester {
private String name;
public Tester(String name){
this.name = name;
}
@Override
public void doTesting() {
System.out.println("Tester " + name + " is testing code");
}
}

Proxy for Tester:

public class TesterProxy implements ITester{
private ITester tester;
public TesterProxy(ITester tester){
this.tester = tester;
}
@Override
public void doTesting() {
System.out.println("Tester is preparing test documentation...");
tester.doTesting();
}
}

Test code and output:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

From the source code of Tester proxy it is easily observed that it has exactly the same logic as Developer proxy. If some time later we have to build the documentation gap for other roles in software delivery process, we have to introduce new static proxy class again and again, which leads to the inflation of static proxy class number and a violation of DRY – Don’t Repeat Yourself.

Dynamic proxy in Java – Variant1: InvocationHandler


Instead of having dedicated static proxy class for each original implementation class, now I use a common proxy class EnginnerProxy to serve all concrete roles.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class EnginnerProxy implements InvocationHandler
{
    Object obj;
    public Object bind(Object obj)
    {
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
                .getClass().getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable
    {
        System.out.println("Enginner writes document");
        Object res = method.invoke(obj, args);
        return res;
    }
}

Key notes


1. Instead of inheriting from dedicated interface with business interface ( IDeveloper or ITester ), in this variant the generic proxy inherites from a technical interface InvocationHandler provided by JDK.
2. In order to ensure the generic proxy can work for all possible concrete implementation classes, a variable with generic type Object is defined within the proxy.
3. When the interface method of a proxyed instance is called, it is intercepted by invoke method defined in InvocationHandler, where the enhanced logic declared by application developer is called together with the original logic called by Java Reflection.
Here below is the code how Dynamic proxy designed by InvocationHandler is consumed and test output:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Limitation of dynamic proxy Variant 1: Invocation handler


Although this variant successfully avoids the duplication disadvantage in static proxy, it still has limitation that it fails to work with implementation class which is not inherited from an interface.
Consider the following example. The product owner does not implement any interface:
public class ProductOwner {
private String name;
public ProductOwner(String name){
this.name = name;
}
public void defineBackLog(){
System.out.println("PO: " + name + " defines Backlog.");
}
}

The following code does not have syntax error in compilation time:

ProductOwner po = new ProductOwner("Ross");
ProductOwner poProxy = (ProductOwner) new EnginnerProxy().bind(po);
poProxy.defineBackLog();

runtime exceptions unfortunately:

Dynamic proxy in Java – Variant2: Proxy using CGLIB


CGLIB is a Byte Code Generation Library which provides high level API to generate and transform Java byte code. It is used by AOP, testing, data access frameworks to generate dynamic proxy objects and intercept field access.Check its detail documentation and sample code from github.
Now I will create proxy class for ProductOwner class which does not implement any interface using CGLIB.

The new common proxy implemented via CGLIB API:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class EnginnerCGLibProxy {
Object obj;
    public Object bind(final Object target)
    {
        this.obj = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(obj.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args,
                    MethodProxy proxy) throws Throwable
            {
                System.out.println("Enginner 2 writes document");
                Object res = method.invoke(target, args);
                return res;
            }
        });
        return enhancer.create();
    }
}

Consumer code:

ProductOwner ross = new ProductOwner("Ross");
ProductOwner rossProxy = (ProductOwner) new EnginnerCGLibProxy().bind(ross);
rossProxy.defineBackLog();

From test result we can see clearly that even the implementation class ProductOwner does not implement any interface, still its public method defineBackLog is successfully proxyed.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Limitation of Dynamic proxy in Java – Variant2: Proxy using CGLIB


The magic of CGLIB has already been explained in my blog Simulate Mockito in ABAP. The dynamic proxy created by CGLIB is actually a transient subclass of original class being proxyed. This dynamic creation feature is so powerful that are widely used in many Java Framework such as Mockito to mock test object and AOP in Spring.

On the other hand the limitation of this variant is also apparent due to its implementation approach based on inheritance. I just now mark ProductOwner class as final, after this change CGLIB proxy approach does not work any more: it only works for a class which is not marked as final.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Dynamic proxy in Java – Variant3: Create Proxy class dynamically via compiler API


The left class ProductOwner below is a final class which DOES NOT implement any interface. Suppose I would like to create a new proxy class on top of it which prints out a documentation preparation log before the real operation of backlog definition is executed.
As described in previous two variants, this requirement could not be fulfilled by either variant 1 or 2.
As the last solution, I have to create a completely new class dynamically.
Suppose I can get the source code of original class ProductOwner via file API in JDK, here for simplification reason I hard code the source code in method getSourceCode. Pay attention to line 35 highlighted: where the proxy logic to print documentation work is written.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Consumer code and test output: it works as expected.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

And the dynamic created Java file is immediately visible in your IDE:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

The whole magic is in method getProxyClass().

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

The dynamic new class generation in this variant 3 example consists of three steps:

1. fill the source code for new Java class assembled from method getSourceCode into a new local file named ProductOwnerSCProxy.java ( SCProxy means SourceCode Proxy ). For simplification reason again I use the absolute file path.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

2. Compile this dynamically filled Java file via JavaCompiler API:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

3. Load the compiled class via ClassLoader. Once done, the class object is ready to be used in various reflection API.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

This variant is the least recommended solution as it will cause great security and performance trouble.
So far the static proxy and three kinds of dynamic proxy pattern implementation in Java are introduced. All the related source code of examples used in this blog could be found from here.

Proxy Pattern in ABAP


Static proxy pattern is also extensively used in variant CRM application or test framework. 
As the names of implementation classes give a very clear hint, CL_CRM_PRODUCT_MOCK_PROXY contains the mock logic while CL_CRM_PRODUCT_REAL_PROXY is responsible for the real productive logic.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Dynamic Proxy pattern in ABAP – variant 1


The proxy class created via this variant is transient, which means the class is only available in the current runtime session. It behaves something similar as the CGLIB variant introduced in Java part.
For detail implementation steps, please refer to these two blogs:

Dynamic Proxy pattern in ABAP – variant 2


In previous chapter Dynamic proxy in Java – Variant3: Create Proxy class dynamically via compiler API, we see the possibility to create complete new class in Java via API. In ABAP again we have similar utility API for example function module SEO_CLASS_CREATE_COMPLETE could be used to create class with persistence based on source code specified by application developer.
A detail description about how to achieve this variant could be found from blog Create dynamic proxy persistently in Java and ABAP

No comments:

Post a Comment