Skip to main content
Version: Next

OpenContract_integration

Steps to Implement OpenContract Library Inside Services

Step 1: Clone the Repository

  • Clone the navida-pro-be-open-contract repository locally.
  • Build the service.
  • Publish it to Maven.

Step 2: Modify build.gradle File

  • Include OpenContract dependencies(latest version to be refered).
    implementation  'org.navida.be.opencontract:navida-pro-be-open-contract:1.0.8'
  • Add mavenLocal() inside repositories block to import OpenContract from the local Maven repository:
    repositories {
    jcenter()
    mavenCentral()
    mavenLocal()
    }
    Note: This should not be committed. It is only for loading the OpenContract library from the local Maven repository.

Step 3: Update Values.yml

  • Add the following property:
    env:
    audit-log-exception-save-request-topic-name: audit-log-exception-save-request

Step 4: Modify the Spring Boot Application Class

  • Replace "org.navida.kafka.utils" by "org.navida.opencontract"(OpenContract package name) for component scanning inside the main class.

    @SpringBootApplication(
    nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class
    )
    @ComponentScan(
    basePackages = {"org.navida.pro", "org.navida.pro.controller" , "org.navida.pro.configuration", "org.navida.opencontract"},
    nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class
    )
  • Include below code in application.properties for local build

    user-challenges-ds-service.health-goal-update-request-ack-topic-name = aokplus-health-goal-update-ack-request
    audit-log-exception-save-request-topic-name = audit-log-exception-save-request

  • Include below code in application-test.properties

    audit-log-exception-save-request-topic-name = audit-log-exception-save-request
  • Build the application.

Step 5: Remove Existing Kafka Util Package

  • Delete the Kafka-utils package from the application.
  • Remove below line from build.gradle file.
    implementation project(path: ':kafka-utils')
  • Remove below line from settings.gradle file.
    include 'kafka-utils'
  • Update Kafka paths from the existing path to the OpenContract path. For example:
    // Old import
    import org.navida.kafka.utils.consumer.KafkaConsumer;

    // New import
    import org.navida.opencontract.kafka.utils.consumer.KafkaConsumer;
    Note: Make Kafka-related changes wherever necessary.

Step 6: Create AuditEventListener Class

  • Create the AuditEventListener class inside the utils package with the following code:

    package org.navida.pro.utils;

    import io.micrometer.tracing.Tracer;
    import org.navida.opencontract.exception.BusinessAuditException;
    import org.navida.opencontract.logger.AuditLogEvent;
    import org.navida.opencontract.logger.BusinessLogger;
    import org.navida.opencontract.request.AuditLogRequestDto;
    import org.navida.pro.multitenancy.TenantContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.event.EventListener;
    import org.springframework.stereotype.Component;

    import java.util.Objects;
    import java.util.concurrent.CompletableFuture;

    @Component
    public class AuditEventListener {
    @Value("${spring.application.name}")
    private String serviceName;
    private static final Logger logger = LoggerFactory.getLogger(AuditEventListener.class);
    private final BusinessLogger businessLogger;
    private final Tracer tracer;
    private static final String BACKEND = "BACKEND";

    public AuditEventListener(BusinessLogger businessLogger, Tracer tracer) {
    this.businessLogger = businessLogger;
    this.tracer = tracer;
    }

    @EventListener
    public void handleAuditLogEvent(AuditLogEvent event) {
    BusinessAuditException ex = event.getMessage();
    String traceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId();
    String tenantId = event.getTenantId() == null ? TenantContext.getCurrentTenant() : event.getTenantId();
    logger.info("inside AuditLogEvent");
    CompletableFuture.runAsync(() -> {
    try {
    String[] message = ex.getMessage().split(":");
    String functionName = message[1].equalsIgnoreCase("NA") ? ex.getStackTrace()[0].getMethodName() : message[1];
    String exceptionDetails = message[3].equalsIgnoreCase("NA") ? message[2] : message[3];

    businessLogger.businessError(AuditLogRequestDto.builder()
    .serviceName(serviceName)
    .functionName(functionName)
    .channelType(BACKEND)
    .traceId(traceId)
    .exceptionMessage(message[2])
    .exceptionDetails(message[0] + ": " + exceptionDetails)
    .build(),
    tenantId);
    logger.info("Business Error is successfully logged: {}", ex.getMessage());
    } catch (Exception e) {
    logger.error("Exception occurred while logging business error: {}", e.getMessage());
    }
    });
    }
    }

Step 7: Create AuditEventListenerTest Class

  • Create the AuditEventListenerTest class inside the utils test package with the following code:
    package org.navida.pro.utills;

    import io.micrometer.tracing.Span;
    import io.micrometer.tracing.TraceContext;
    import io.micrometer.tracing.Tracer;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    import org.navida.opencontract.exception.BusinessAuditException;
    import org.navida.opencontract.logger.AuditLogEvent;
    import org.navida.opencontract.logger.BusinessLogger;
    import org.navida.opencontract.request.AuditLogRequestDto;
    import org.navida.pro.multitenancy.TenantContext;
    import org.navida.pro.utils.AuditEventListener;

    import static org.mockito.ArgumentMatchers.any;
    import static org.mockito.Mockito.*;

    class AuditEventListenerTest {

    @Mock
    private BusinessLogger businessLogger;

    @Mock
    private Tracer tracer;

    @Mock
    private Span span;

    @Mock
    private TraceContext traceContext;

    @InjectMocks
    private AuditEventListener auditEventListener;

    @BeforeEach
    void setUp() {
    MockitoAnnotations.openMocks(this);
    when(tracer.currentSpan()).thenReturn(span);
    when(span.context()).thenReturn(traceContext);
    when(span.context().traceId()).thenReturn("testTraceId");
    TenantContext.setCurrentTenant("testTenantId");
    }

    @Test
    void testHandleAuditLogEvent_Success() {
    // Given
    AuditLogEvent event = mock(AuditLogEvent.class);
    BusinessAuditException ex = new BusinessAuditException("Error:NA:Error Message:NA");
    when(event.getMessage()).thenReturn(ex);
    when(event.getTenantId()).thenReturn("testTenantId");

    // When
    auditEventListener.handleAuditLogEvent(event);

    // Then
    verify(businessLogger, timeout(1000)).businessError(any(AuditLogRequestDto.class), eq("testTenantId"));
    }

    @Test
    void testHandleAuditLogEvent_SuccessWithTenantIdNull() {
    // Given
    AuditLogEvent event = mock(AuditLogEvent.class);
    BusinessAuditException ex = new BusinessAuditException("Error:FunctionName:Error Message:MessageDetails");
    when(event.getMessage()).thenReturn(ex);
    when(event.getTenantId()).thenReturn(null);

    // When
    auditEventListener.handleAuditLogEvent(event);

    // Then
    verify(businessLogger, timeout(1000)).businessError(any(AuditLogRequestDto.class), eq("testTenantId"));
    }

    @Test
    void testHandleAuditLogEvent_Failed() {
    // Given
    AuditLogEvent event = mock(AuditLogEvent.class);
    BusinessAuditException ex = new BusinessAuditException("Error");
    when(event.getMessage()).thenReturn(ex);
    when(event.getTenantId()).thenReturn(null);

    // When
    auditEventListener.handleAuditLogEvent(event);
    }

    }

Step 8: Autowire AuditEventPublisher

  • Use the code below to autowire the AuditEventPublisher in the classes where business errors need to be logged:

    private final AuditEventPublisher auditEventPublisher;

    public CronServiceImpl(
    AuditEventPublisher auditEventPublisher)
    {
    this.auditEventPublisher = auditEventPublisher;
    }
  • Call the publishEvent method to log business errors. For example:

    auditEventPublisher.publishEvent(new BusinessAuditException("ERROR:startConJob:Exception in cron job, sports challenge status update for the user "+userResult.getUserId()+ ":"+e.getMessage()), null);                

    Note: The publishEvent method accepts two parameters:

    1. BusinessAuditException with the following message format.
    2. tenantId - If the integrating service does not have a tenant context, you should pass the tenantId. If the service has a tenant context, it will take it from the context.
  • The message format insied BusinessAuditException should be as follows:

    WARN/ERROR:FunctionName:Message:MessageDetails
    • WARN/ERROR: Indicates the type of business error.
    • FunctionName: The name of the function or method where the business error is logged.
    • Message: The error message.
    • MessageDetails: Additional details about the error message.

Note: Steps 1 to 7 are initial setup steps. Once these steps are completed in any service, only Step 8 needs to be followed for subsequent implementations.