DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Integration Patterns in Microservices World
  • Scaling Salesforce Apps Using Heroku Microservices - Part 2
  • A Comparison of Single-Page and Multi-Page Applications
  • Enterprise Integration Patterns From ESB to ESP and API

Trending

  • Creating a Web Project: Caching for Performance Optimization
  • Implementing API Design First in .NET for Efficient Development, Testing, and CI/CD
  • Modern Test Automation With AI (LLM) and Playwright MCP
  • Understanding the Shift: Why Companies Are Migrating From MongoDB to Aerospike Database?
  1. DZone
  2. Data Engineering
  3. Data
  4. Integrating Microservices With a Monolithic Application

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.

By 
Tridib Bolar user avatar
Tridib Bolar
·
Jan. 16, 18 · Tutorial
Likes (4)
Comment
Save
Tweet
Share
9.5K Views

Join the DZone community and get the full member experience.

Join For Free

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,  ProductService, PaymentService, etc.). 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 InvocationTargetException, it 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.MicroServiceWeaverAspect, and 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);
    .
    .
    .


    microservice Web Service mobile app

    Opinions expressed by DZone contributors are their own.

    Related

    • Integration Patterns in Microservices World
    • Scaling Salesforce Apps Using Heroku Microservices - Part 2
    • A Comparison of Single-Page and Multi-Page Applications
    • Enterprise Integration Patterns From ESB to ESP and API

    Partner Resources

    ×

    Comments
    Oops! Something Went Wrong

    The likes didn't load as expected. Please refresh the page and try again.

    ABOUT US

    • About DZone
    • Support and feedback
    • Community research
    • Sitemap

    ADVERTISE

    • Advertise with DZone

    CONTRIBUTE ON DZONE

    • Article Submission Guidelines
    • Become a Contributor
    • Core Program
    • Visit the Writers' Zone

    LEGAL

    • Terms of Service
    • Privacy Policy

    CONTACT US

    • 3343 Perimeter Hill Drive
    • Suite 100
    • Nashville, TN 37211
    • support@dzone.com

    Let's be friends:

    Likes
    There are no likes...yet! 👀
    Be the first to like this post!
    It looks like you're not logged in.
    Sign in to see who liked this post!