diff --git a/docker-compose.yml b/docker-compose.yml index d633cbb..1bf031f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,6 +56,7 @@ services: - "5432:5432" volumes: - ./script.sql:/docker-entrypoint-initdb.d/script.sql + command: 'postgres -c max_connections=1000 -c work_mem=8MB -c hot_standby=off -c shared_buffers=135MB -c checkpoint_timeout=1d -c wal_level=minimal -c synchronous_commit=off -c fsync=off -c full_page_writes=off -c max_wal_senders=0' deploy: resources: limits: diff --git a/nginx.conf b/nginx.conf index 7eb00d0..44ed7b9 100755 --- a/nginx.conf +++ b/nginx.conf @@ -1,5 +1,5 @@ events { - worker_connections 400; + worker_connections 1000; } http { diff --git a/src/main/java/com/github/rinha/controllers/CustomerController.java b/src/main/java/com/github/rinha/controllers/CustomerController.java index 5eed014..fece3dd 100644 --- a/src/main/java/com/github/rinha/controllers/CustomerController.java +++ b/src/main/java/com/github/rinha/controllers/CustomerController.java @@ -20,24 +20,21 @@ public CustomerController(AccountService accountService) { @GetMapping("/clientes/{id}/extrato") public Mono> getExtratoByClienteId(@PathVariable int id) { - if (!accountService.isValidCustomerId(id)) - return Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).build()); - - return accountService.findStatementByCustomerId(id) + return Mono.just(id) + .filterWhen(accountService::isValidCustomerId) + .flatMap(accountService::findStatementByCustomerId) .map(ResponseEntity::ok) - .onErrorReturn(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build()); + .defaultIfEmpty(ResponseEntity.status(HttpStatus.NOT_FOUND).build()); } @PostMapping("/clientes/{id}/transacoes") public Mono> transacionar(@PathVariable int id, @RequestBody TransactionRequest transaction) { - if (!accountService.isValidCustomerId(id)) { - return Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).build()); - } else if (!transaction.isRequestValid()) { - return Mono.just(ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build()); - } else { - return accountService.updateBalanceAndInsertTransaction(id, transaction.parseValueToInt(), transaction.tipo(), transaction.descricao()) - .map(ResponseEntity::ok) - .onErrorReturn(ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build()); - } + return Mono.just(id) + .filterWhen(accountService::isValidCustomerId) + .flatMap(unused -> accountService.isTransactionValid(transaction)) + .flatMap(clientId -> accountService.updateBalanceAndInsertTransaction(id, transaction.parseValueToInt(), transaction.tipo(), transaction.descricao())) + .map(ResponseEntity::ok) + .switchIfEmpty(Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).build())) + .onErrorReturn(ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build()); } } diff --git a/src/main/java/com/github/rinha/services/AccountService.java b/src/main/java/com/github/rinha/services/AccountService.java index 62c707c..77823ba 100644 --- a/src/main/java/com/github/rinha/services/AccountService.java +++ b/src/main/java/com/github/rinha/services/AccountService.java @@ -43,7 +43,11 @@ public Mono updateBalanceAndInsertTransaction(int id, int value, St return Mono.just(new CustomerDTO(customer.getMaxLimit(), newBalance)); } - public Boolean isValidCustomerId(int id) { - return this.accountPersistence.existsCustomerById(id); + public Mono isValidCustomerId(int id) { + return Mono.just(this.accountPersistence.existsCustomerById(id)); + } + + public Mono isTransactionValid(TransactionRequest transaction) { + return transaction.isRequestValid() ? Mono.just(true): Mono.error(UnprocessableException::new); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a7b3609..a5f7dde 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,5 +2,4 @@ spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://${DB_HOSTNAME:localhost}:5432/${POSTGRES_DB:rinha} spring.datasource.username=${POSTGRES_USER:admin} spring.datasource.password=${DATABASE_PASSWORD:123} -spring.datasource.hikari.maximum-pool-size=10 -spring.main.banner-mode=off \ No newline at end of file +spring.datasource.hikari.maximum-pool-size=15 diff --git a/src/test/java/com/github/rinha/AccountServiceTest.java b/src/test/java/com/github/rinha/AccountServiceTest.java index 982b24f..6dbfde8 100644 --- a/src/test/java/com/github/rinha/AccountServiceTest.java +++ b/src/test/java/com/github/rinha/AccountServiceTest.java @@ -3,6 +3,7 @@ import com.github.rinha.domain.Customer; import com.github.rinha.domain.dtos.CustomerDTO; import com.github.rinha.domain.dtos.StatementDTO; +import com.github.rinha.domain.dtos.TransactionRequest; import com.github.rinha.domain.dtos.TransactionResponse; import com.github.rinha.domain.exceptions.UnprocessableException; import com.github.rinha.persistence.AccountPersistence; @@ -19,7 +20,6 @@ import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; @@ -88,10 +88,12 @@ void shouldVerifyCustomerId() { when(this.accountPersistence.existsCustomerById(anyInt())).thenReturn(true); // When - Boolean isCustomerValid = this.accountService.isValidCustomerId(1); + Mono isCustomerValid = this.accountService.isValidCustomerId(1); // Then - assertEquals(true, isCustomerValid); + StepVerifier.create(isCustomerValid) + .expectNextMatches(b -> b.equals(true)) + .verifyComplete(); } @Test @@ -100,9 +102,33 @@ void shouldVerifyIfIsInvalidCustomerId() { when(this.accountPersistence.existsCustomerById(anyInt())).thenReturn(false); // When - Boolean isCustomerValid = this.accountService.isValidCustomerId(1); + Mono isCustomerValid = this.accountService.isValidCustomerId(1); // Then - assertEquals(false, isCustomerValid); + StepVerifier.create(isCustomerValid) + .expectNextMatches(b -> b.equals(false)) + .verifyComplete(); + } + + @Test + void shouldVerifyIfIsTransactionValid() { + // When + Mono isTransactionValid = this.accountService.isTransactionValid(new TransactionRequest("1", "c", "test")); + + // Then + StepVerifier.create(isTransactionValid) + .expectNextMatches(b -> b.equals(true)) + .verifyComplete(); + } + + @Test + void shouldVerifyIfIsTransactionInvalid() { + // When + Mono isTransactionValid = this.accountService.isTransactionValid(new TransactionRequest("c", "c", "test")); + + // Then + StepVerifier.create(isTransactionValid) + .expectErrorMatches(throwable -> throwable instanceof UnprocessableException) + .verify(); } } diff --git a/src/test/java/com/github/rinha/CustomerControllerTest.java b/src/test/java/com/github/rinha/CustomerControllerTest.java index 493efcf..74afc86 100644 --- a/src/test/java/com/github/rinha/CustomerControllerTest.java +++ b/src/test/java/com/github/rinha/CustomerControllerTest.java @@ -31,7 +31,7 @@ public class CustomerControllerTest { @Test void shouldBeRetrieveStatement() { // Given - when(accountService.isValidCustomerId(anyInt())).thenReturn(true); + when(accountService.isValidCustomerId(anyInt())).thenReturn(Mono.just(true)); when(accountService.findStatementByCustomerId(anyInt())).thenReturn(Mono.just(new StatementDTO(new BalanceDTO(10, 10), List.of(new TransactionResponse(1, "c", "test", "2024-02-19"))))); // When and Then @@ -47,7 +47,7 @@ void shouldBeRetrieveStatement() { @Test void shouldBeReturnNotFoundWhenCustomerIdIsInvalidForStatement() { // Given - when(accountService.isValidCustomerId(anyInt())).thenReturn(false); + when(accountService.isValidCustomerId(anyInt())).thenReturn(Mono.just(false)); // When and Then controller.getExtratoByClienteId(123) @@ -62,7 +62,8 @@ void shouldBeCreateTransaction() { // Given TransactionRequest transaction = new TransactionRequest("100", "c", "test"); - when(accountService.isValidCustomerId(anyInt())).thenReturn(true); + when(accountService.isValidCustomerId(anyInt())).thenReturn(Mono.just(true)); + when(accountService.isTransactionValid(transaction)).thenReturn(Mono.just(true)); when(accountService.updateBalanceAndInsertTransaction(anyInt(), anyInt(), anyString(), anyString())) .thenReturn(Mono.just(new CustomerDTO(123, 10))); @@ -71,6 +72,7 @@ void shouldBeCreateTransaction() { .doOnNext(response -> { assert response.getStatusCodeValue() == HttpStatus.OK.value(); verify(accountService, times(1)).isValidCustomerId(123); + verify(accountService, times(1)).isTransactionValid(transaction); verify(accountService, times(1)).updateBalanceAndInsertTransaction(123, transaction.parseValueToInt(), transaction.tipo(), transaction.descricao()); }) .block(); @@ -81,7 +83,7 @@ void shouldBeReturnNotFoundWhenCustomerIdIsInvalid() { // Given TransactionRequest transaction = new TransactionRequest("100", "c", "test"); - when(accountService.isValidCustomerId(anyInt())).thenReturn(false); + when(accountService.isValidCustomerId(anyInt())).thenReturn(Mono.just(false)); // When and Then controller.transacionar(123, transaction) @@ -94,8 +96,10 @@ void shouldBeReturnNotFoundWhenCustomerIdIsInvalid() { @Test void shouldBeReturnUnprocessableEntityWhenTransactionIsInvalid() { // Given - TransactionRequest transaction = new TransactionRequest("100", "e", "test"); - when(accountService.isValidCustomerId(anyInt())).thenReturn(true); + TransactionRequest transaction = new TransactionRequest("100", "c", "test"); + + when(accountService.isValidCustomerId(anyInt())).thenReturn(Mono.just(true)); + when(accountService.isTransactionValid(transaction)).thenReturn(Mono.just(false)); // When and Then controller.transacionar(123, transaction)