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:
Note: This should not be committed. It is only for loading the OpenContract library from the local Maven repository.
repositories {
jcenter()
mavenCentral()
mavenLocal()
}
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:
Note: Make Kafka-related changes wherever necessary.
// Old import
import org.navida.kafka.utils.consumer.KafkaConsumer;
// New import
import org.navida.opencontract.kafka.utils.consumer.KafkaConsumer;
Step 6: Create AuditEventListener
Class
-
Create the
AuditEventListener
class inside theutils
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 theutils
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:BusinessAuditException
with the following message format.tenantId
- If the integrating service does not have a tenant context, you should pass thetenantId
. 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.