FINERACT-2304: Create Configuration assembler and move resilience4j props into fineract namespace implement proper retry for SynchronousCommandProcessingService and BatchApiServiceImpl
This commit is contained in:
+9
-3
@@ -23,10 +23,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
import io.cucumber.java8.En;
|
||||
import io.github.resilience4j.retry.Retry;
|
||||
import io.github.resilience4j.retry.RetryRegistry;
|
||||
import io.github.resilience4j.retry.event.RetryEvent;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.fineract.commands.configuration.RetryConfigurationAssembler;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.exception.RollbackTransactionNotApprovedException;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
@@ -50,6 +52,9 @@ public class CommandServiceStepDefinitions implements En {
|
||||
@Autowired
|
||||
private RetryRegistry retryRegistry;
|
||||
|
||||
@Autowired
|
||||
private RetryConfigurationAssembler retryConfigurationAssembler;
|
||||
|
||||
private PortfolioCommandSourceWritePlatformService commandSourceWritePlatformService;
|
||||
|
||||
private DummyCommand command;
|
||||
@@ -67,8 +72,9 @@ public class CommandServiceStepDefinitions implements En {
|
||||
Mockito.when(contextHolder.getAttribute(any(), any())).thenThrow(new CannotAcquireLockException("BLOW IT UP!!!"))
|
||||
.thenThrow(new ObjectOptimisticLockingFailureException("Dummy", new RuntimeException("BLOW IT UP!!!")))
|
||||
.thenThrow(new RollbackTransactionNotApprovedException(1L, null));
|
||||
|
||||
this.retryRegistry.retry("executeCommand").getEventPublisher().onRetry(event -> {
|
||||
Retry retry1 = retryConfigurationAssembler.getRetryConfigurationForExecuteCommand();
|
||||
assertNotNull(retry1);
|
||||
retry1.getEventPublisher().onRetry(event -> {
|
||||
log.warn("... retry event: {}", event);
|
||||
|
||||
counter.incrementAndGet();
|
||||
@@ -92,7 +98,7 @@ public class CommandServiceStepDefinitions implements En {
|
||||
assertEquals(2, retryEvent.getNumberOfRetryAttempts());
|
||||
});
|
||||
|
||||
Then("/^The command processing service execute function should be called 3 times$/", () -> {
|
||||
Then("/^The command processing service execute function should be called 2 times$/", () -> {
|
||||
assertEquals(2, counter.get());
|
||||
});
|
||||
}
|
||||
|
||||
+81
@@ -20,12 +20,21 @@ package org.apache.fineract.commands.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import io.github.resilience4j.retry.Retry;
|
||||
import io.github.resilience4j.retry.RetryConfig;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.batch.exception.ErrorInfo;
|
||||
import org.apache.fineract.commands.configuration.RetryConfigurationAssembler;
|
||||
import org.apache.fineract.commands.domain.CommandProcessingResultType;
|
||||
import org.apache.fineract.commands.domain.CommandSource;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
@@ -39,6 +48,7 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati
|
||||
import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -70,6 +80,9 @@ public class SynchronousCommandProcessingServiceTest {
|
||||
@Mock
|
||||
private CommandSourceService commandSourceService;
|
||||
|
||||
@Mock
|
||||
private RetryConfigurationAssembler retryConfigurationAssembler;
|
||||
|
||||
@Spy
|
||||
private FineractRequestContextHolder fineractRequestContextHolder;
|
||||
|
||||
@@ -83,6 +96,71 @@ public class SynchronousCommandProcessingServiceTest {
|
||||
public void setup() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
when(retryConfigurationAssembler.getRetryConfigurationForExecuteCommand())
|
||||
.thenReturn(Retry.of("IDENTIFIER", RetryConfig.custom().maxAttempts(3).failAfterMaxAttempts(true)
|
||||
.waitDuration(Duration.ofMillis(1)).retryOnException(e -> e instanceof RetryException).build()));
|
||||
ErrorInfo errorInfo = mock(ErrorInfo.class);
|
||||
when(errorInfo.getMessage()).thenReturn("Failed");
|
||||
when(errorInfo.getStatusCode()).thenReturn(500);
|
||||
when(commandSourceService.generateErrorInfo(any())).thenReturn(errorInfo);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void teardown() {
|
||||
reset(context);
|
||||
reset(applicationContext);
|
||||
reset(toApiJsonSerializer);
|
||||
reset(toApiResultJsonSerializer);
|
||||
reset(configurationDomainService);
|
||||
reset(commandHandlerProvider);
|
||||
reset(idempotencyKeyResolver);
|
||||
reset(commandSourceService);
|
||||
reset(retryConfigurationAssembler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteCommandSuccessAfter2Fails() {
|
||||
CommandWrapper commandWrapper = Mockito.mock(CommandWrapper.class);
|
||||
when(commandWrapper.isDatatableResource()).thenReturn(false);
|
||||
when(commandWrapper.isNoteResource()).thenReturn(false);
|
||||
when(commandWrapper.isSurveyResource()).thenReturn(false);
|
||||
when(commandWrapper.isLoanDisburseDetailResource()).thenReturn(false);
|
||||
|
||||
long commandId = 1L;
|
||||
JsonCommand jsonCommand = Mockito.mock(JsonCommand.class);
|
||||
when(jsonCommand.commandId()).thenReturn(commandId);
|
||||
|
||||
NewCommandSourceHandler commandHandler = Mockito.mock(NewCommandSourceHandler.class);
|
||||
CommandProcessingResult commandProcessingResult = Mockito.mock(CommandProcessingResult.class);
|
||||
when(commandProcessingResult.isRollbackTransaction()).thenReturn(false);
|
||||
when(commandHandler.processCommand(jsonCommand)).thenReturn(commandProcessingResult);
|
||||
|
||||
when(commandHandlerProvider.getHandler(Mockito.any(), Mockito.any())).thenReturn(commandHandler);
|
||||
|
||||
when(configurationDomainService.isMakerCheckerEnabledForTask(Mockito.any())).thenReturn(false);
|
||||
String idk = "idk";
|
||||
when(idempotencyKeyResolver.resolve(commandWrapper)).thenReturn(idk);
|
||||
CommandSource commandSource = Mockito.mock(CommandSource.class);
|
||||
when(commandSource.getId()).thenReturn(commandId);
|
||||
when(commandSourceService.findCommandSource(commandWrapper, idk)).thenReturn(null);
|
||||
when(commandSourceService.getCommandSource(commandId)).thenReturn(commandSource);
|
||||
|
||||
AppUser appUser = Mockito.mock(AppUser.class);
|
||||
when(commandSourceService.saveInitialNewTransaction(commandWrapper, jsonCommand, appUser, idk)).thenReturn(commandSource);
|
||||
when(commandSourceService.saveResultSameTransaction(commandSource)).thenReturn(commandSource);
|
||||
when(commandSource.getStatus()).thenReturn(CommandProcessingResultType.PROCESSED.getValue());
|
||||
when(context.authenticatedUser(Mockito.any(CommandWrapper.class))).thenReturn(appUser);
|
||||
|
||||
when(commandSourceService.processCommand(commandHandler, jsonCommand, commandSource, appUser, false))
|
||||
.thenThrow(new RetryException()).thenThrow(new RetryException()).thenReturn(commandProcessingResult);
|
||||
|
||||
CommandProcessingResult actualCommandProcessingResult = underTest.executeCommand(commandWrapper, jsonCommand, false);
|
||||
|
||||
assertEquals(CommandProcessingResultType.PROCESSED.getValue(), commandSource.getStatus());
|
||||
assertEquals(commandProcessingResult, actualCommandProcessingResult);
|
||||
// verify 2x throw before success
|
||||
verify(commandSourceService, times(2)).generateErrorInfo(any());
|
||||
verify(commandSourceService).saveResultSameTransaction(commandSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -186,4 +264,7 @@ public class SynchronousCommandProcessingServiceTest {
|
||||
underTest.publishHookEvent(entityName, actionName, command, Object.class);
|
||||
});
|
||||
}
|
||||
|
||||
private static final class RetryException extends RuntimeException {}
|
||||
|
||||
}
|
||||
|
||||
@@ -259,11 +259,11 @@ spring.batch.initialize-schema=NEVER
|
||||
# Disabling Spring Batch jobs on startup
|
||||
spring.batch.job.enabled=false
|
||||
|
||||
resilience4j.retry.instances.executeCommand.max-attempts=3
|
||||
resilience4j.retry.instances.executeCommand.wait-duration=1s
|
||||
resilience4j.retry.instances.executeCommand.enable-exponential-backoff=true
|
||||
resilience4j.retry.instances.executeCommand.exponential-backoff-multiplier=2
|
||||
resilience4j.retry.instances.executeCommand.retryExceptions=org.springframework.dao.CannotAcquireLockException,org.springframework.orm.ObjectOptimisticLockingFailureException
|
||||
fineract.retry.instances.executeCommand.max-attempts=3
|
||||
fineract.retry.instances.executeCommand.wait-duration=1s
|
||||
fineract.retry.instances.executeCommand.enable-exponential-backoff=true
|
||||
fineract.retry.instances.executeCommand.exponential-backoff-multiplier=2
|
||||
fineract.retry.instances.executeCommand.retryExceptions=org.springframework.dao.CannotAcquireLockException,org.springframework.orm.ObjectOptimisticLockingFailureException
|
||||
|
||||
resilience4j.retry.instances.processJobDetailForExecution.max-attempts=3
|
||||
resilience4j.retry.instances.processJobDetailForExecution.wait-duration=1s
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
|
||||
Feature: Commands Service
|
||||
|
||||
@soma
|
||||
Scenario: Verify that command source write service are working with fallback function
|
||||
Given A command source write service
|
||||
When The user executes the command via a command write service with exceptions
|
||||
Then The command processing service should fallback as expected
|
||||
Then The command processing service execute function should be called 3 times
|
||||
Then The command processing service execute function should be called 2 times
|
||||
|
||||
|
||||
Reference in New Issue
Block a user