Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Integrating Microservices With a Monolithic Application

DZone's Guide to

Integrating Microservices With a Monolithic Application

Learn about integrating microservices with your existing monolithic application while making minimal changes to the code, to avoid affecting users.

· Microservices Zone ·
Free Resource

Learn how modern cloud architectures use of microservices has many advantages and enables developers to deliver business software in a CI/CD way.

Problem Statement

The technical requirement is to weave a microservice with a non-Spring monolith service layer.

Monolith modules expose SOAP-based web services via WebService facade classes, which are an internally invoking method of different service classes (e.g. StoreService,  ProductServicePaymentServiceetc.). These web services return essential responses with the help of different utils, application services, and other business services (there is inter-communication between multiple business services). In the roadmap, we have a requirement to convert these legacy services into microservices. The associated necessity is to integrate these microservices with the monolithic backbone without impacting the consumer, and with minimal changes to the monolith’s existing code base.

High-Level Implementation Details

As per the best practice, to break a monolith into a microservice based architecture, first, I identified a candidate piece of functionality (e.g.  PaymentServiceImpl:getCustomerPanInfo() ); second, I created a microservice for it. Now it’s time to integrate the microservice with the monolith’s façade. At this point, I have deviated a little from the best practice steps, as we already have a façade for SOAP services (OnlineBusinessService), which is accessed by different tenants (Web, Mobile, etc.). I have not introduced any separate façade for microservices, and correspondingly, there is no on-the-fly toggler for the incoming traffic. Instead, I have implemented an aspect-based compile time toggler. Once the developer is done developing the microservice for a particular functionality, he/she can annotate the service in the monolith to instruct the system to toggle to the corresponding microservice. Also, there is the option of a fallback method, which can enable the system to fall back into an alternate execution flow if a microservice is not available. The fallback method can be a separate tracking method or can be an existing legacy service.

Image title

Implementation Details

To realize the above approach, I have introduced the following components:

  • An annotation class: com.abccorp.online.business.annotations.MicroServiceWeaver 
  • An aspect class: com.abccorp.online.business.aspect.MicroServiceWeaverAspect 
  • Inside the monolith module, a  resources folder and an aspect configuration file  aop.xml 
  • The below pom dependencies and plugins in the monolith
  • Dependency

    Plugin

    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.13</version>
    
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
    
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.5-jre</version>



    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.9</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.8.13</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
    </configuration>


    To weave aspects, I need to state  aspectjweaver-1.8.13.jar  as a Java agent and weaving mode as compile in VM options:

     -javaagent:"<Project path>\lib\aspectjweaver-1.8.13.jar" -DweavingMode=compile 

    Currently, I provide an absolute path in the VM option. Going forward, we have to pass this file in a relative manner.

    com.abccorp.online.business.annotations.MicroServiceWeaver

    A Java annotation with three parameters:

    •  serviceClass: The full class name, which contains the microservice caller (msMethod) and fallback (fallbackMethod) method. It is an optional parameter. By default, it is same as the class where the actual legacy service has been defined.

    •  msMethod: The method to invoke a microservice.

    •  fallbackMethod: The fallback method which will be invoked if a microservice invocation returns InvocationTargetException.

    com.abccorp.online.business.aspect.MicroServiceWeaverAspect

    This is an actual aspect implementation. This class has a single pointcut which is implemented as an around (@Around) aspect. If any method annotated with the MicroServiceWeaver annotation instruction pointer first enters into this pointcut and try to invoke the microservice caller method defined as  msMethod and return the corresponding response. If during the microservice invocation, the system receives a InvocationTargetExceptionit will invoke fallbackMethod.

    aop.xml

    This configuration file declares the aspects and weaver. In this proof of concept, the only aspect is com.abccorp.online.business.aspect.MicroServiceWeaverAspectand the below packages are woven by the weaver.

    •  com.abccorp.online.business.services..* 
    •  com.abccorp.online.business.aspect.* 

    Annotating Monolith Services

    I chose the following service methods to test this implementation:

    •  com.abccorp.online.business.services.Echo:echo() 
    •  com.abccorp.online.business.services.StoreService:getStoreImpl() 

    Why Not Hystrix for the Fallback?

    I have found that HystrixCommandAspect has a bug when invoking a static method, and most of our monolith service class's methods are static. There is a class called  AopUtils  inside hystrix-javanica which extracts the fallbackMethod method dynamically by invoking the  joinPoint.getTarget() method using a Java reflection. For static methods, this invocation is returning null , i.e. why I am getting  NullPointerException . To get rid of this issue, we have to use the signature.getDeclaringType() method to fetch the fallback method name dynamically. It was fixed in issue # 1631 but is not yet available in the repository. I tried with the latest version of hystrix-javanica (i.e. 1.5.13) but the bug is present there.

    Note: For non-static service methods, HystrixCommandAspect will work fine to implement the fallback functionality.

    All the implementations are in the appendix below.

    Appendix

    com.abccorp.online.business.annotations.MicroServiceWeaver

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface MicroServiceWeaver {
      String   serviceClass() default "";
      String   msMethod() default "";
      String fallbackMethod() default "";
    }


    com.abccorp.online.business.aspect.MicroServiceWeaverAspect

    @Aspect
    public class MicroServiceWeaverAspect {
      private static Logger logger = Logger.getLogger(MicroServiceWeaverAspect.class);
      @Pointcut("@annotation(com.abccorp.online.business.annotations.MicroServiceWeaver)")
      public void microServiceWeaverPointcut() {
      }
    
      @Around("microServiceWeaverPointcut()")
      public Object microServiceWeaverAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnObject = null;
        try {
          logger.debug("MicroServiceWeaverAspect:microServiceWeaverPointcut - entry into aroundAdvice");
          returnObject = invokeMethod(joinPoint);
        } catch (Throwable throwable) {
          throw throwable;
        } finally {
          logger.debug("MicroServiceWeaverAspect:microServiceWeaverPointcut - exit from aroundAdvice");
        }
        return returnObject;
      }
      private Object invokeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnObject = null;
        Signature signature = joinPoint.getSignature();
        Object obj = null;
        Method method = null;
        if (signature instanceof MethodSignature) {
          MethodSignature methodSignature = (MethodSignature) signature;
          String methodName = methodSignature.getMethod().getName();
          Class<?>[] parameterTypes = methodSignature.getMethod().getParameterTypes();
          Annotation[] annotations = methodSignature.getMethod().getAnnotations();
          Class<MicroServiceWeaver> microServiceWeaverClass = MicroServiceWeaver.class;
    
          for (Annotation annotation : annotations) {
            if (microServiceWeaverClass.isAssignableFrom(annotation.annotationType())) {
              Class cls;
              if (((MicroServiceWeaver) annotation).serviceClass().isEmpty()){
                cls = signature.getDeclaringType();
              }else {
                cls = Class.forName(((MicroServiceWeaver) annotation).serviceClass());
              }
              obj = cls.newInstance();
              method = cls.getDeclaredMethod(((MicroServiceWeaver) annotation).msMethod(), parameterTypes);
              try {
                returnObject = method.invoke(obj, joinPoint.getArgs());
              }catch (InvocationTargetException ite){
                String fallbackMethodName = ((MicroServiceWeaver) annotation).fallbackMethod();
                if (fallbackMethodName.contentEquals(methodName)){
                  returnObject = joinPoint.proceed();
                }else {
                  method = cls.getDeclaredMethod(((MicroServiceWeaver) annotation).fallbackMethod(), parameterTypes);
                  returnObject = method.invoke(obj, joinPoint.getArgs());
                }
              }
            }
            break;
          }
        }
        return returnObject;
      }
    }


    aop.xml

    <aspectj>
    <weaver options="-verbose -showWeaveInfo -debug">
        <include within="com.abccorp.online.business.services..*" />
      <include within="com.abccorp.online.business.aspect.*"/>
      </weaver>
      <aspects>
      <aspect name="com.abccorp.online.business.aspect.MicroServiceWeaverAspect"/>
      </aspects>
     </aspectj>


    com.abccorp.online.business.services.Echo:echo()

    public EchoResponse echo2(String ping) {
        RestTemplate restTemplate = new RestTemplate();
        URI uri = URI.create("http://localhost:2222/payment/types");
        String payload = restTemplate.getForObject(uri, String.class);
        return new EchoResponse("I am echo2:: " + payload);
    }
    
    @MicroServiceWeaver(msMethod = "echo2", fallbackMethod = "echo")
    public EchoResponse echo(String ping) {
        return new EchoResponse("I am echo:: " + ping);
    }



    com.abccorp.online.business.services.StoreService:getStoreImpl()

    public static StoresResponse getStoresImpl2(final StoresRequest request, final Affiliate affiliate, final StoreEntityAccess storeEntityAccess, final PaymentProcessorEntityAccess paymentProcessorEntityAccess, final ProductEntityAccess productEntityAccess) {
        RestTemplate restTemplate = new RestTemplate();
        URI uri = URI.create("http://localhost:2222/payment/paninfo");
        String payload = restTemplate.getForObject(uri, String.class);
        log.debug("PAYLOAD:: "+payload);
        return new StoresResponse(StoresResponseCode.INVALID_CALL_TYPE_ERROR);
    }
    
    public static StoresResponse msFallbackMethod(final StoresRequest request, final Affiliate affiliate, final StoreEntityAccess storeEntityAccess, final PaymentProcessorEntityAccess paymentProcessorEntityAccess, final ProductEntityAccess productEntityAccess){
        log.debug("Microservice is not working");
        return null;
    }
    
    /**
     * Retrieve store objects for a given request
     */
    @MicroServiceWeaver(msMethod = "getStoresImpl2", fallbackMethod = "msFallbackMethod")
    public static StoresResponse getStoresImpl(final StoresRequest request, final Affiliate affiliate, final StoreEntityAccess storeEntityAccess, final PaymentProcessorEntityAccess paymentProcessorEntityAccess, final ProductEntityAccess productEntityAccess) {
        if (request == null) return new StoresResponse(StoresResponseCode.MISSING_REQUEST_ERROR);
        if (request.getCallType() == null) return new StoresResponse(StoresResponseCode.MISSING_CALL_TYPE_ERROR);
        StoresResponse response = new StoresResponse(StoresResponseCode.OK_INFO);
    .
    .
    .


    Discover how to deploy pre-built sample microservices OR create simple microservices from scratch.

    Topics:
    microservice architecture ,microservices ,monolith ,tutorial

    Opinions expressed by DZone contributors are their own.

    {{ parent.title || parent.header.title}}

    {{ parent.tldr }}

    {{ parent.urlSource.name }}